|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pike __REAL_VERSION__ |
#require constant(Thread.Thread) |
|
#include "pgsql.h" |
|
#define ERROR(X ...) predef::error(X) |
|
int _fetchlimit=FETCHLIMIT; |
Thread.Mutex _unnamedportalmux; |
protected Thread.Mutex unnamedstatement; |
int _portalsinflight; |
|
protected .pgsql_util.PGassist c; |
protected string cancelsecret; |
protected int backendpid; |
protected int backendstatus; |
mapping(string:mixed) options; |
protected array(string) lastmessage=({}); |
protected int clearmessage; |
protected mapping(string:array(mixed)) notifylist=([]); |
mapping(string:string) _runtimeparameter; |
mapping(string:mapping(string:mixed)) _prepareds=([]); |
protected int pstmtcount; |
protected int ptstmtcount; |
|
|
int _pportalcount; |
protected int totalhits; |
protected int cachedepth=STATEMENTCACHEDEPTH; |
protected int timeout=QUERYTIMEOUT; |
protected int portalbuffersize=PORTALBUFFERSIZE; |
protected int reconnected; |
protected int reconnectdelay; |
#ifdef PG_STATS |
protected int skippeddescribe; |
protected int portalsopened; |
protected int prepstmtused; |
#endif |
int _msgsreceived; |
int _bytesreceived; |
protected int warningsdropcount; |
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 Thread.Mutex waitforauth; |
protected Thread.Condition waitforauthready; |
int _readyforquerycount; |
|
protected string _sprintf(int type, void|mapping flags) { |
string res=UNDEFINED; |
switch(type) { |
case 'O': |
res=sprintf(DRIVERNAME"(%s@%s:%d/%s,%d)", |
user,_host,_port,database,backendpid); |
break; |
} |
return res; |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected void create(void|string host, void|string database, |
void|string user, void|string pass, |
void|mapping(string:mixed) options) { |
this::pass = pass; |
if(pass) { |
String.secure(pass); |
pass = "CENSORED"; |
} |
this::user = user; |
this::database = database; |
this::options = options || ([]); |
|
if(!host) host = PGSQL_DEFAULT_HOST; |
if(has_value(host,":") && sscanf(host,"%s:%d",host,_port)!=2) |
ERROR("Error in parsing the hostname argument\n"); |
this::_host = host; |
|
if(!_port) |
_port = PGSQL_DEFAULT_PORT; |
.pgsql_util.register_backend(); |
waitforauth=Thread.Mutex(); |
reconnect(); |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string error(void|int clear) { |
throwdelayederror(this); |
string s=lastmessage*"\n"; |
if(clear) |
lastmessage=({}); |
warningscollected=0; |
return sizeof(s) && s; |
} |
|
|
|
|
|
|
string host_info() { |
return sprintf("fd:%d TCP/IP %s:%d PID %d", |
c?c->socket->query_fd():-1,_host,_port,backendpid); |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
int is_open() { |
catch { |
return c->socket->is_open(); |
}; |
return 0; |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int ping() { |
return is_open() && !catch(c->start()->sendcmd(flushsend)) |
? !!reconnected : -1; |
} |
|
final protected .pgsql_util.PGassist getsocket(void|int nossl) { |
return .pgsql_util.PGassist(this,qportals,(int)nossl); |
} |
|
|
|
|
|
|
|
|
|
void cancelquery() { |
PD("CancelRequest\n"); |
.pgsql_util.PGassist lcon=getsocket(1); |
lcon->add_int32(16)->add_int32(PG_PROTOCOL(1234,5678)) |
->add_int32(backendpid)->add(cancelsecret)->sendcmd(flushsend); |
lcon->close(); |
#ifdef PG_DEBUGMORE |
PD("Closetrace %O\n",backtrace()); |
#endif |
.pgsql_util.PGassist plugbuffer=c->start(1); |
foreach(qportals->peek_array();;int|.pgsql_util.pgsql_result portal) |
if(objectp(portal)) |
portal->_closeportal(plugbuffer); |
plugbuffer->sendcmd(sendout); |
} |
|
|
|
|
|
|
|
|
|
|
void set_charset(string charset) { |
big_query(sprintf("SET CLIENT_ENCODING TO '%s'",quote(charset))); |
} |
|
|
|
|
|
|
|
string get_charset() { |
waitauthready(); |
return _runtimeparameter[CLIENT_ENCODING]; |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mapping(string:string) getruntimeparameters() { |
waitauthready(); |
return _runtimeparameter+([]); |
} |
|
|
|
|
|
|
|
|
#ifdef PG_STATS |
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
#ifdef PG_STATS |
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mapping(string:mixed) getstatistics() { |
mapping(string:mixed) stats=([ |
"warnings_dropped":warningsdropcount, |
"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; |
} |
|
|
|
|
|
|
|
|
|
|
int setcachedepth(void|int newdepth) { |
int olddepth=cachedepth; |
if(!undefinedp(newdepth) && newdepth>=0) |
cachedepth=newdepth; |
return olddepth; |
} |
|
|
|
|
|
|
|
|
|
|
int settimeout(void|int newtimeout) { |
int oldtimeout=timeout; |
if(!undefinedp(newtimeout) && newtimeout>0) { |
timeout=newtimeout; |
if(c) |
c->timeout=timeout; |
} |
return oldtimeout; |
} |
|
|
|
|
|
|
|
|
|
|
int setportalbuffersize(void|int newportalbuffersize) { |
int oldportalbuffersize=portalbuffersize; |
if(!undefinedp(newportalbuffersize) && newportalbuffersize>0) |
portalbuffersize=newportalbuffersize; |
return oldportalbuffersize; |
} |
|
|
|
|
|
|
|
|
|
|
int setfetchlimit(void|int newfetchlimit) { |
int oldfetchlimit=_fetchlimit; |
if(!undefinedp(newfetchlimit) && newfetchlimit>=0) |
_fetchlimit=newfetchlimit; |
return oldfetchlimit; |
} |
|
final protected string glob2reg(string glob) { |
if(!glob||!sizeof(glob)) |
return "%"; |
return replace(glob,({"*","?","\\","%","_"}),({"%","_","\\\\","\\%","\\_"})); |
} |
|
final protected string a2nls(array(string) msg) { |
return msg*"\n"+"\n"; |
} |
|
final protected string pinpointerror(void|string query,void|string offset) { |
if(!query) |
return ""; |
int k=(int)offset; |
if(k<=0) |
return MARKSTART+query+MARKEND; |
return MARKSTART+(k>1?query[..k-2]:"")+MARKERROR+query[k-1..]+MARKEND; |
} |
|
protected void connect_cb() { |
PD("%O\n",_runtimeparameter); |
} |
|
protected void reconnect_cb() { |
lastmessage+=({sprintf("Reconnected to database %s",host_info())}); |
runcallback(backendpid,"_reconnect",""); |
} |
|
protected array(string) showbindings(.pgsql_util.pgsql_result portal) { |
array(string) msgs=({}); |
array from; |
if(portal && (from = portal._params)) { |
array to,paramValues; |
[from,to,paramValues] = from; |
if(sizeof(paramValues)) { |
string val; |
int i; |
string fmt=sprintf("%%%ds %%3s %%.61s",max(@map(from,sizeof))); |
foreach(paramValues;i;val) |
msgs+=({sprintf(fmt,from[i],to[i],sprintf("%O",val))}); |
} |
} |
return msgs; |
} |
|
protected void preplastmessage(mapping(string:string) msgresponse) { |
lastmessage=({ |
sprintf("%s %s:%s %s\n (%s:%s:%s)", |
msgresponse.S,msgresponse.C,msgresponse.P||"", |
msgresponse.M,msgresponse.F||"",msgresponse.R||"", |
msgresponse.L||"")}); |
} |
|
protected void storetiming(.pgsql_util.pgsql_result portal) { |
mapping(string:mixed) tp=portal._tprepared; |
tp.trun=gethrtime()-tp.trunstart; |
m_delete(tp,"trunstart"); |
portal._tprepared = UNDEFINED; |
} |
|
protected void waitauthready() { |
if(waitforauthready) { |
Thread.MutexKey lock=waitforauth->lock(); |
catch(waitforauthready->wait(lock)); |
lock=0; |
} |
} |
|
final void _processloop(.pgsql_util.PGassist ci) { |
int terminating=0; |
int|.pgsql_util.pgsql_result portal; |
mixed err; |
{ |
Stdio.Buffer 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(sendout); |
} |
cancelsecret=0; |
#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(); |
#ifdef PG_DEBUG |
showportal(msgtype); |
#endif |
} |
int msglen=ci->read_int32(); |
_msgsreceived++; |
_bytesreceived+=1+msglen; |
enum errortype { |
noerror=0, |
protocolerror, |
protocolunsupported |
}; |
errortype errtype=noerror; |
switch(msgtype) { |
array(mapping) getcols() { |
int bintext=ci->read_int8(); |
int cols=ci->read_int16(); |
#ifdef PG_DEBUG |
array a; |
msglen-=4+1+2+2*cols; |
foreach(a=allocate(cols,([]));;mapping m) |
m.type=ci->read_int16(); |
#else |
ci->consume(cols<<1); |
#endif // Discard column info, and make it line oriented |
return ({(["type":bintext?BYTEAOID:TEXTOID,"name":"line"])}); |
}; |
array(string) reads() { |
#ifdef PG_DEBUG |
if(msglen<1) |
errtype=protocolerror; |
#endif |
array ret=({}),aw=({0}); |
do { |
string w=ci->read_cstring(); |
msglen-=sizeof(w)+1; aw[0]=w; ret+=aw; |
} while(msglen); |
return ret; |
}; |
mapping(string:string) getresponse() { |
mapping(string:string) msgresponse=([]); |
msglen-=4; |
foreach(reads();;string f) |
if(sizeof(f)) |
msgresponse[f[..0]]=f[1..]; |
PD("%O\n",msgresponse); |
return msgresponse; |
}; |
case 'R': { |
PD("<Authentication "); |
string sendpass; |
int authtype; |
msglen-=4+4; |
switch(authtype=ci->read_int32()) { |
case 0: |
PD("Ok\n"); |
.pgsql_util.local_backend->remove_call_out(reconnect); |
ci->gottimeout=cancelquery; |
ci->timeout=timeout; |
reconnectdelay=0; |
cancelsecret=""; |
break; |
case 2: |
PD("KerberosV5\n"); |
errtype=protocolunsupported; |
break; |
case 3: |
PD("ClearTextPassword\n"); |
sendpass=pass; |
break; |
case 4: |
PD("CryptPassword\n"); |
errtype=protocolunsupported; |
break; |
case 5: |
PD("MD5Password\n"); |
#ifdef PG_DEBUG |
if(msglen<4) |
errtype=protocolerror; |
#endif |
#define md5hex(x) String.string2hex(Crypto.MD5.hash(x)) |
sendpass=md5hex(pass+user); |
sendpass="md5"+md5hex(sendpass+ci->read(msglen)); |
#ifdef PG_DEBUG |
msglen=0; |
#endif |
break; |
case 6: |
PD("SCMCredential\n"); |
errtype=protocolunsupported; |
break; |
case 7: |
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); |
#ifdef PG_DEBUG |
if(msglen<1) |
errtype=protocolerror; |
msglen=0; |
#endif |
break; |
default: |
PD("Unknown Authentication Method %c\n",authtype); |
errtype=protocolunsupported; |
break; |
} |
switch(errtype) { |
case noerror: |
if(cancelsecret!="") |
ci->start()->add_int8('p')->add_hstring(sendpass,4,5) |
->add_int8(0)->sendcmd(sendout); |
break; |
default: |
case protocolunsupported: |
ERROR("Unsupported authenticationmethod %c\n",authtype); |
break; |
} |
break; |
} |
case 'K': |
msglen-=4+4;backendpid=ci->read_int32(); |
cancelsecret=ci->read(msglen); |
#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]; |
#ifdef PG_DEBUG |
PD("%O=%O\n",ts[0],ts[1]); |
} else |
errtype=protocolerror; |
#endif |
break; |
} |
case '3': |
#ifdef PG_DEBUG |
PD("<CloseComplete\n"); |
msglen-=4; |
#endif |
break; |
case 'Z': |
backendstatus=ci->read_int8(); |
#ifdef PG_DEBUG |
msglen-=4+1; |
PD("<ReadyForQuery %c\n",backendstatus); |
#endif |
for(;objectp(portal);portal=qportals->read()) { |
#ifdef PG_DEBUG |
showportal(msgtype); |
#endif |
portal->_purgeportal(); |
} |
foreach(qportals->peek_array();;.pgsql_util.pgsql_result qp) { |
if(objectp(qp) && qp._synctransact && qp._synctransact<=portal) { |
PD("Checking portal %O %d<=%d\n", |
qp._portalname,qp._synctransact,portal); |
qp->_purgeportal(); |
} |
} |
portal=0; |
_readyforquerycount--; |
if(readyforquery_cb) |
readyforquery_cb(),readyforquery_cb=0; |
if(waitforauthready) |
destruct(waitforauthready); |
break; |
case '1': |
#ifdef PG_DEBUG |
PD("<ParseComplete\n"); |
msglen-=4; |
#endif |
break; |
case 't': { |
array a; |
int cols=ci->read_int16(); |
#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; |
Thread.Thread(portal->_preparebind,a); |
break; |
} |
case 'T': { |
array a; |
#ifdef PG_DEBUG |
int cols=ci->read_int16(); |
PD("<RowDescription %d columns %O\n",cols,portal._query); |
msglen-=4+2; |
foreach(a=allocate(cols);int i;) |
#else |
foreach(a=allocate(ci->read_int16());int i;) |
#endif |
{ |
string s=ci->read_cstring(); |
mapping(string:mixed) res=(["name":s]); |
#ifdef PG_DEBUG |
msglen-=sizeof(s)+1+4+2+4+2+4+2; |
res.tableoid=ci->read_int32()||UNDEFINED; |
res.tablecolattr=ci->read_int16()||UNDEFINED; |
#else |
ci->consume(6); |
#endif |
res.type=ci->read_int32(); |
#ifdef PG_DEBUG |
{ |
int len=ci->read_sint(2); |
res.length=len>=0?len:"variable"; |
} |
res.atttypmod=ci->read_int32(); |
|
|
|
|
res.formatcode=ci->read_int16(); |
#else |
ci->consume(8); |
#endif |
a[i]=res; |
} |
#ifdef PG_DEBUGMORE |
PD("%O\n",a); |
#endif |
if(portal._forcetext) |
portal->_setrowdesc(a); |
else { |
portal->_processrowdesc(a); |
portal=0; |
} |
break; |
} |
case 'n': { |
#ifdef PG_DEBUG |
msglen-=4; |
PD("<NoData %O\n",portal._query); |
#endif |
portal._fetchlimit=0; |
portal->_processrowdesc(({})); |
portal=0; |
break; |
} |
case 'H': |
portal->_processrowdesc(getcols()); |
PD("<CopyOutResponse %d %O\n", |
sizeof(portal._datarowdesc),portal._query); |
break; |
case '2': { |
mapping tp; |
#ifdef PG_DEBUG |
msglen-=4; |
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)) |
tp.preparedname=portal._preparedname; |
tstart=tend-tstart; |
if(!tp.tparse || tp.tparse>tstart) |
tp.tparse=tstart; |
} |
tp.trunstart=tend; |
} |
} |
break; |
} |
case 'D': { |
msglen-=4; |
string serror; |
if(portal._tprepared) |
storetiming(portal); |
portal._bytesreceived+=msglen; |
array 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) |
datarowdebug=sprintf( |
"<%O DataRow %d cols %d bytes",portal._portalname,cols,msglen); |
#endif |
int atext = portal._alltext; |
int forcetext = portal._forcetext; |
string cenc=_runtimeparameter[CLIENT_ENCODING]; |
array a=allocate(cols,!atext&&Val.null); |
msglen-=2+4*cols; |
foreach(datarowdesc;int i;mapping m) { |
int collen=ci->read_sint(4); |
if(collen>0) { |
msglen-=collen; |
mixed value; |
switch(int typ=m.type) { |
case FLOAT4OID: |
#if SIZEOF_FLOAT>=8 |
case FLOAT8OID: |
#endif |
if(!atext) { |
value=(float)ci->read(collen); |
break; |
} |
default:value=ci->read(collen); |
break; |
case CHAROID: |
value=atext?ci->read(1):ci->read_int8(); |
break; |
case BOOLOID:value=ci->read_int8(); |
switch(value) { |
case 'f':value=0; |
break; |
case 't':value=1; |
} |
if(atext) |
value=value?"t":"f"; |
break; |
case TEXTOID: |
case BPCHAROID: |
case VARCHAROID: |
value=ci->read(collen); |
if(cenc==UTF8CHARSET && catch(value=utf8_to_string(value)) |
&& !serror) |
serror=SERROR("%O contains non-%s characters\n", |
value,UTF8CHARSET); |
break; |
case INT8OID:case INT2OID: |
case OIDOID:case INT4OID: |
if(forcetext) { |
value=ci->read(collen); |
if(!atext) |
value=(int)value; |
} else { |
switch(typ) { |
case INT8OID:value=ci->read_sint(8); |
break; |
case INT2OID:value=ci->read_sint(2); |
break; |
case OIDOID: |
case INT4OID:value=ci->read_sint(4); |
} |
if(atext) |
value=(string)value; |
} |
} |
a[i]=value; |
} else if(!collen) |
a[i]=""; |
} |
portal._inflight--; |
portal._datarows->write(a); |
if(serror) |
ERROR(serror); |
portal->_processdataready(); |
break; |
} |
case 's': |
#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 |
string s=ci->read(msglen-1); |
if(portal._tprepared) |
storetiming(portal); |
PD("<%O CommandComplete %O\n",portal._portalname,s); |
if(!portal._statuscmdcomplete) |
portal._statuscmdcomplete=s; |
#ifdef PG_DEBUG |
if(ci->read_int8()) |
errtype=protocolerror; |
msglen=0; |
#else |
ci->consume(1); |
#endif |
portal->_releasesession(); |
portal=0; |
break; |
} |
case 'I': |
#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; |
#ifdef PG_DEBUG |
if(msglen<0) |
errtype=protocolerror; |
#endif |
portal._bytesreceived+=msglen; |
portal._datarows->write(({ci->read(msglen)})); |
#ifdef PG_DEBUG |
msglen=0; |
#endif |
portal->_processdataready(); |
break; |
case 'G': |
portal->_setrowdesc(getcols()); |
PD("<%O CopyInResponse %d columns\n", |
portal._portalname,sizeof(portal._datarowdesc)); |
portal._state=copyinprogress; |
{ |
Thread.MutexKey resultlock=portal._resultmux->lock(); |
portal._newresult.signal(); |
resultlock=0; |
} |
break; |
case 'c': |
#ifdef PG_DEBUG |
PD("<%O CopyDone\n",portal._portalname); |
msglen-=4; |
#endif |
portal=0; |
break; |
case 'E': { |
if(!_readyforquerycount) |
sendsync(); |
PD("<%O ErrorResponse %O\n", |
objectp(portal)&&(portal._portalname||portal._preparedname), |
objectp(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))); |
case "57P01":case "57P02":case "57P03": |
preplastmessage(msgresponse); |
PD(a2nls(lastmessage));throw(0); |
case "08P01":case "42P05": |
errtype=protocolerror; |
case "XX000":case "42883":case "42P01": |
invalidatecache=1; |
default: |
preplastmessage(msgresponse); |
if(msgresponse.D) |
lastmessage+=({msgresponse.D}); |
if(msgresponse.H) |
lastmessage+=({msgresponse.H}); |
lastmessage+=({ |
pinpointerror(objectp(portal)&&portal._query,msgresponse.P)+ |
pinpointerror(msgresponse.q,msgresponse.p)}); |
if(msgresponse.W) |
lastmessage+=({msgresponse.W}); |
if(objectp(portal)) |
lastmessage+=showbindings(portal); |
switch(msgresponse.S) { |
case "PANIC":werror(a2nls(lastmessage)); |
} |
USERERROR(a2nls(lastmessage)); |
} |
if(objectp(portal)) |
portal->_releasesession(); |
break; |
} |
case 'N': { |
PD("<NoticeResponse\n"); |
mapping(string:string) msgresponse; |
msgresponse=getresponse(); |
if(clearmessage) { |
warningsdropcount+=warningscollected; |
clearmessage=warningscollected=0; |
lastmessage=({}); |
} |
warningscollected++; |
lastmessage=({sprintf("%s %s: %s", |
msgresponse.S,msgresponse.C,msgresponse.M)}); |
break; |
} |
case 'A': { |
PD("<NotificationResponse\n"); |
msglen-=4+4; |
int pid=ci->read_int32(); |
string condition,extrainfo=UNDEFINED; |
{ |
array(string) ts=reads(); |
switch(sizeof(ts)) { |
#if PG_DEBUG |
case 0: |
errtype=protocolerror; |
break; |
default: |
errtype=protocolerror; |
#endif |
case 2: |
extrainfo=ts[1]; |
case 1: |
condition=ts[0]; |
} |
} |
PD("%d %s\n%s\n",pid,condition,extrainfo); |
runcallback(pid,condition,extrainfo); |
break; |
} |
default: |
if(msgtype!=-1) { |
string s; |
PD("Unknown message received %c\n",msgtype); |
s=ci->read(msglen-=4);PD("%O\n",s); |
#ifdef PG_DEBUG |
msglen=0; |
#endif |
errtype=protocolunsupported; |
} else { |
lastmessage+=({ |
sprintf("Connection lost to database %s@%s:%d/%s %d\n", |
user,_host,_port,database,backendpid)}); |
if(!waitforauthready) |
throw(0); |
USERERROR(a2nls(lastmessage)); |
} |
break; |
} |
#ifdef PG_DEBUG |
if(msglen) |
errtype=protocolerror; |
#endif |
{ |
string msg; |
switch(errtype) { |
case protocolunsupported: |
msg=sprintf("Unsupported servermessage received %c\n",msgtype); |
break; |
case protocolerror: |
msg=sprintf("Protocol error with database %s",host_info()); |
break; |
case noerror: |
continue; |
} |
ERROR(a2nls(lastmessage+=({msg}))); |
} |
}; |
if(err==MAGICTERMINATE) { |
ci->start()->add("X\0\0\0\4")->sendcmd(sendout); |
terminating=1; |
if(!sizeof(ci)) |
break; |
} |
if(stringp(err)) { |
.pgsql_util.pgsql_result or; |
if(!objectp(or=portal)) |
or=this; |
if(!or._delayederror) |
or._delayederror=err; |
if(objectp(portal)) |
portal->_releasesession(); |
portal=0; |
continue; |
} |
break; |
} |
_delayederror=err; |
if(!ci->close() && !terminating && options.reconnect) |
_connectfail(); |
if(err) |
throw(err); |
} |
|
|
|
|
|
|
|
void close() { |
cancelquery(); |
c?->sendterminate(); |
c=0; |
} |
|
void destroy() { |
close(); |
.pgsql_util.unregister_backend(); |
} |
|
void _connectfail(void|mixed err) { |
PD("Connect failed %O reconnectdelay %d\n",err,reconnectdelay); |
if(!err || reconnectdelay) { |
int tdelay; |
switch(tdelay=reconnectdelay) { |
case 0: |
reconnectdelay=RECONNECTDELAY; |
break; |
default: |
if(err) |
_delayederror=err; |
if(options.reconnect!=-1) |
return; |
reconnectdelay=RECONNECTBACKOFF; |
break; |
} |
Thread.MutexKey lock=waitforauth->lock(); |
if(!waitforauthready) |
waitforauthready=Thread.Condition(); |
lock=0; |
PD("Schedule reconnect in %ds\n",tdelay); |
_delayederror=0; |
.pgsql_util.local_backend->call_out(reconnect,tdelay,1); |
} else if(err) |
_delayederror=err; |
} |
|
protected int reconnect(void|int force) { |
PD("(Re)connect\n"); |
if(!force) { |
Thread.MutexKey lock=waitforauth->lock(); |
if(waitforauthready) { |
lock=0; |
return 0; |
} |
waitforauthready=Thread.Condition(); |
lock=0; |
} |
if(c) { |
PD("Close old connection\n"); |
reconnected++; |
#ifdef PG_STATS |
prepstmtused=0; |
#endif |
if(!force) |
c->sendterminate(); |
else |
c->close(); |
c=0; |
PD("Flushing old cache\n"); |
foreach(_prepareds;;mapping tp) |
m_delete(tp,"preparedname"); |
if(!options.reconnect) |
return 0; |
} |
PD("Actually start to connect\n"); |
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=([]); |
_unnamedportalmux=Thread.Mutex(); |
unnamedstatement=Thread.Mutex(); |
readyforquery_cb=force?reconnect_cb:connect_cb; |
_portalsinflight=0; |
return 1; |
} |
|
|
|
|
|
|
|
void reload() { |
resync(); |
} |
|
protected void resync_cb() { |
switch(backendstatus) { |
case 'T':case 'E': |
foreach(_prepareds;;mapping tp) { |
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); |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void resync() { |
mixed err; |
if(!is_open()&&!reconnect()) |
ERROR(a2nls(lastmessage)); |
err = catch { |
PD("Portalsinflight: %d\n",_portalsinflight); |
readyforquery_cb=resync_cb; |
sendsync(); |
return; |
}; |
PD("%O\n",err); |
if(!reconnect()) |
ERROR(a2nls(lastmessage)); |
} |
|
|
|
|
|
|
|
|
|
|
|
|
void select_db(string dbname) { |
database=dbname; |
reconnect(); |
reconnected=0; |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void set_notify_callback(string condition, |
void|function(int,string,string,mixed ...:void) notify_cb,void|int selfnotify, |
mixed ... args) { |
if(!notify_cb) |
m_delete(notifylist,condition); |
else { |
array old=notifylist[condition]; |
if(!old) |
old=({notify_cb}); |
if(selfnotify||args) |
old+=({selfnotify}); |
if(args) |
old+=args; |
notifylist[condition]=old; |
} |
} |
|
final protected void runcallback(int pid,string condition,string extrainfo) { |
array cb; |
if((cb=notifylist[condition]||notifylist[""]) |
&& (pid!=backendpid || sizeof(cb)>1 && cb[1])) |
cb[0](pid,condition,extrainfo,@cb[2..]); |
} |
|
|
|
|
|
|
|
|
|
|
|
string quote(string s) { |
waitauthready(); |
string r=_runtimeparameter.standard_conforming_strings; |
if(r && r=="on") |
return replace(s, "'", "''"); |
return replace(s, ({ "'", "\\" }), ({ "''", "\\\\" }) ); |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string quotebinary(string s) { |
return replace(s, ({ "'", "\\", "\0" }), ({ "''", "\\\\", "\\000" }) ); |
} |
|
|
|
|
|
|
|
|
|
void create_db(string db) { |
big_query(sprintf("CREATE DATABASE %s",db)); |
} |
|
|
|
|
|
|
|
|
|
|
|
void drop_db(string db) { |
big_query(sprintf("DROP DATABASE %s",db)); |
} |
|
|
|
|
|
|
|
|
|
string server_info () { |
waitauthready(); |
return DRIVERNAME"/"+(_runtimeparameter.server_version||"unknown"); |
} |
|
|
|
|
|
|
array(string) list_dbs (void|string glob) { |
array row,ret=({}); |
.pgsql_util.pgsql_result res=big_query("SELECT d.datname " |
"FROM pg_database d " |
"WHERE d.datname ILIKE :glob " |
"ORDER BY d.datname", |
([":glob":glob2reg(glob)])); |
while(row=res->fetch_row()) |
ret+=({row[0]}); |
return ret; |
} |
|
|
|
|
|
|
|
array(string) list_tables (void|string glob) { |
array row,ret=({}); |
.pgsql_util.pgsql_result res=big_query( |
"SELECT CASE WHEN 'public'=n.nspname THEN '' ELSE n.nspname||'.' END " |
" ||c.relname AS name " |
"FROM pg_catalog.pg_class c " |
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace " |
"WHERE c.relkind IN ('r','v') AND n.nspname<>'pg_catalog' " |
" AND n.nspname !~ '^pg_toast' AND pg_catalog.pg_table_is_visible(c.oid) " |
" AND c.relname ILIKE :glob " |
" ORDER BY 1", |
([":glob":glob2reg(glob)])); |
while(row=res->fetch_row()) |
ret+=({row[0]}); |
return ret; |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
array(mapping(string:mixed)) list_fields(void|string table, void|string glob) { |
array row, ret=({}); |
string schema=UNDEFINED; |
|
sscanf(table||"*", "%s.%s", schema, table); |
|
.pgsql_util.pgsql_result res = big_typed_query( |
"SELECT a.attname, a.atttypid, t.typname, a.attlen, " |
" c.relhasindex, c.relhaspkey, CAST(c.reltuples AS BIGINT) AS reltuples, " |
" (c.relpages " |
" +COALESCE( " |
" (SELECT SUM(tst.relpages) " |
" FROM pg_catalog.pg_class tst " |
" WHERE tst.relfilenode=c.reltoastrelid) " |
" ,0) " |
" )*8192::BIGINT AS datasize, " |
" (COALESCE( " |
" (SELECT SUM(pin.relpages) " |
" FROM pg_catalog.pg_index pi " |
" JOIN pg_catalog.pg_class pin ON pin.relfilenode=pi.indexrelid " |
" WHERE pi.indrelid IN (c.relfilenode,c.reltoastrelid)) " |
" ,0) " |
" )*8192::BIGINT AS indexsize, " |
" c.relisshared, t.typdefault, " |
" n.nspname, c.relname, " |
" CASE c.relkind " |
" WHEN 'r' THEN 'table' " |
" WHEN 'v' THEN 'view' " |
" WHEN 'i' THEN 'index' " |
" WHEN 'S' THEN 'sequence' " |
" WHEN 's' THEN 'special' " |
" WHEN 't' THEN 'toastable' " |
" WHEN 'c' THEN 'composite' " |
" ELSE c.relkind::TEXT END AS relkind, " |
" r.rolname " |
"FROM pg_catalog.pg_class c " |
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace " |
" JOIN pg_catalog.pg_roles r ON r.oid=c.relowner " |
" JOIN pg_catalog.pg_attribute a ON c.oid=a.attrelid " |
" JOIN pg_catalog.pg_type t ON a.atttypid=t.oid " |
"WHERE c.relname ILIKE :table AND " |
" (n.nspname ILIKE :schema OR " |
" :schema IS NULL " |
" AND n.nspname<>'pg_catalog' AND n.nspname !~ '^pg_toast') " |
" AND a.attname ILIKE :glob " |
" AND (a.attnum>0 OR '*'=:realglob) " |
"ORDER BY n.nspname,c.relname,a.attnum,a.attname", |
([":schema":glob2reg(schema),":table":glob2reg(table), |
":glob":glob2reg(glob),":realglob":glob])); |
|
array colnames=res->fetch_fields(); |
{ |
mapping(string:string) renames=([ |
"attname":"name", |
"nspname":"schema", |
"relname":"table", |
"rolname":"owner", |
"typname":"type", |
"attlen":"length", |
"typdefault":"default", |
"relisshared":"is_shared", |
"atttypid":"typeoid", |
"relkind":"kind", |
"relhasindex":"has_index", |
"relhaspkey":"has_primarykey", |
"reltuples":"rowcount", |
]); |
foreach(colnames;int i;mapping m) { |
string nf,field=m.name; |
if(nf=renames[field]) |
field=nf; |
colnames[i]=field; |
} |
} |
|
#define delifzero(m,field) if(!(m)[field]) m_delete(m,field) |
|
while(row=res->fetch_row()) { |
mapping m=mkmapping(colnames,row); |
delifzero(m,"is_shared"); |
delifzero(m,"has_index"); |
delifzero(m,"has_primarykey"); |
delifzero(m,"default"); |
ret+=({m}); |
} |
return ret; |
} |
|
final protected string trbackendst(int c) { |
switch(c) { |
case 'I': return "idle"; |
case 'T': return "intransaction"; |
case 'E': return "infailedtransaction"; |
} |
return ""; |
} |
|
|
|
|
|
|
|
|
|
|
|
|
final string status_commit() { |
return trbackendst(backendstatus); |
} |
|
final inline void closestatement( |
.pgsql_util.PGassist|.pgsql_util.PGplugbuffer plugbuffer,string oldprep) { |
.pgsql_util.closestatement(plugbuffer,oldprep); |
} |
|
protected inline string int2hex(int i) { |
return String.int2hex(i); |
} |
|
final inline void throwdelayederror(object parent) { |
.pgsql_util.throwdelayederror(parent); |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.pgsql_util.pgsql_result big_query(string q, |
void|mapping(string|int:mixed) bindings, |
void|int _alltyped) { |
throwdelayederror(this); |
string preparedname=""; |
int forcecache=-1, forcetext=options.text_query; |
int syncparse=zero_type(options.sync_parse)?-1:options.sync_parse; |
if(waitforauthready) |
waitauthready(); |
string cenc=_runtimeparameter[CLIENT_ENCODING]; |
switch(cenc) { |
case UTF8CHARSET: |
q=string_to_utf8(q); |
break; |
default: |
if(String.width(q)>8) |
ERROR("Don't know how to convert %O to %s encoding\n",q,cenc); |
} |
array(string|int) paramValues; |
array from; |
if(bindings) { |
if(forcetext) |
q = .sql_util.emulate_bindings(q, bindings, this), paramValues=({}); |
else { |
int pi=0,rep=0; |
paramValues=allocate(sizeof(bindings)); |
from=allocate(sizeof(bindings)); |
array(string) to=allocate(sizeof(bindings)); |
foreach(bindings; mixed name; mixed value) { |
if(stringp(name)) { |
if(name[0]!=':') |
name=":"+name; |
if(name[1]=='_') { |
switch(name) { |
case ":_cache": |
forcecache=(int)value; |
break; |
case ":_text": |
forcetext=(int)value; |
break; |
case ":_syncparse": |
syncparse=(int)value; |
break; |
} |
continue; |
} |
if(!has_value(q,name)) |
continue; |
} |
from[rep]=name; |
string rval; |
if(multisetp(value)) |
rval=indices(value)*","; |
else { |
paramValues[pi++]=value; |
rval=sprintf("$%d",pi); |
} |
to[rep++]=rval; |
} |
if(rep--) |
q=replace(q,from=from[..rep],to=to[..rep]); |
paramValues= pi ? paramValues[..pi-1] : ({}); |
from=({from,to,paramValues}); |
} |
} else |
paramValues=({}); |
if(String.width(q)>8) |
ERROR("Wide string literals in %O not supported\n",q); |
if(has_value(q,"\0")) |
ERROR("Querystring %O contains invalid literal nul-characters\n",q); |
mapping(string:mixed) tp; |
int tstart; |
if(!forcetext && forcecache==1 |
|| forcecache!=0 |
&& (sizeof(q)>=MINPREPARELENGTH || .pgsql_util.cachealways[q])) { |
object plugbuffer=c->start(); |
if(tp=_prepareds[q]) { |
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+int2hex(pstmtcount++); |
} else { |
if(totalhits>=cachedepth) |
foreach(_prepareds;string ind;tp) { |
int oldhits=tp.hits; |
totalhits-=oldhits-(tp.hits=oldhits>>1); |
if(oldhits<=1) { |
closestatement(plugbuffer,tp.preparedname); |
m_delete(_prepareds,ind); |
} |
} |
if(forcecache!=1 && .pgsql_util.createprefix->match(q)) { |
invalidatecache=1; |
tp=UNDEFINED; |
} else |
_prepareds[q]=tp=([]); |
} |
if(invalidatecache) { |
invalidatecache=0; |
foreach(_prepareds;;mapping np) { |
closestatement(plugbuffer,np.preparedname); |
m_delete(np,"preparedname"); |
} |
} |
if(sizeof(plugbuffer)) { |
PD("%O\n",(string)plugbuffer); |
plugbuffer->sendcmd(flushsend); |
} else |
plugbuffer->sendcmd(); |
tstart=gethrtime(); |
} else |
tp=UNDEFINED; |
.pgsql_util.pgsql_result portal; |
portal=.pgsql_util.pgsql_result(this,c,q, |
portalbuffersize,_alltyped,from,forcetext); |
portal._tprepared=tp; |
#ifdef PG_STATS |
portalsopened++; |
#endif |
clearmessage=1; |
if(forcetext) { |
portal._unnamedportalkey=_unnamedportalmux->lock(1); |
portal._portalname=""; |
portal->_openportal(); |
_readyforquerycount++; |
Thread.MutexKey lock=unnamedstatement->lock(1); |
c->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 plugbuffer; |
if(!sizeof(preparedname) || !tp || !tp.preparedname) { |
if(!sizeof(preparedname)) |
preparedname= |
(portal._unnamedstatementkey= |
(syncparse?unnamedstatement->lock:unnamedstatement->trylock)(1)) |
? "" : PTSTMTPREFIX+int2hex(ptstmtcount++); |
|
|
|
|
PD("Parse statement %O=%O\n",preparedname,q); |
plugbuffer=c->start()->add_int8('P') |
->add_hstring(({preparedname,0,q,"\0\0\0"}),4,4)->add(PGFLUSH); |
} |
if(!tp || !tp.datatypeoid) { |
PD("Describe statement %O\n",preparedname); |
(plugbuffer||c->start())->add_int8('D') |
->add_hstring(({'S',preparedname,0}),4,4)->sendcmd(flushsend,portal); |
} else { |
if(plugbuffer) |
plugbuffer->sendcmd(); |
#ifdef PG_STATS |
skippeddescribe++; |
#endif |
portal->_setrowdesc(tp.datarowdesc); |
} |
portal._preparedname=preparedname; |
if((portal._tprepared=tp) && tp.datatypeoid) { |
mixed e=catch(portal->_preparebind(tp.datatypeoid)); |
if(e && !portal._delayederror) |
throw(e); |
} |
} |
throwdelayederror(portal); |
return portal; |
} |
|
|
|
|
|
|
.pgsql_util.pgsql_result streaming_query(string q, |
void|mapping(string|int:mixed) bindings) { |
return big_query(q,bindings); |
} |
|
|
|
|
|
|
.pgsql_util.pgsql_result big_typed_query(string q, |
void|mapping(string|int:mixed) bindings) { |
return big_query(q,bindings,1); |
} |
|
|