pike.git
/
lib
/
modules
/
Sql.pmod
/
pgsql_util.pmod
version
»
Context lines:
10
20
40
80
file
none
3
pike.git/lib/modules/Sql.pmod/pgsql_util.pmod:5:
#pike __REAL_VERSION__ #include "pgsql.h" //! //! The pgsql backend, shared between all connection instances. //! It even runs in non-callback mode. //!
-
protected Thread.Mutex backendmux;
-
final Pike.Backend local_backend;
+
protected Thread.Mutex backendmux
= Thread.Mutex()
;
+
final Pike.Backend local_backend
= Pike.SmallBackend()
;
protected int clientsregistered;
-
+
multiset cachealways=(<"BEGIN","begin","END","end","COMMIT","commit">);
+
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]*$");
+
+
final void closestatement(object plugbuffer,string oldprep) {
+
if(oldprep) {
+
PD("Close statement %s\n",oldprep);
+
plugbuffer->add_int8('C')->add_hstring(({'S',oldprep,0}),4,4);
+
}
+
}
+
protected void run_local_backend() { Thread.MutexKey lock; int looponce; do { looponce=0; if(lock=backendmux->trylock()) { PD("Starting local backend\n"); while(clientsregistered) // Autoterminate when not needed local_backend(4096.0); PD("Terminating local backend\n"); lock=0; looponce=clientsregistered; } } while(looponce); } protected void create() {
-
backendmux = Thread.Mutex();
-
local_backend = Pike.SmallBackend();
+
} final void register_backend() { if(!clientsregistered++) Thread.Thread(run_local_backend); } final void unregister_backend() { --clientsregistered; } final void throwdelayederror(object parent) { if(mixed err=parent._delayederror) { parent._delayederror=UNDEFINED; if(stringp(err)) err=({err,backtrace()[..<2]}); throw(err); } }
-
+
final int oidformat(int oid) {
+
switch(oid) {
+
case BOOLOID:
+
case BYTEAOID:
+
case CHAROID:
+
case INT8OID:
+
case INT2OID:
+
case INT4OID:
+
case TEXTOID:
+
case OIDOID:
+
case XMLOID:
+
case MACADDROID:
+
case BPCHAROID:
+
case VARCHAROID:
+
case CTIDOID:
+
case UUIDOID:
+
return 1; //binary
+
}
+
return 0; // text
+
}
+
protected sctype mergemode(PGassist realbuffer,sctype mode) { if(mode>realbuffer->stashflushmode) realbuffer->stashflushmode=mode; return realbuffer->stashflushmode; } //! Some pgsql utility functions class PGplugbuffer { inherit Stdio.Buffer;
pike.git/lib/modules/Sql.pmod/pgsql_util.pmod:79:
error("pgsql.PGplugbuffer not allowed here\n"); #endif return this; } final void sendcmd(void|sctype mode,void|object portal) { Thread.MutexKey lock=realbuffer->stashupdate->lock(); if(portal) realbuffer->stashqueue->write(portal);
-
if(mode==flushlogsend)
-
mode=flushsend, realbuffer->stashqueue->write(1);
+
realbuffer->stash->add(this); mode=mergemode(realbuffer,mode); if(!--realbuffer->stashcount) realbuffer->stashavail.signal(); lock=0; this->clear(); if(lock=realbuffer->nostash->trylock(1)) { realbuffer->started=lock; lock=0; realbuffer->sendcmd(sendout); } } } class PGassist { inherit Stdio.Buffer:i; inherit Stdio.Buffer:o;
-
protected Thread.Condition
condition
;
-
protected Thread.Mutex
mux
;
+
protected Thread.Condition
fillread
;
+
protected Thread.Mutex
fillreadmux
;
protected Thread.Queue qportals; final Stdio.File socket; protected int towrite; final function(:void) gottimeout; final int timeout; Thread.Mutex nostash; Thread.MutexKey started; Thread.Mutex stashupdate; Thread.Queue stashqueue;
pike.git/lib/modules/Sql.pmod/pgsql_util.pmod:151:
return PGplugbuffer(this); } protected final bool range_error(int howmuch) { if(!howmuch) return false; #ifdef PG_DEBUG if(howmuch<0) error("Out of range %d\n",howmuch); #endif
-
if(
condition
) {
+
if(
fillread
) {
array cid=local_backend->call_out(gottimeout,timeout);
-
Thread.MutexKey lock=
mux
->lock();
-
condition
.wait(lock);
+
Thread.MutexKey lock=
fillreadmux
->lock();
+
fillread
.wait(lock);
lock=0; local_backend->remove_call_out(cid); } else throw(MAGICTERMINATE); return true; } protected int read_cb(mixed id,mixed b) {
-
Thread.MutexKey lock=
mux
->lock();
-
if(
condition
)
-
condition
.signal();
+
Thread.MutexKey lock=
fillreadmux
->lock();
+
if(
fillread
)
+
fillread
.signal();
lock=0; return 0; } protected int write_cb() { towrite-=output_to(socket,towrite);
-
if(!
condition
&& !sizeof(this))
+
if(!
fillread
&& !sizeof(this))
socket->close(); return 0; } inline final int consume(int w) { return i::consume(w); } inline final int unread(int w) { return i::unread(w); } inline final string read(int w) { return i::read(w); } inline final object read_buffer(int w) { return i::read_buffer(w); } inline final int read_sint(int w) { return i::read_sint(w); } inline final int read_int8() { return i::read_int8(); }
pike.git/lib/modules/Sql.pmod/pgsql_util.pmod:230:
} if(towrite=sizeof(this)) { PD(">Sendcmd %O\n",((string)this)[..towrite-1]); towrite-=output_to(socket,towrite); } } while(0); started=0; } final void sendterminate() {
-
destruct(
condition
); // Delayed close() after flushing the output buffer
+
destruct(
fillread
); // Delayed close() after flushing the output buffer
} final int close() { return socket->close(); } final void destroy() { catch(close()); // Exceptions don't work inside destructors }
pike.git/lib/modules/Sql.pmod/pgsql_util.pmod:288:
Thread.Thread(pgsqlsess->_processloop,this); return; }; pgsqlsess->_connectfail(err); } void create(object pgsqlsess,Thread.Queue _qportals,int nossl) { i::create(); o::create(); qportals = _qportals; synctransact = 1;
-
condition
=Thread.Condition();
-
mux
=Thread.Mutex();
+
fillread
=Thread.Condition();
+
fillreadmux
=Thread.Mutex();
gottimeout=sendcmd; // Preset it with a NOP timeout=128; // Just a reasonable amount socket=Stdio.File(); nostash=Thread.Mutex(); stashupdate=Thread.Mutex(); stashqueue=Thread.Queue(); stashavail=Thread.Condition(); stash=Stdio.Buffer(); Thread.Thread(connectloop,pgsqlsess,nossl); }
pike.git/lib/modules/Sql.pmod/pgsql_util.pmod:326:
int _alltext; int _forcetext; string _portalname; int _bytesreceived; int _rowsreceived; int _inflight; int _portalbuffersize; int _synctransact;
+
Thread.Condition _ddescribe;
+
Thread.Mutex _ddescribemux;
Thread.MutexKey _unnamedportalkey,_unnamedstatementkey; protected Thread.Mutex closemux; array _params; string _statuscmdcomplete; string _query; Thread.Queue _datarows;
-
array(mapping(string:mixed)) _datarowdesc
=({})
;
+
array(mapping(string:mixed)) _datarowdesc;
int _oldpbpos; object _plugbuffer; string _preparedname; mapping(string:mixed) _tprepared; protected string _sprintf(int type, void|mapping flags) { string res=UNDEFINED; switch(type) { case 'O': res=sprintf("pgsql_result numrows: %d eof: %d inflight: %d\n" #ifdef PG_DEBUGMORE "query: %O\n" #endif "portalname: %O datarows: %d" " laststatus: %s\n", numrows,eoffound,_inflight, #ifdef PG_DEBUGMORE _query, #endif
-
_portalname,sizeof(_datarowdesc),
+
_portalname,
_datarowdesc&&
sizeof(_datarowdesc),
_statuscmdcomplete||""); break; } return res; } void create(object pgsqlsess,PGassist _c,string query, int portalbuffersize,int alltyped,array params,int forcetext) { _pgsqlsess = pgsqlsess; c = _c; _query = query; _datarows = Thread.Queue(); numrows = UNDEFINED;
-
+
_ddescribe=Thread.Condition();
+
_ddescribemux=Thread.Mutex();
closemux=Thread.Mutex(); _portalbuffersize=portalbuffersize; _alltext = !alltyped; _params = params; _forcetext = forcetext; _state = portalinit; } //! Returns the command-complete status for this query. //!
pike.git/lib/modules/Sql.pmod/pgsql_util.pmod:400:
//! @note //! This function is PostgreSQL-specific, and thus it is not available //! through the generic SQL-interface. int affected_rows() { int rows; if(_statuscmdcomplete) sscanf(_statuscmdcomplete,"%*s %d",rows); return rows; }
+
protected void waitfordescribe() {
+
Thread.MutexKey lock=_ddescribemux->lock();
+
if(!_datarowdesc)
+
_ddescribe->wait(lock);
+
lock=0;
+
}
+
//! @seealso //! @[Sql.sql_result()->num_fields()] int num_fields() {
-
+
if(!_datarowdesc)
+
waitfordescribe();
+
trydelayederror();
return sizeof(_datarowdesc); } //! @seealso //! @[Sql.sql_result()->num_rows()] int num_rows() {
-
int numrows;
-
if
(
_statuscmdcomplete
)
-
sscanf(_statuscmdcomplete,"%*s %d",numrows)
;
-
return
numrows
;
+
trydelayederror
();
+
return
_rowsreceived
;
} protected inline void trydelayederror() { if(_delayederror) throwdelayederror(this); } //! @seealso //! @[Sql.sql_result()->eof()] int eof() { trydelayederror(); return eoffound; } //! @seealso //! @[Sql.sql_result()->fetch_fields()] array(mapping(string:mixed)) fetch_fields() {
-
+
if(!_datarowdesc)
+
waitfordescribe();
trydelayederror(); return _datarowdesc+({}); }
-
void _openportal() {
+
final
void _
setrowdesc(array(mapping(string:mixed)) datarowdesc) {
+
_datarowdesc=datarowdesc;
+
Thread.MutexKey lock=_ddescribemux->lock();
+
_ddescribe->broadcast();
+
lock=0;
+
}
+
+
final void _preparebind() {
+
array dtoid=_tprepared.datatypeoid;
+
array(string|int) paramValues=_params?_params[2]:({});
+
if(sizeof(dtoid)!=sizeof(paramValues))
+
SUSERERROR("Invalid number of bindings, expected %d, got %d\n",
+
sizeof(dtoid),sizeof(paramValues));
+
#ifdef PG_DEBUGMORE
+
PD("ParamValues to bind: %O\n",paramValues);
+
#endif
+
object plugbuffer=Stdio.Buffer();
+
plugbuffer->add(_portalname=
+
(_unnamedportalkey=_pgsqlsess._unnamedportalmux->trylock(1))
+
? "" : PORTALPREFIX+int2hex(_pgsqlsess._pportalcount++) )->add_int8(0)
+
->add(_preparedname)->add_int8(0)->add_int16(sizeof(paramValues));
+
foreach(dtoid;;int textbin)
+
plugbuffer->add_int16(oidformat(textbin));
+
plugbuffer->add_int16(sizeof(paramValues));
+
string cenc=_pgsqlsess._runtimeparameter[CLIENT_ENCODING];
+
foreach(paramValues;int i;mixed value) {
+
if(undefinedp(value))
+
plugbuffer->add_int32(-1); // NULL
+
else if(stringp(value) && !sizeof(value)) {
+
int k=0;
+
switch(dtoid[i]) {
+
default:
+
k=-1; // cast empty strings to NULL for non-string types
+
case BYTEAOID:
+
case TEXTOID:
+
case XMLOID:
+
case BPCHAROID:
+
case VARCHAROID:;
+
}
+
plugbuffer->add_int32(k);
+
} else
+
switch(dtoid[i]) {
+
case TEXTOID:
+
case BPCHAROID:
+
case VARCHAROID: {
+
if(!value) {
+
plugbuffer->add_int32(-1);
+
break;
+
}
+
value=(string)value;
+
switch(cenc) {
+
case UTF8CHARSET:
+
value=string_to_utf8(value);
+
break;
+
default:
+
if(String.width(value)>8) {
+
SUSERERROR("Don't know how to convert %O to %s encoding\n",
+
value,cenc);
+
value="";
+
}
+
}
+
plugbuffer->add_hstring(value,4);
+
break;
+
}
+
default: {
+
if(!value) {
+
plugbuffer->add_int32(-1);
+
break;
+
}
+
value=(string)value;
+
if(String.width(value)>8)
+
if(dtoid[i]==BYTEAOID)
+
value=string_to_utf8(value);
+
else {
+
SUSERERROR("Wide string %O not supported for type OID %d\n",
+
value,dtoid[i]);
+
value="";
+
}
+
plugbuffer->add_hstring(value,4);
+
break;
+
}
+
case BOOLOID:plugbuffer->add_int32(1);
+
do {
+
int tval;
+
if(stringp(value))
+
tval=value[0];
+
else if(!intp(value)) {
+
value=!!value; // cast to boolean
+
break;
+
} else
+
tval=value;
+
switch(tval) {
+
case 'o':case 'O':
+
catch {
+
tval=value[1];
+
value=tval=='n'||tval=='N';
+
};
+
break;
+
default:
+
value=1;
+
break;
+
case 0:case '0':case 'f':case 'F':case 'n':case 'N':
+
value=0;
+
break;
+
}
+
} while(0);
+
plugbuffer->add_int8(value);
+
break;
+
case CHAROID:
+
if(intp(value))
+
plugbuffer->add_hstring(value,4);
+
else {
+
value=(string)value;
+
switch(sizeof(value)) {
+
default:
+
SUSERERROR(
+
"\"char\" types must be 1 byte wide, got %O\n",value);
+
case 0:
+
plugbuffer->add_int32(-1); // NULL
+
break;
+
case 1:
+
plugbuffer->add_hstring(value[0],4);
+
}
+
}
+
break;
+
case INT8OID:
+
plugbuffer->add_int32(8)->add_int((int)value,8);
+
break;
+
case OIDOID:
+
case INT4OID:
+
plugbuffer->add_int32(4)->add_int32((int)value);
+
break;
+
case INT2OID:
+
plugbuffer->add_int32(2)->add_int16((int)value);
+
break;
+
}
+
}
+
_plugbuffer=plugbuffer;
+
if(_tprepared)
+
if(_tprepared.datarowdesc)
+
_dodatarows();
+
else if(dontcacheprefix->match(_query)) // Don't cache FETCH/COPY
+
m_delete(_pgsqlsess->_prepareds,_query),_tprepared=0;
+
}
+
+
final void _processrowdesc(array(mapping(string:mixed)) datarowdesc) {
+
_setrowdesc(datarowdesc);
+
mapping(string:mixed) tp=_tprepared; // FIXME Is caching this worthwhile?
+
if(!tp || !tp.datarowdesc)
+
Thread.Thread(_dodatarows,this);
+
if(tp)
+
tp.datarowdesc=datarowdesc;
+
}
+
+
final void _dodatarows() {
+
object plugbuffer=_plugbuffer;
+
_plugbuffer=0;
+
plugbuffer->add_int16(sizeof(_datarowdesc));
+
foreach(_datarowdesc;;mapping col)
+
plugbuffer->add_int16(oidformat(col.type));
+
PD("Bind portal %O statement %O\n",_portalname,_preparedname);
+
_fetchlimit=_pgsqlsess->_fetchlimit;
+
_
openportal()
;
+
object bindbuffer=c->start(1);
+
_unnamedstatementkey=0;
+
bindbuffer->add_int8('B')->add_hstring(plugbuffer,4,4);
+
if(!_tprepared)
+
closestatement(bindbuffer,_preparedname);
+
_sendexecute(_pgsqlsess->_fetchlimit
+
&& !(cachealways[_query]
+
|| sizeof(_query)>=MINPREPARELENGTH &&
+
execfetchlimit->match(_query))
+
&& FETCHLIMITLONGRUN,bindbuffer);
+
}
+
+
final void _openportal()
{
_pgsqlsess->_portalsinflight++; Thread.MutexKey lock=closemux->lock(); _state=bound; lock=0; _statuscmdcomplete=UNDEFINED; }
-
sctype _closeportal(
void|
PGplugbuffer plugbuffer) {
+
final void _purgeportal() {
+
_unnamedportalkey=0;
+
Thread.MutexKey lock=closemux->lock();
+
_fetchlimit=0; // disables further Executes
+
switch(_state) {
+
case copyinprogress:
+
case bound:
+
--_pgsqlsess->_portalsinflight;
+
}
+
_state=closed;
+
lock=0;
+
}
+
+
final
sctype _closeportal(PGplugbuffer plugbuffer) {
sctype retval=keep; PD("%O Try Closeportal %d\n",_portalname,_state); Thread.MutexKey lock=closemux->lock(); _fetchlimit=0; // disables further Executes switch(_state) {
-
+
case portalinit:
+
_state=closed;
+
break;
case copyinprogress:
-
if(plugbuffer) {
+
PD("CopyDone\n"); plugbuffer->add("c\0\0\0\4");
-
}
+
case bound: _state=closed; lock=0; PD("Close portal %O\n",_portalname); if(sizeof(_portalname)) {
-
if(plugbuffer) {
+
plugbuffer->add_int8('C')->add_hstring(({'P',_portalname,0}),4,4); retval=flushsend;
-
}
+
} else _unnamedportalkey=0;
-
if(!--_pgsqlsess->_portalsinflight
&& plugbuffer
) {
+
if(!--_pgsqlsess->_portalsinflight) {
_pgsqlsess->_readyforquerycount++; _pgsqlsess->_pportalcount=0; retval=syncsend; } } lock=0; return retval; }
-
final void _processdataready(
int gfetchlimit
) {
+
final void _processdataready() {
_rowsreceived++; if(_rowsreceived==1) PD("<%O _fetchlimit %d=min(%d||1,%d), _inflight %d\n",_portalname, _fetchlimit,(_portalbuffersize>>1)*_rowsreceived/_bytesreceived,
-
gfetchlimit
,_inflight);
+
_pgsqlsess._fetchlimit
,_inflight);
if(_fetchlimit) { _fetchlimit=
-
min((_portalbuffersize>>1)*_rowsreceived/_bytesreceived||1,
gfetchlimit
);
+
min((_portalbuffersize>>1)*_rowsreceived/_bytesreceived||1,
+
_pgsqlsess._fetchlimit
);
Thread.MutexKey lock=closemux->lock(); if(_fetchlimit && _inflight<=_fetchlimit-1) _sendexecute(_fetchlimit); else if(!_fetchlimit) PD("<%O _fetchlimit %d, _inflight %d, skip execute\n", _portalname,_fetchlimit,_inflight); lock=0; } }
-
void _releasesession() {
+
final
void _releasesession() {
_inflight=0;
-
_datarows->write(1);
+
_datarows->write(1);
// Signal EOF
object plugbuffer=c->start(1); plugbuffer->sendcmd(_closeportal(plugbuffer)); _pgsqlsess=UNDEFINED; } protected void destroy() { catch { // inside destructors, exceptions don't work _releasesession(); }; }
pike.git/lib/modules/Sql.pmod/pgsql_util.pmod:533:
//! //! When using COPY FROM STDOUT, this method returns one row at a time //! as a single string containing the entire row. //! //! @seealso //! @[eof()], @[send_row()] array(mixed) fetch_row() { int|array datarow; if(arrayp(datarow=_datarows->try_read())) return datarow;
-
if(!
datarow
&&
!
eoffound
+
if(!
eoffound)
{
+
if(
!
datarow
&& (PD("%O Block for datarow\n",_portalname), arrayp(datarow=_datarows->read()))) return datarow;
-
trydelayederror();
+
eoffound=1;
-
+
_datarows->write(1); // Signal EOF for other threads
+
}
+
trydelayederror();
return 0; } //! @returns //! Multiple result rows at a time (at least one). //! //! When using COPY FROM STDOUT, this method returns one row at a time //! as a single string containing the entire row. //! //! @seealso