pike.git/lib/modules/Sql.pmod/pgsql.pike:71:
mapping(string:mixed) options;
protected array(string) lastmessage=({});
protected int clearmessage;
protected mapping(string:array(mixed)) notifylist=([]);
mapping(string:string) _runtimeparameter;
protected enum querystate {
streconnect,reconnectforce,queryidle,inquery,cancelpending,canceled};
protected querystate qstate;
protected mapping(string:mapping(string:mixed)) prepareds=([]);
protected int pstmtcount;
- protected int ptstmtcount;
+ protected int ptstmtcount; // Periodically one would like to reset this
+ // but checking when this is safe to do
+ // probably is more costly than the gain
int _pportalcount;
protected int totalhits;
protected int cachedepth=STATEMENTCACHEDEPTH;
protected int timeout=QUERYTIMEOUT;
protected int portalbuffersize=PORTALBUFFERSIZE;
protected int reconnected; // Number of times the connection was reset
protected int reconnectdelay; // Time to next reconnect
#ifdef PG_STATS
protected int skippeddescribe; // Number of times we skipped Describe phase
protected int portalsopened; // Number of portals opened
-
+ protected int prepstmtused; // Number of times prepared statements were used
#endif
int _msgsreceived; // Number of protocol messages received
int _bytesreceived; // Number of bytes received
protected int warningsdropcount; // Number of uncollected warnings
- protected int prepstmtused; // Number of times prepared statements were used
+
protected int warningscollected;
protected int invalidatecache;
protected Thread.Queue qportals;
mixed _delayederror;
protected function (:void) readyforquery_cb;
string _host;
protected string database, user, pass;
int _port;
protected multiset cachealways=(<"BEGIN","begin","END","end","COMMIT","commit">);
protected object createprefix
=Regexp("^[ \t\f\r\n]*[Cc][Rr][Ee][Aa][Tt][Ee][ \t\f\r\n]");
protected object dontcacheprefix
=Regexp("^[ \t\f\r\n]*([Ff][Ee][Tt][Cc][Hh]|[Cc][Oo][Pp][Yy])[ \t\f\r\n]");
protected object execfetchlimit
=Regexp("^[ \t\f\r\n]*(([Uu][Pp][Dd][Aa]|[Dd][Ee][Ll][Ee])[Tt][Ee]|\
[Ii][Nn][Ss][Ee][Rr][Tt])[ \t\f\r\n]|\
[ \t\f\r\n][Ll][Ii][Mm][Ii][Tt][ \t\f\r\n]+[12][; \t\f\r\n]*$");
protected Thread.Mutex waitforauth;
protected Thread.Condition waitforauthready;
-
+ int _readyforquerycount;
#define DERROR(msg ...) ({sprintf(msg),backtrace()})
#define SERROR(msg ...) (sprintf(msg))
#define USERERROR(msg) throw(msg)
#define SUSERERROR(msg ...) USERERROR(SERROR(msg))
protected string _sprintf(int type, void|mapping flags) {
string res=UNDEFINED;
switch(type) {
case 'O':
pike.git/lib/modules/Sql.pmod/pgsql.pike:421:
//! A set of statistics for the current session:
//! @mapping
//! @member int "warnings_dropped"
//! Number of warnings/notices generated by the database but not
//! collected by the application by using @[error()] after the statements
//! that generated them.
#ifdef PG_STATS
//! @member int "skipped_describe_count"
//! Number of times the driver skipped asking the database to
//! describe the statement parameters because it was already cached.
- #endif
+
//! @member int "used_prepared_statements"
//! Numer of times prepared statements were used from cache instead of
//! reparsing in the current session.
-
+ #endif
//! @member int "current_prepared_statements"
//! Cache size of currently prepared statements.
//! @member int "current_prepared_statement_hits"
//! Sum of the number hits on statements in the current statement cache.
//! @member int "prepared_statement_count"
//! Total number of prepared statements generated.
#ifdef PG_STATS
//! @member int "portals_opened_count"
//! Total number of portals opened, i.e. number of statements issued
//! to the database.
pike.git/lib/modules/Sql.pmod/pgsql.pike:453:
//! @member int "portals_in_flight"
//! Currently still open portals, i.e. running statements.
//! @endmapping
//!
//! @note
//! This function is PostgreSQL-specific, and thus it is not available
//! through the generic SQL-interface.
mapping(string:mixed) getstatistics() {
mapping(string:mixed) stats=([
"warnings_dropped":warningsdropcount,
- "used_prepared_statements":prepstmtused,
+
"current_prepared_statements":sizeof(prepareds),
"current_prepared_statement_hits":totalhits,
"prepared_statement_count":pstmtcount,
#ifdef PG_STATS
-
+ "used_prepared_statements":prepstmtused,
"skipped_describe_count":skippeddescribe,
"portals_opened_count":portalsopened,
#endif
"messages_received":_msgsreceived,
"bytes_received":_bytesreceived,
"reconnect_count":reconnected,
"portals_in_flight":_portalsinflight,
]);
return stats;
}
pike.git/lib/modules/Sql.pmod/pgsql.pike:606:
protected void storetiming(object portal) {
mapping(string:mixed) tp=portal._tprepared;
tp.trun=gethrtime()-tp.trunstart;
m_delete(tp,"trunstart");
portal._tprepared = UNDEFINED;
}
final void _processloop(object ci) {
int die=0,terminating=0;
- .pgsql_util.pgsql_result portal;
+ int|.pgsql_util.pgsql_result portal;
mixed err;
{
object plugbuffer=Stdio.Buffer()->add_int32(PG_PROTOCOL(3,0));
if(user)
plugbuffer->add("user\0")->add(user)->add_int8(0);
if(database)
plugbuffer->add("database\0")->add(database)->add_int8(0);
options->reconnect=undefinedp(options->reconnect) || options->reconnect;
foreach(options
-(<"use_ssl","force_ssl","cache_autoprepared_statements","reconnect",
"text_query","is_superuser","server_encoding","server_version",
"integer_datetimes","session_authorization">);
string name;mixed value)
plugbuffer->add(name)->add_int8(0)->add((string)value)->add_int8(0);
plugbuffer->add_int8(0);
PD("%O\n",(string)plugbuffer);
ci->start()->add_hstring(plugbuffer,4,4)->sendcmd(flushsend);
}
cancelsecret=0;
- PD("Processloop\n");
+
#ifdef PG_DEBUG
-
+ PD("Processloop\n");
string datarowdebug;
int datarowdebugcount;
-
+
+ void showportal(int msgtype) {
+ if(objectp(portal))
+ PD("<%O %d %c switch portal\n",
+ portal._portalname,++ci->queueinidx,msgtype);
+ else if(portal>0)
+ PD("<Sync %d %d %c portal\n",++ci->queueinidx,portal,msgtype);
+ };
#endif
for(;;) {
err=catch {
#ifdef PG_DEBUG
if(!portal && datarowdebug) {
PD("%s rows %d\n",datarowdebug,datarowdebugcount);
datarowdebug=0; datarowdebugcount=0;
}
#endif
int msgtype=ci->read_int8();
if(!portal) {
portal=qportals->try_read();
- if(portal)
- PD("<%O %d %c switch portal ===================\n",
- portal._portalname,++ci->queueinidx,msgtype);
+ #ifdef PG_DEBUG
+ showportal(msgtype);
+ #endif
}
if(qstate==cancelpending)
qstate=canceled,sendclose();
int msglen=ci->read_int32();
_msgsreceived++;
_bytesreceived+=1+msglen;
enum errortype {
noerror=0,
protocolerror,
protocolunsupported
pike.git/lib/modules/Sql.pmod/pgsql.pike:746:
PD("GSS\n");
errtype=protocolunsupported;
break;
case 9:
PD("SSPI\n");
errtype=protocolunsupported;
break;
case 8:
PD("GSSContinue\n");
errtype=protocolunsupported;
+ cancelsecret=ci->read(msglen); // Actually SSauthdata
#ifdef PG_DEBUG
if(msglen<1)
errtype=protocolerror;
- #endif
- cancelsecret=ci->read(msglen); // Actually SSauthdata
- #ifdef PG_DEBUG
+
msglen=0;
#endif
break;
default:
PD("Unknown Authentication Method %c\n",authtype);
errtype=protocolunsupported;
break;
}
switch(errtype) {
case noerror:
pike.git/lib/modules/Sql.pmod/pgsql.pike:776:
default:
case protocolunsupported:
ERROR("Unsupported authenticationmethod %c\n",authtype);
break;
}
break;
}
case 'K':
msglen-=4+4;backendpid=ci->read_int32();
cancelsecret=ci->read(msglen);
- PD("<BackendKeyData %O\n",cancelsecret);
+
#ifdef PG_DEBUG
-
+ PD("<BackendKeyData %O\n",cancelsecret);
msglen=0;
#endif
break;
case 'S': {
PD("<ParameterStatus ");
msglen-=4;
array(string) ts=reads();
#ifdef PG_DEBUG
if(sizeof(ts)==2) {
#endif
_runtimeparameter[ts[0]]=ts[1];
- PD("%O=%O\n",ts[0],ts[1]);
+
#ifdef PG_DEBUG
-
+ PD("%O=%O\n",ts[0],ts[1]);
} else
errtype=protocolerror;
#endif
break;
}
case '3':
- PD("<CloseComplete\n");
+
#ifdef PG_DEBUG
-
+ PD("<CloseComplete\n");
msglen-=4;
#endif
break;
case 'Z':
-
+ backendstatus=ci->read_int8();
#ifdef PG_DEBUG
msglen-=4+1;
- #endif
- backendstatus=ci->read_int8();
+
PD("<ReadyForQuery %c\n",backendstatus);
-
+ #endif
+ for(;objectp(portal);portal->read()) {
+ #ifdef PG_DEBUG
+ showportal(msgtype);
+ #endif
+ portal->_closeportal();
+ }
+ foreach(qportals->peek_array();;.pgsql_util.pgsql_result qp) {
+ PD("Checking portal %O %d<=%d\n",
+ qp._portalname,qp._synctransact,portal);
+ if(qp._synctransact && qp._synctransact<=portal)
+ qp->_closeportal();
+ }
+ portal=0;
+ _readyforquerycount--;
if(readyforquery_cb)
readyforquery_cb(),readyforquery_cb=0;
qstate=queryidle;
if(waitforauthready) {
Thread.MutexKey lock=waitforauth->lock();
waitforauthready->broadcast();
waitforauthready=0;
lock=0;
}
break;
case '1':
- PD("<ParseComplete\n");
+
#ifdef PG_DEBUG
-
+ PD("<ParseComplete\n");
msglen-=4;
#endif
break;
case 't': {
array a;
int cols=ci->read_int16();
- PD("<%O ParameterDescription %d values\n",portal._query,cols);
+
#ifdef PG_DEBUG
-
+ PD("<%O ParameterDescription %d values\n",portal._query,cols);
msglen-=4+2+4*cols;
#endif
foreach(a=allocate(cols);int i;)
a[i]=ci->read_int32();
#ifdef PG_DEBUGMORE
PD("%O\n",a);
#endif
if(portal._tprepared)
portal._tprepared.datatypeoid=a;
preparebind(portal);
pike.git/lib/modules/Sql.pmod/pgsql.pike:888:
PD("%O\n",a);
#endif
portal._datarowdesc=a;
processrowdescription(portal);
portal=0;
break;
}
case 'n': {
#ifdef PG_DEBUG
msglen-=4;
- #endif
+
PD("<NoData %O\n",portal._query);
-
+ #endif
portal._datarowdesc=({});
portal._fetchlimit=0; // disables subsequent Executes
processrowdescription(portal);
portal=0;
break;
}
case 'H':
portal._datarowdesc=getcols();
PD("<CopyOutResponse %d %O\n",
sizeof(portal._datarowdesc),portal._query);
processrowdescription(portal);
break;
case '2': {
mapping tp;
#ifdef PG_DEBUG
msglen-=4;
- #endif
+
PD("<%O BindComplete\n",portal._portalname);
-
+ #endif
if(tp=portal._tprepared) {
int tend=gethrtime();
int tstart=tp.trun;
if(tend==tstart)
m_delete(prepareds,portal._query);
else {
tp.hits++;
totalhits++;
if(!tp.preparedname) {
if(sizeof(portal._preparedname))
pike.git/lib/modules/Sql.pmod/pgsql.pike:933:
tp.trunstart=tend;
}
}
break;
}
case 'D': {
msglen-=4;
string serror;
if(portal._tprepared)
storetiming(portal);
- #ifdef USEPGsql
- ci->decodedatarow(msglen);msglen=0;
- #else
+
array a, datarowdesc;
portal._bytesreceived+=msglen;
datarowdesc=portal._datarowdesc;
int cols=ci->read_int16();
#ifdef PG_DEBUG
#ifdef PG_DEBUGMORE
PD("<%O DataRow %d cols %d bytes\n",portal._portalname,cols,msglen);
#endif
datarowdebugcount++;
if(!datarowdebug)
pike.git/lib/modules/Sql.pmod/pgsql.pike:1018:
}
}
a[i]=value;
} else if(!collen)
a[i]="";
}
portal._inflight--;
portal._datarows->write(a);
if(serror)
ERROR(serror);
- #endif // USEPGsql
+
portal->_processdataready(fetchlimit);
break;
}
case 's':
- PD("<%O PortalSuspended\n",portal._portalname);
- #if !STREAMEXECUTES
- portal->_sendexecute(portal._fetchlimit);
- #endif
+
#ifdef PG_DEBUG
-
+ PD("<%O PortalSuspended\n",portal._portalname);
msglen-=4;
#endif
portal=0;
break;
case 'C': {
msglen-=4;
#ifdef PG_DEBUG
if(msglen<1)
errtype=protocolerror;
#endif
pike.git/lib/modules/Sql.pmod/pgsql.pike:1056: Inside #if defined(PG_DEBUG)
errtype=protocolerror;
msglen=0;
#else
ci->consume(1);
#endif
portal->_releasesession();
portal=0;
break;
}
case 'I':
- PD("<EmptyQueryResponse %O\n",portal._portalname);
+
#ifdef PG_DEBUG
-
+ PD("<EmptyQueryResponse %O\n",portal._portalname);
msglen-=4;
#endif
portal->_releasesession();
portal=0;
break;
case 'd':
PD("<%O CopyData\n",portal._portalname);
if(portal._tprepared)
storetiming(portal);
msglen-=4;
pike.git/lib/modules/Sql.pmod/pgsql.pike:1098:
}
break;
case 'c':
#ifdef PG_DEBUG
PD("<%O CopyDone\n",portal._portalname);
msglen-=4;
#endif
portal=0;
break;
case 'E': {
- if(portal)
- portal->_releasesession();
+ if(!_readyforquerycount)
+ sendsync();
PD("<%O ErrorResponse %O\n",
- portal&&portal._portalname,portal&&portal._query);
+ portal&&(portal._portalname||portal._preparedname),
+ portal&&portal._query);
mapping(string:string) msgresponse;
msgresponse=getresponse();
warningsdropcount+=warningscollected;
warningscollected=0;
switch(msgresponse.C) {
case "P0001":
lastmessage=({sprintf("%s: %s",msgresponse.S,msgresponse.M)});
USERERROR(a2nls(lastmessage
+({pinpointerror(portal._query,msgresponse.P)})
+showbindings(portal)));
pike.git/lib/modules/Sql.pmod/pgsql.pike:1136:
pinpointerror(portal&&portal._query,msgresponse.P)+
pinpointerror(msgresponse.q,msgresponse.p)});
if(msgresponse.W)
lastmessage+=({msgresponse.W});
lastmessage+=showbindings(portal);
switch(msgresponse.S) {
case "PANIC":werror(a2nls(lastmessage));
}
USERERROR(a2nls(lastmessage));
}
+ if(portal)
+ portal->_releasesession();
break;
}
case 'N': {
PD("<NoticeResponse\n");
mapping(string:string) msgresponse;
msgresponse=getresponse();
if(clearmessage) {
warningsdropcount+=warningscollected;
clearmessage=warningscollected=0;
lastmessage=({});
pike.git/lib/modules/Sql.pmod/pgsql.pike:1222:
ERROR(a2nls(lastmessage+=({msg})));
}
}; // We only get here if there is an error
if(err==MAGICTERMINATE) {
ci->start()->add("X\0\0\0\4")->sendcmd(sendout);
terminating=1;
if(!sizeof(ci))
break;
}
if(stringp(err)) {
- object to=portal?portal:this;
- if(!to._delayederror)
- to._delayederror=err;
+ .pgsql_util.pgsql_result or;
+ if(!(or=portal))
+ or=this;
+ if(!or._delayederror)
+ or._delayederror=err;
+ if(portal)
+ portal->_releasesession();
+ portal=0;
continue;
}
break;
}
_delayederror=err;
if(!ci->close() && !terminating && options.reconnect)
_connectfail();
}
//! Closes the connection to the database, any running queries are
pike.git/lib/modules/Sql.pmod/pgsql.pike:1287:
Thread.MutexKey lock=waitforauth->lock();
if(waitforauthready) {
lock=0;
return 0; // Connect still in progress in other thread
}
waitforauthready=Thread.Condition();
lock=0;
}
if(c) {
reconnected++;
+ #ifdef PG_STATS
prepstmtused=0;
-
+ #endif
if(!force)
c->sendterminate();
else
c->close();
c=0;
foreach(prepareds;;mapping tp)
m_delete(tp,"preparedname");
if(!options.reconnect)
return 0;
}
qportals=Thread.Queue();
-
+ _readyforquerycount=1;
+ qportals->write(1);
if(!(c=getsocket())) {
string msg=sprintf("Couldn't connect to database on %s:%d",_host,_port);
if(force) {
if(!sizeof(lastmessage) || lastmessage[sizeof(lastmessage)-1]!=msg)
lastmessage+=({msg});
return 0;
} else
ERROR(msg+"\n");
}
_runtimeparameter=([]);
pike.git/lib/modules/Sql.pmod/pgsql.pike:1341:
m_delete(tp,"datatypeoid");
m_delete(tp,"datarowdesc");
}
big_query("ROLLBACK");
big_query("RESET ALL");
big_query("CLOSE ALL");
big_query("DISCARD TEMP");
}
}
+ protected void sendsync() {
+ _readyforquerycount++;
+ c->start()->sendcmd(syncsend);
+ }
+
//! @decl void resync()
//!
//! Resyncs the database session; typically used to make sure the session is
//! not still in a dangling transaction.
//!
//! If called while the connection is in idle state, the function is
//! lightweight and briefly touches base with the database server to
//! make sure client and server are in sync.
//!
//! If issued while inside a transaction, it will rollback the transaction,
pike.git/lib/modules/Sql.pmod/pgsql.pike:1367:
//! @note
//! This function is PostgreSQL-specific, and thus it is not available
//! through the generic SQL-interface.
void resync(void|int|object portal) {
mixed err;
if(!is_open()&&!reconnect())
ERROR(a2nls(lastmessage));
err = catch {
PD("Portalsinflight: %d\n",_portalsinflight);
readyforquery_cb=resync_cb;
- c->start()->add(PGSYNC)->sendcmd(sendout);
+ sendsync();
return;
};
PD("%O\n",err);
if(!reconnect())
ERROR(a2nls(lastmessage));
}
//! This function allows you to connect to a database. Due to
//! restrictions of the Postgres frontend-backend protocol, you always
//! have to be connected to a database, so in fact this function just
pike.git/lib/modules/Sql.pmod/pgsql.pike:1882:
lock=0;
}
int tstart;
if(forcetext) {
if(bindings)
q = .sql_util.emulate_bindings(q, bindings, this);
} else if(forcecache==1
|| forcecache!=0 && (sizeof(q)>=MINPREPARELENGTH || cachealways[q])) {
object plugbuffer=c->start();
if(tp=prepareds[q]) {
- if(tp.preparedname)
- prepstmtused++, preparedname=tp.preparedname;
- else if((tstart=tp.trun)
+ if(tp.preparedname) {
+ #ifdef PG_STATS
+ prepstmtused++;
+ #endif
+ preparedname=tp.preparedname;
+ } else if((tstart=tp.trun)
&& tp.tparse*FACTORPLAN>=tstart
&& (undefinedp(options.cache_autoprepared_statements)
|| options.cache_autoprepared_statements))
preparedname=PREPSTMTPREFIX+(string)pstmtcount++;
} else {
if(totalhits>=cachedepth)
foreach(prepareds;string ind;tp) {
int oldhits=tp.hits;
totalhits-=oldhits-(tp.hits=oldhits>>1);
if(oldhits<=1) {
pike.git/lib/modules/Sql.pmod/pgsql.pike:1930:
object portal;
portal=.pgsql_util.pgsql_result(this,c,q,
portalbuffersize,_alltyped,from,forcetext);
portal._tprepared=tp;
qstate=inquery;
#ifdef PG_STATS
portalsopened++;
#endif
clearmessage=1;
object plugbuffer=c;
- if(forcetext) {
+ if(forcetext) { // FIXME What happens if portals are still open?
portal._unnamedportalkey=unnamedportalmux->lock(1);
portal->_openportal();
- plugbuffer->start()->add_int8('Q')->add_hstring(q,4,4+1)->add_int8(0)
- ->sendcmd(flushsend,portal);
+ _readyforquerycount++;
+ Thread.MutexKey lock=unnamedstatement->lock(1);
+ plugbuffer->start(1)->add_int8('Q')->add_hstring(q,4,4+1)->add_int8(0)
+ ->sendcmd(flushlogsend,portal);
+ lock=0;
PD("Simple query: %O\n",q);
} else {
object parsebuffer;
if(!sizeof(preparedname) || !tp || !tp.preparedname) {
if(!sizeof(preparedname))
preparedname=
(portal._unnamedstatementkey=unnamedstatement->trylock(1))
? "" : PTSTMTPREFIX+(string)ptstmtcount++;
// Even though the protocol doesn't require the Parse command to be
// followed by a flush, it makes a VERY noticeable difference in