ecbab12008-07-27Stephen R. van den Berg /* * Some pgsql utility functions. * They are kept here to avoid circular references. */ #pike __REAL_VERSION__ #include "pgsql.h"
cff3a32014-09-12Stephen R. van den Berg //! //! The pgsql backend, shared between all connection instances. //! It even runs in non-callback mode. //!
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg protected Thread.Mutex backendmux; final Pike.Backend local_backend; protected int clientsregistered; 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); }
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg protected void create() { backendmux = Thread.Mutex(); local_backend = Pike.SmallBackend(); }
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg final void register_backend() { if(!clientsregistered++) Thread.Thread(run_local_backend); }
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg final void unregister_backend() { --clientsregistered; }
4741cd2008-07-31Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg final void throwdelayederror(object parent) { if(mixed err=parent._delayederror) { parent._delayederror=UNDEFINED; if(stringp(err)) err=({err,backtrace()[..<2]}); throw(err);
ecbab12008-07-27Stephen R. van den Berg  }
cff3a32014-09-12Stephen R. van den Berg }
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg protected sctype mergemode(PGassist realbuffer,sctype mode) {
4ef8a32014-10-29Stephen R. van den Berg  if(mode>realbuffer->stashflushmode) realbuffer->stashflushmode=mode; return realbuffer->stashflushmode;
cff3a32014-09-12Stephen R. van den Berg }
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg //! Some pgsql utility functions
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg class PGplugbuffer { inherit Stdio.Buffer;
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg  protected PGassist realbuffer;
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg  final void create(PGassist _realbuffer) { realbuffer=_realbuffer;
ecbab12008-07-27Stephen R. van den Berg  }
d432822008-07-31Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg  final PGplugbuffer start(void|int waitforreal) { realbuffer->stashcount++; #ifdef PG_DEBUG if(waitforreal) error("pgsql.PGplugbuffer not allowed here\n");
d432822008-07-31Stephen R. van den Berg #endif
cff3a32014-09-12Stephen R. van den Berg  return this; } final void sendcmd(void|sctype mode,void|object portal) { Thread.MutexKey lock=realbuffer->stashupdate->lock(); if(portal) realbuffer->stashqueue->write(portal);
4ef8a32014-10-29Stephen R. van den Berg  if(mode==flushlogsend) mode=flushsend, realbuffer->stashqueue->write(1);
cff3a32014-09-12Stephen R. van den Berg  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); }
d432822008-07-31Stephen R. van den Berg  }
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg }
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg class PGassist { inherit Stdio.Buffer:i; inherit Stdio.Buffer:o; protected Thread.Condition condition; protected Thread.Mutex mux; 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; Thread.Condition stashavail; Stdio.Buffer stash; int stashflushmode; int stashcount;
4ef8a32014-10-29Stephen R. van den Berg  int synctransact;
cff3a32014-09-12Stephen R. van den Berg #ifdef PG_DEBUG int queueoutidx; int queueinidx;
ecbab12008-07-27Stephen R. van den Berg #endif
4ef8a32014-10-29Stephen R. van den Berg  inline void queueup(pgsql_result portal) { qportals->write(portal); portal->_synctransact=synctransact; PD(">%O %d %d Queue portal %d bytes\n",portal._portalname,++queueoutidx, synctransact,sizeof(this)); }
cff3a32014-09-12Stephen R. van den Berg  final PGassist|PGplugbuffer start(void|int waitforreal) { Thread.MutexKey lock; if(lock=(waitforreal?nostash->lock:nostash->trylock)(1)) { started=lock; lock=stashupdate->lock();
4ef8a32014-10-29Stephen R. van den Berg  if(stashcount)
cff3a32014-09-12Stephen R. van den Berg  stashavail.wait(lock);
4ef8a32014-10-29Stephen R. van den Berg  add(stash); stash->clear(); foreach(stashqueue->try_read_array();;pgsql_result portal) queueup(portal);
cff3a32014-09-12Stephen R. van den Berg  lock=0; return this; } stashcount++; return PGplugbuffer(this);
ecbab12008-07-27Stephen R. van den Berg  }
cff3a32014-09-12Stephen R. van den Berg  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) { array cid=local_backend->call_out(gottimeout,timeout); Thread.MutexKey lock=mux->lock(); condition.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(); lock=0; return 0; } protected int write_cb() { towrite-=output_to(socket,towrite); if(!condition && !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(); } inline final int read_int16() { return i::read_int16(); } inline final int read_int32() { return i::read_int32(); } inline final string read_cstring() { return i::read_cstring(); } final void sendcmd(void|sctype mode,void|pgsql_result portal) {
4ef8a32014-10-29Stephen R. van den Berg  if(portal) queueup(portal); if(mode==flushlogsend) { mode=flushsend; qportals->write(synctransact++); PD(">%O %d Queue simplequery %d bytes\n",portal._portalname, ++queueoutidx,sizeof(this));
cff3a32014-09-12Stephen R. van den Berg  } if(started) { Thread.MutexKey lock=stashupdate->lock(); if(sizeof(stash)) { add(stash); stash->clear();
4ef8a32014-10-29Stephen R. van den Berg  foreach(stashqueue->try_read_array();;pgsql_result portal) queueup(portal);
cff3a32014-09-12Stephen R. van den Berg  } mode=mergemode(this,mode); stashflushmode=keep; lock=0; }
4ef8a32014-10-29Stephen R. van den Berg outer: do { switch(mode) { default: break outer; case syncsend: PD(">Sync %d %d Queue\n",synctransact,++queueoutidx); qportals->write(synctransact++); add(PGSYNC); break; case flushsend: PD("Flush\n"); add(PGFLUSH); case sendout:; } if(towrite=sizeof(this)) { PD(">Sendcmd %O\n",((string)this)[..towrite-1]); towrite-=output_to(socket,towrite); } } while(0);
cff3a32014-09-12Stephen R. van den Berg  started=0;
ecbab12008-07-27Stephen R. van den Berg  }
cff3a32014-09-12Stephen R. van den Berg  final void sendterminate() { destruct(condition); // Delayed close() after flushing the output buffer
ecbab12008-07-27Stephen R. van den Berg  }
cff3a32014-09-12Stephen R. van den Berg  final int close() { return socket->close();
ecbab12008-07-27Stephen R. van den Berg  }
cff3a32014-09-12Stephen R. van den Berg  final void destroy() { catch(close()); // Exceptions don't work inside destructors
ecbab12008-07-27Stephen R. van den Berg  }
cff3a32014-09-12Stephen R. van den Berg  final void connectloop(object pgsqlsess,int nossl) { mixed err=catch { for(;;clear()) { socket->connect(pgsqlsess._host,pgsqlsess._port);
fc7f092014-06-01Martin Nilsson #if constant(SSL.File)
cff3a32014-09-12Stephen R. van den Berg  if(!nossl && !pgsqlsess->nossl && (pgsqlsess.options.use_ssl || pgsqlsess.options.force_ssl)) { PD("SSLRequest\n"); start()->add_int32(8)->add_int32(PG_PROTOCOL(1234,5679)) ->sendcmd(sendout); switch(read_int8()) { case 'S': object fcon=SSL.File(socket,SSL.Context()); if(fcon->connect()) { socket=fcon; break; } default:socket->close(); pgsqlsess.nossl=1; continue; case 'N': if(pgsqlsess.options.force_ssl) error("Encryption not supported on connection to %s:%d\n", pgsqlsess.host,pgsqlsess.port); } } #else if(pgsqlsess.options.force_ssl) error("Encryption library missing," " cannot establish connection to %s:%d\n", pgsqlsess.host,pgsqlsess.port); #endif break; } socket->set_backend(local_backend); socket->set_buffer_mode(i::this,0); socket->set_nonblocking(read_cb,write_cb,0); Thread.Thread(pgsqlsess->_processloop,this); return; }; pgsqlsess->_connectfail(err); } void create(object pgsqlsess,Thread.Queue _qportals,int nossl) { i::create(); o::create(); qportals = _qportals;
4ef8a32014-10-29Stephen R. van den Berg  synctransact = 1;
cff3a32014-09-12Stephen R. van den Berg  condition=Thread.Condition(); mux=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);
ecbab12008-07-27Stephen R. van den Berg  } }
f4c9d62009-02-15Stephen R. van den Berg //! The result object returned by @[Sql.pgsql()->big_query()], except for
0412962009-01-19Stephen R. van den Berg //! the noted differences it behaves the same as @[Sql.sql_result]. //! //! @seealso
f4c9d62009-02-15Stephen R. van den Berg //! @[Sql.sql_result], @[Sql.pgsql], @[Sql.Sql], @[Sql.pgsql()->big_query()]
ecbab12008-07-27Stephen R. van den Berg class pgsql_result {
11b13b2014-08-16Martin Nilsson  object _pgsqlsess;
cff3a32014-09-12Stephen R. van den Berg  protected int numrows; protected int eoffound; protected PGassist c; mixed _delayederror; portalstate _state;
11b13b2014-08-16Martin Nilsson  int _fetchlimit; int _alltext; int _forcetext;
ecbab12008-07-27Stephen R. van den Berg 
11b13b2014-08-16Martin Nilsson  string _portalname; int _bytesreceived; int _rowsreceived; int _inflight; int _portalbuffersize;
4ef8a32014-10-29Stephen R. van den Berg  int _synctransact;
cff3a32014-09-12Stephen R. van den Berg  Thread.MutexKey _unnamedportalkey,_unnamedstatementkey; protected Thread.Mutex closemux;
11b13b2014-08-16Martin Nilsson  array _params; string _statuscmdcomplete; string _query;
cff3a32014-09-12Stephen R. van den Berg  Thread.Queue _datarows;
11b13b2014-08-16Martin Nilsson  array(mapping(string:mixed)) _datarowdesc=({});
cff3a32014-09-12Stephen R. van den Berg  int _oldpbpos; object _plugbuffer; string _preparedname; mapping(string:mixed) _tprepared;
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg  protected string _sprintf(int type, void|mapping flags) {
11b13b2014-08-16Martin Nilsson  string res=UNDEFINED;
cff3a32014-09-12Stephen R. van den Berg  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), _statuscmdcomplete||""); break;
11b13b2014-08-16Martin Nilsson  } return res;
ecbab12008-07-27Stephen R. van den Berg  }
cff3a32014-09-12Stephen R. van den Berg  void create(object pgsqlsess,PGassist _c,string query, int portalbuffersize,int alltyped,array params,int forcetext) {
11b13b2014-08-16Martin Nilsson  _pgsqlsess = pgsqlsess;
cff3a32014-09-12Stephen R. van den Berg  c = _c;
11b13b2014-08-16Martin Nilsson  _query = query;
cff3a32014-09-12Stephen R. van den Berg  _datarows = Thread.Queue(); numrows = UNDEFINED; closemux=Thread.Mutex();
11b13b2014-08-16Martin Nilsson  _portalbuffersize=portalbuffersize; _alltext = !alltyped; _params = params; _forcetext = forcetext;
cff3a32014-09-12Stephen R. van den Berg  _state = portalinit;
11b13b2014-08-16Martin Nilsson  }
ecbab12008-07-27Stephen R. van den Berg 
11b13b2014-08-16Martin Nilsson  //! Returns the command-complete status for this query. //! //! @seealso //! @[affected_rows()] //! //! @note //! This function is PostgreSQL-specific, and thus it is not available //! through the generic SQL-interface.
cff3a32014-09-12Stephen R. van den Berg  string status_command_complete() {
11b13b2014-08-16Martin Nilsson  return _statuscmdcomplete; }
ecbab12008-07-27Stephen R. van den Berg 
11b13b2014-08-16Martin Nilsson  //! Returns the number of affected rows by this query. //! //! @seealso //! @[status_command_complete()] //! //! @note //! This function is PostgreSQL-specific, and thus it is not available //! through the generic SQL-interface.
cff3a32014-09-12Stephen R. van den Berg  int affected_rows() {
11b13b2014-08-16Martin Nilsson  int rows; if(_statuscmdcomplete) sscanf(_statuscmdcomplete,"%*s %d",rows); return rows; }
ecbab12008-07-27Stephen R. van den Berg 
11b13b2014-08-16Martin Nilsson  //! @seealso //! @[Sql.sql_result()->num_fields()]
cff3a32014-09-12Stephen R. van den Berg  int num_fields() {
11b13b2014-08-16Martin Nilsson  return sizeof(_datarowdesc); }
ecbab12008-07-27Stephen R. van den Berg 
11b13b2014-08-16Martin Nilsson  //! @seealso //! @[Sql.sql_result()->num_rows()]
cff3a32014-09-12Stephen R. van den Berg  int num_rows() {
11b13b2014-08-16Martin Nilsson  int numrows; if(_statuscmdcomplete) sscanf(_statuscmdcomplete,"%*s %d",numrows); return numrows; }
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg  protected inline void trydelayederror() { if(_delayederror) throwdelayederror(this); }
11b13b2014-08-16Martin Nilsson  //! @seealso //! @[Sql.sql_result()->eof()]
cff3a32014-09-12Stephen R. van den Berg  int eof() { trydelayederror();
11b13b2014-08-16Martin Nilsson  return eoffound; }
ecbab12008-07-27Stephen R. van den Berg 
11b13b2014-08-16Martin Nilsson  //! @seealso //! @[Sql.sql_result()->fetch_fields()]
cff3a32014-09-12Stephen R. van den Berg  array(mapping(string:mixed)) fetch_fields() { trydelayederror();
11b13b2014-08-16Martin Nilsson  return _datarowdesc+({}); }
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg  void _openportal() { _pgsqlsess->_portalsinflight++; Thread.MutexKey lock=closemux->lock(); _state=bound; lock=0; _statuscmdcomplete=UNDEFINED; }
4ef8a32014-10-29Stephen R. van den Berg  sctype _closeportal(void|PGplugbuffer plugbuffer) { sctype retval=keep;
cff3a32014-09-12Stephen R. van den Berg  PD("%O Try Closeportal %d\n",_portalname,_state); Thread.MutexKey lock=closemux->lock(); _fetchlimit=0; // disables further Executes switch(_state) { case copyinprogress:
4ef8a32014-10-29Stephen R. van den Berg  if(plugbuffer) { PD("CopyDone\n"); plugbuffer->add("c\0\0\0\4"); }
cff3a32014-09-12Stephen R. van den Berg  case bound: _state=closed; lock=0; PD("Close portal %O\n",_portalname); if(sizeof(_portalname)) {
4ef8a32014-10-29Stephen R. van den Berg  if(plugbuffer) { plugbuffer->add_int8('C')->add_hstring(({'P',_portalname,0}),4,4); retval=flushsend; }
cff3a32014-09-12Stephen R. van den Berg  } else _unnamedportalkey=0;
4ef8a32014-10-29Stephen R. van den Berg  if(!--_pgsqlsess->_portalsinflight && plugbuffer) { _pgsqlsess->_readyforquerycount++;
cff3a32014-09-12Stephen R. van den Berg  _pgsqlsess->_pportalcount=0;
4ef8a32014-10-29Stephen R. van den Berg  retval=syncsend;
cff3a32014-09-12Stephen R. van den Berg  } } lock=0; return retval; } final void _processdataready(int gfetchlimit) { _rowsreceived++; if(_rowsreceived==1) PD("<%O _fetchlimit %d=min(%d||1,%d), _inflight %d\n",_portalname, _fetchlimit,(_portalbuffersize>>1)*_rowsreceived/_bytesreceived, gfetchlimit,_inflight); if(_fetchlimit) { _fetchlimit= min((_portalbuffersize>>1)*_rowsreceived/_bytesreceived||1,gfetchlimit); 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;
ecbab12008-07-27Stephen R. van den Berg  }
cff3a32014-09-12Stephen R. van den Berg  } void _releasesession() { _inflight=0; _datarows->write(1); object plugbuffer=c->start(1); plugbuffer->sendcmd(_closeportal(plugbuffer));
11b13b2014-08-16Martin Nilsson  _pgsqlsess=UNDEFINED;
ecbab12008-07-27Stephen R. van den Berg  }
cff3a32014-09-12Stephen R. van den Berg  protected void destroy() { catch { // inside destructors, exceptions don't work _releasesession();
11b13b2014-08-16Martin Nilsson  }; }
ecbab12008-07-27Stephen R. van den Berg 
cff3a32014-09-12Stephen R. van den Berg  final void _sendexecute(int fetchlimit,void|PGplugbuffer plugbuffer) { int flushmode; PD("Execute portal %O fetchlimit %d\n",_portalname,fetchlimit); if(!plugbuffer) plugbuffer=c->start(1); plugbuffer->add_int8('E')->add_hstring(_portalname,4,8+1) ->add_int8(0)->add_int32(fetchlimit);
4ef8a32014-10-29Stephen R. van den Berg  if(!fetchlimit) flushmode=_closeportal(plugbuffer)==syncsend?syncsend:flushsend; else
cff3a32014-09-12Stephen R. van den Berg  _inflight+=fetchlimit, flushmode=flushsend; plugbuffer->sendcmd(flushmode,this);
11b13b2014-08-16Martin Nilsson  }
ecbab12008-07-27Stephen R. van den Berg 
11b13b2014-08-16Martin Nilsson  //! @returns //! One result row at a time. //! //! When using COPY FROM STDOUT, this method returns one row at a time //! as a single string containing the entire row. //!
cff3a32014-09-12Stephen R. van den Berg  //! @seealso //! @[eof()], @[send_row()] array(mixed) fetch_row() { int|array datarow; if(arrayp(datarow=_datarows->try_read())) return datarow; if(!datarow && !eoffound
4ef8a32014-10-29Stephen R. van den Berg  && (PD("%O Block for datarow\n",_portalname),
cff3a32014-09-12Stephen R. van den Berg  arrayp(datarow=_datarows->read()))) return datarow; trydelayederror(); eoffound=1; 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 //! @[eof()], @[fetch_row()] array(array(mixed)) fetch_row_array() { if(eoffound) return 0; array(array|int) datarow=_datarows->try_read_array(); if(!datarow) datarow=_datarows->read_array(); if(arrayp(datarow[-1])) return datarow; trydelayederror(); eoffound=1; return (datarow=datarow[..<1]); }
11b13b2014-08-16Martin Nilsson  //! @param copydatasend //! When using COPY FROM STDIN, this method accepts a string or an //! array of strings to be processed by the COPY command; when sending //! the amount of data sent per call does not have to hit row or column //! boundaries. //! //! The COPY FROM STDIN sequence needs to be completed by either
cff3a32014-09-12Stephen R. van den Berg  //! explicitly or implicitly destroying the result object, or by passing no //! argument to this method.
11b13b2014-08-16Martin Nilsson  //! //! @seealso
cff3a32014-09-12Stephen R. van den Berg  //! @[fetch_row()], @[eof()] void send_row(void|string|array(string) copydata) { trydelayederror(); if(copydata) { PD("CopyData\n"); c->start()->add_int8('d')->add_hstring(copydata,4,4)->sendcmd(sendout); } else _releasesession(); } protected void run_result_cb( function(pgsql_result, array(mixed), mixed ...:void) callback, array(mixed) args) { int|array datarow; while(arrayp(datarow=_datarows->read_array())) callback(this, datarow, @args); trydelayederror(); eoffound=1; callback(this, 0, @args); } //! Sets up a callback for every row returned from the database. //! First argument passed is the resultobject itself, second argument //! is the result row (zero on EOF). //! //! @seealso //! @[fetch_row()] void set_result_callback( function(pgsql_result, array(mixed), mixed ...:void) callback, mixed ... args) { if(callback) Thread.Thread(run_result_cb,callback,args); } protected void run_result_array_cb( function(pgsql_result, array(array(mixed)), mixed ...:void) callback, array(mixed) args) { array(array|int) datarow; while((datarow=_datarows->read_array()) && arrayp(datarow[-1])) callback(this, datarow, @args); trydelayederror(); eoffound=1; if(sizeof(datarow)>1) callback(this, datarow=datarow[..<1], @args); callback(this, 0, @args); } //! Sets up a callback for sets of rows returned from the database. //! First argument passed is the resultobject itself, second argument //! is the array of result rows (zero on EOF). //! //! @seealso //! @[fetch_row()] void set_result_array_callback( function(pgsql_result, array(array(mixed)), mixed ...:void) callback, mixed ... args) { if(callback) Thread.Thread(run_result_array_cb,callback,args);
11b13b2014-08-16Martin Nilsson  }
ecbab12008-07-27Stephen R. van den Berg 
11b13b2014-08-16Martin Nilsson }