pike.git / lib / modules / Sql.pmod / pgsql_util.pmod

version» Context lines:

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