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:1:
+
/*
+
* Some pgsql utility functions.
+
* They are kept here to avoid circular references.
+
*
+
*/
-
+
#pike __REAL_VERSION__
+
+
#include "pgsql.h"
+
+
#define FLUSH "H\0\0\0\4"
+
+
//! Some pgsql utility functions
+
+
class PGassist
+
{ int(-1..1) peek(int timeout) { }
+
+
string read(int len,void|int(0..1) not_all) { }
+
+
int write(string|array(string) data) { }
+
+
int getchar() { }
+
+
int close() { }
+
+
private final array(string) cmdbuf=({});
+
+
#ifdef USEPGsql
+
inherit _PGsql.PGsql;
+
#else
+
object portal;
+
+
void setportal(void|object newportal)
+
{ portal=newportal;
+
}
+
+
inline int(-1..1) bpeek(int timeout)
+
{ return peek(timeout);
+
}
+
+
int flushed=-1;
+
+
inline final int getbyte()
+
{ if(!flushed && !bpeek(0))
+
sendflush();
+
return getchar();
+
}
+
+
final string getstring(void|int len)
+
{ String.Buffer acc=String.Buffer();
+
if(!zero_type(len))
+
{ string res;
+
do
+
{ if(!flushed && !bpeek(0))
+
sendflush();
+
res=read(len,!flushed);
+
if(res)
+
{ if(!sizeof(res))
+
return acc->get();
+
acc->add(res);
+
}
+
}
+
while(sizeof(acc)<len&&res);
+
return sizeof(acc)?acc->get():res;
+
}
+
int c;
+
while((c=getbyte())>0)
+
acc->putchar(c);
+
return acc->get();
+
}
+
+
inline final int getint16()
+
{ int s0=getbyte();
+
int r=(s0&0x7f)<<8|getbyte();
+
return s0&0x80 ? r-(1<<15) : r ;
+
}
+
+
inline final int getint32()
+
{ int r=getint16();
+
r=r<<8|getbyte();
+
return r<<8|getbyte();
+
}
+
+
inline final int getint64()
+
{ int r=getint32();
+
return r<<32|getint32()&0xffffffff;
+
}
+
#endif
+
+
inline final string plugbyte(int x)
+
{ return String.int2char(x&255);
+
}
+
+
inline final string plugint16(int x)
+
{ return sprintf("%c%c",x>>8&255,x&255);
+
}
+
+
inline final string plugint32(int x)
+
{ return sprintf("%c%c%c%c",x>>24&255,x>>16&255,x>>8&255,x&255);
+
}
+
+
inline final string plugint64(int x)
+
{ return sprintf("%c%c%c%c%c%c%c%c",x>>56&255,x>>48&255,x>>40&255,x>>32&255,
+
x>>24&255,x>>16&255,x>>8&255,x&255);
+
}
+
+
final void sendflush()
+
{ sendcmd(({}),1);
+
}
+
+
final void sendcmd(string|array(string) data,void|int flush)
+
{ if(arrayp(data))
+
cmdbuf+=data;
+
else
+
cmdbuf+=({data});
+
switch(flush)
+
{ case 3:
+
cmdbuf+=({FLUSH});
+
flushed=1;
+
break;
+
default:
+
flushed=0;
+
break;
+
case 1:
+
cmdbuf+=({FLUSH});
+
PD("Flush\n");
+
case 2:
+
flushed=1;
+
{ int i=write(cmdbuf);
+
if(portal && portal._pgsqlsess)
+
{ portal._pgsqlsess._packetssent++;
+
portal._pgsqlsess._bytessent+=i;
+
}
+
}
+
cmdbuf=({});
+
}
+
}
+
+
final void sendterminate()
+
{ PD("Terminate\n");
+
sendcmd(({"X",plugint32(4)}),2);
+
close();
+
}
+
+
void create()
+
{
+
#ifdef USEPGsql
+
::create();
+
#endif
+
}
+
}
+
+
class PGconn
+
{ inherit PGassist:pg;
+
#ifdef UNBUFFEREDIO
+
inherit Stdio.File:std;
+
+
inline int getchar()
+
{ return std::read(1)[0];
+
}
+
#else
+
inherit Stdio.FILE:std;
+
+
inline int getchar()
+
{ return std::getchar();
+
}
+
#endif
+
+
inline int(-1..1) peek(int timeout)
+
{ return std::peek(timeout);
+
}
+
+
inline string read(int len,void|int(0..1) not_all)
+
{ return std::read(len,not_all);
+
}
+
+
inline int write(string|array(string) data)
+
{ return std::write(data);
+
}
+
+
int close()
+
{ return std::close();
+
}
+
+
void create(Stdio.File stream,object t)
+
{ std::create();
+
std::assign(stream);
+
pg::create();
+
}
+
}
+
+
#if constant(SSL.sslfile)
+
class PGconnS
+
{ inherit SSL.sslfile:std;
+
inherit PGassist:pg;
+
+
Stdio.File rawstream;
+
+
inline int(-1..1) peek(int timeout)
+
{ return rawstream.peek(timeout); // This is a kludge
+
} // Actually SSL.sslfile should provide a peek() method
+
+
inline string read(int len,void|int(0..1) not_all)
+
{ return std::read(len,not_all);
+
}
+
+
inline int write(string|array(string) data)
+
{ return std::write(data);
+
}
+
+
void create(Stdio.File stream, SSL.Context ctx)
+
{ rawstream=stream;
+
std::create(stream,ctx,1,1);
+
std::set_blocking();
+
if (!std::connect()) {
+
error("Secure connection failed.\n");
+
}
+
pg::create();
+
}
+
}
+
#endif
+
+
//! The result object returned by @[Sql.pgsql()->big_query()], except for
+
//! the noted differences it behaves the same as @[Sql.sql_result].
+
//!
+
//! @seealso
+
//! @[Sql.sql_result], @[Sql.pgsql], @[Sql.Sql], @[Sql.pgsql()->big_query()]
+
class pgsql_result {
+
+
object _pgsqlsess;
+
private int numrows;
+
private int eoffound;
+
private mixed delayederror;
+
private int copyinprogress;
+
int _fetchlimit;
+
int _alltext;
+
int _forcetext;
+
+
#ifdef NO_LOCKING
+
int _qmtxkey;
+
#else
+
Thread.MutexKey _qmtxkey;
+
#endif
+
+
string _portalname;
+
+
int _bytesreceived;
+
int _rowsreceived;
+
int _interruptable;
+
int _inflight;
+
int _portalbuffersize;
+
array _params;
+
string _statuscmdcomplete;
+
string _query;
+
array(array(mixed)) _datarows;
+
array(mapping(string:mixed)) _datarowdesc=({});
+
array(int) _datatypeoid;
+
#ifdef USEPGsql
+
int _buffer;
+
#endif
+
+
private object fetchmutex;
+
+
protected string _sprintf(int type, void|mapping flags)
+
{ string res=UNDEFINED;
+
switch(type)
+
{ case 'O':
+
res=sprintf("pgsql_result numrows: %d eof: %d querylock: %d"
+
" inflight: %d\nportalname: %O datarows: %d laststatus: %s\n",
+
numrows,eoffound,!!_qmtxkey,_inflight,
+
_portalname,sizeof(_datarowdesc),
+
_statuscmdcomplete||"");
+
break;
+
}
+
return res;
+
}
+
+
void create(object pgsqlsess,string query,int fetchlimit,
+
int portalbuffersize,int alltyped,array params,int forcetext)
+
{ _pgsqlsess = pgsqlsess;
+
_query = query;
+
_datarows = ({ }); numrows = UNDEFINED;
+
fetchmutex = Thread.Mutex();
+
_fetchlimit=forcetext?0:fetchlimit;
+
_portalbuffersize=portalbuffersize;
+
_alltext = !alltyped;
+
_params = params;
+
_forcetext = forcetext;
+
steallock();
+
}
+
+
//! 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.
+
string status_command_complete()
+
{ return _statuscmdcomplete;
+
}
+
+
//! 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.
+
int affected_rows()
+
{ int rows;
+
if(_statuscmdcomplete)
+
sscanf(_statuscmdcomplete,"%*s %d",rows);
+
return rows;
+
}
+
+
//! @seealso
+
//! @[Sql.sql_result()->num_fields()]
+
int num_fields()
+
{ return sizeof(_datarowdesc);
+
}
+
+
//! @seealso
+
//! @[Sql.sql_result()->num_rows()]
+
int num_rows()
+
{ int numrows;
+
if(_statuscmdcomplete)
+
sscanf(_statuscmdcomplete,"%*s %d",numrows);
+
return numrows;
+
}
+
+
//! @seealso
+
//! @[Sql.sql_result()->eof()]
+
int eof()
+
{ return eoffound;
+
}
+
+
//! @seealso
+
//! @[Sql.sql_result()->fetch_fields()]
+
array(mapping(string:mixed)) fetch_fields()
+
{ return _datarowdesc+({});
+
}
+
+
private void releasesession()
+
{ if(_pgsqlsess)
+
{ if(copyinprogress)
+
{ PD("CopyDone\n");
+
_pgsqlsess._c.sendcmd("c\0\0\0\4",1);
+
}
+
if(_pgsqlsess.is_open())
+
_pgsqlsess.resync(2);
+
}
+
_qmtxkey=UNDEFINED;
+
_pgsqlsess=UNDEFINED;
+
}
+
+
void destroy()
+
{ catch // inside destructors, exceptions don't work
+
{ releasesession();
+
};
+
}
+
+
inline private array(mixed) getdatarow()
+
{ array(mixed) datarow=_datarows[0];
+
_datarows=_datarows[1..];
+
return datarow;
+
}
+
+
private void steallock()
+
{
+
#ifndef NO_LOCKING
+
PD("Going to steal oldportal %d\n",!!_pgsqlsess._c.portal);
+
Thread.MutexKey stealmtxkey = _pgsqlsess._stealmutex.lock();
+
do
+
if(_qmtxkey = _pgsqlsess._querymutex.current_locking_key())
+
{ pgsql_result portalb;
+
if(portalb=_pgsqlsess._c.portal)
+
{ _pgsqlsess._nextportal++;
+
if(portalb->_interruptable)
+
portalb->fetch_row(2);
+
else
+
{ PD("Waiting for the querymutex\n");
+
if((_qmtxkey=_pgsqlsess._querymutex.lock(2)))
+
{ if(copyinprogress)
+
error("COPY needs to be finished first\n");
+
error("Driver bug, please report, "
+
"conflict while interleaving SQL-operations\n");
+
}
+
PD("Got the querymutex\n");
+
}
+
_pgsqlsess._nextportal--;
+
}
+
break;
+
}
+
while(!(_qmtxkey=_pgsqlsess._querymutex.trylock()));
+
#else
+
PD("Skipping lock\n");
+
_qmtxkey=1;
+
#endif
+
_pgsqlsess._c.setportal(this);
+
PD("Stealing successful\n");
+
}
+
+
//! @decl array(mixed) fetch_row()
+
//! @decl void fetch_row(string|array(string) copydatasend)
+
//!
+
//! @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.
+
//!
+
//! @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
+
//! explicitly or implicitly destroying the result object, or by passing a
+
//! zero argument to this method.
+
//!
+
//! @seealso
+
//! @[eof()]
+
array(mixed) fetch_row(void|int|string|array(string) buffer)
+
{
+
#ifndef NO_LOCKING
+
Thread.MutexKey fetchmtxkey = fetchmutex.lock();
+
#endif
+
if(!buffer && sizeof(_datarows))
+
return getdatarow();
+
if(copyinprogress)
+
{ fetchmtxkey = UNDEFINED;
+
if(stringp(buffer) || arrayp(buffer))
+
{ int totalsize=4;
+
if(arrayp(buffer))
+
foreach(buffer;;string value)
+
totalsize+=sizeof(value);
+
else
+
totalsize+=sizeof(buffer),buffer=({buffer});
+
PD("CopyData\n");
+
_pgsqlsess._c.sendcmd(
+
({"d",_pgsqlsess._c.plugint32(totalsize)})+buffer,2);
+
}
+
else
+
releasesession();
+
return UNDEFINED;
+
}
+
mixed err;
+
if(buffer!=2 && (err=delayederror))
+
{ delayederror=UNDEFINED;
+
throw(err);
+
}
+
err = catch
+
{ if(_portalname)
+
{ if(buffer!=2 && !_qmtxkey)
+
{ steallock();
+
if(_fetchlimit)
+
_pgsqlsess._sendexecute(_fetchlimit);
+
}
+
while(_pgsqlsess._closesent)
+
_pgsqlsess._decodemsg(); // Flush previous portal sequence
+
for(;;)
+
{
+
#ifdef PG_DEBUGMORE
+
PD("buffer: %d nextportal: %d lock: %d\n",
+
buffer,_pgsqlsess._nextportal,!!_qmtxkey);
+
#endif
+
#ifdef USEPGsql
+
_buffer=buffer;
+
#endif
+
switch(_pgsqlsess._decodemsg())
+
{ case copyinresponse:
+
copyinprogress=1;
+
return UNDEFINED;
+
case dataready:
+
_pgsqlsess._mstate=dataprocessed;
+
_rowsreceived++;
+
switch(buffer)
+
{ case 0:
+
case 1:
+
if(_fetchlimit)
+
_fetchlimit=
+
min(_portalbuffersize/2*_rowsreceived/_bytesreceived || 1,
+
_pgsqlsess._fetchlimit);
+
}
+
switch(buffer)
+
{ case 2:
+
case 3:
+
continue;
+
case 1:
+
_interruptable=1;
+
if(_pgsqlsess._nextportal)
+
continue;
+
#if STREAMEXECUTES
+
if(_fetchlimit && _inflight<=_fetchlimit-1)
+
_pgsqlsess._sendexecute(_fetchlimit);
+
#endif
+
return UNDEFINED;
+
}
+
#if STREAMEXECUTES
+
if(_fetchlimit && _inflight<=_fetchlimit-1)
+
_pgsqlsess._sendexecute(_fetchlimit); // Overlap Executes
+
#endif
+
return getdatarow();
+
case commandcomplete:
+
_inflight=0;
+
releasesession();
+
switch(buffer)
+
{ case 1:
+
case 2:
+
return UNDEFINED;
+
case 3:
+
if(sizeof(_datarows))
+
return getdatarow();
+
}
+
break;
+
case portalsuspended:
+
if(_inflight)
+
continue;
+
if(_pgsqlsess._nextportal)
+
{ switch(buffer)
+
{ case 1:
+
case 2:
+
_qmtxkey = UNDEFINED;
+
return UNDEFINED;
+
case 3:
+
_qmtxkey = UNDEFINED;
+
return getdatarow();
+
}
+
_fetchlimit=FETCHLIMITLONGRUN;
+
if(sizeof(_datarows))
+
{ _qmtxkey = UNDEFINED;
+
return getdatarow();
+
}
+
buffer=3;
+
}
+
_pgsqlsess._sendexecute(_fetchlimit);
+
default:
+
continue;
+
}
+
break;
+
}
+
}
+
eoffound=1;
+
return UNDEFINED;
+
};
+
PD("Exception %O\n",err);
+
_pgsqlsess.resync();
+
if(buffer!=2)
+
throw(err);
+
if(!delayederror)
+
delayederror=err;
+
return UNDEFINED;
+
}
+
+
};
Newline at end of file added.