|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pike __REAL_VERSION__ |
#pragma dynamic_dot |
#require constant(Thread.Thread) |
|
#include "pgsql.h" |
|
#define PORTALINIT 0 // Portal states |
#define PARSING 1 |
#define BOUND 2 |
#define COMMITTED 3 |
#define COPYINPROGRESS 4 |
#define CLOSING 5 |
#define CLOSED 6 |
#define PURGED 7 |
|
#define NOERROR 0 // Error states networkparser |
#define PROTOCOLERROR 1 |
#define PROTOCOLUNSUPPORTED 2 |
|
#define LOSTERROR "Database connection lost" |
|
#if PG_DEADLOCK_SENTINEL |
private multiset mutexes = set_weak_flag((<>), Pike.WEAK); |
private int deadlockseq; |
|
private class MUTEX { |
inherit Thread.Mutex; |
|
private Thread.Thread sentinelthread; |
|
private void dump_lockgraph(Thread.Thread curthread) { |
sleep(PG_DEADLOCK_SENTINEL); |
String.Buffer buf = String.Buffer(); |
buf.add("\n{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{\n"); |
buf.sprintf("Deadlock detected at\n%s\n", |
describe_backtrace(curthread.backtrace(), -1)); |
foreach(mutexes; this_program mu; ) |
if (mu) { |
Thread.Thread ct; |
if (ct = mu.current_locking_thread()) |
buf.sprintf("====================================== Mutex: %O\n%s\n", |
mu, describe_backtrace(ct.backtrace(), -1)); |
} |
buf.add("}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}"); |
werror(replace(buf.get(), "\n", sprintf("\n%d> ", deadlockseq++)) + "\n"); |
} |
|
Thread.MutexKey lock(void|int type) { |
Thread.MutexKey key; |
if (!(key = trylock(type))) { |
sentinelthread = Thread.Thread(dump_lockgraph, this_thread()); |
key = ::lock(type); |
sentinelthread->kill(); |
} |
return key; |
} |
|
protected void create() { |
|
mutexes[this] = 1; |
} |
}; |
#else |
#define MUTEX Thread.Mutex |
#endif |
|
|
final Pike.Backend local_backend; |
|
private Shuffler.Shuffler shuffler = Shuffler.Shuffler(); |
private Pike.Backend cb_backend; |
private Result qalreadyprinted; |
private Thread.Mutex backendmux = Thread.Mutex(); |
final multiset(proxy) clients = set_weak_flag((<>), Pike.WEAK); |
|
mapping describenodata |
= (["datarowdesc":({}), "datarowtypes":({}), "datatypeoid":({})]); |
private multiset censoroptions = (<"use_ssl", "force_ssl", |
"cache_autoprepared_statements", "reconnect", "text_query", "is_superuser", |
"server_encoding", "server_version", "integer_datetimes", |
"session_authorization">); |
|
|
|
|
final Regexp createprefix = iregexp("^\a*(CREATE|DROP)\a"); |
|
|
|
private Regexp dontcacheprefix = iregexp("^\a*(FETCH|COPY)\a"); |
|
|
|
|
|
private Regexp paralleliseprefix |
= iregexp("^\a*((SELEC|INSER)T|(UPDA|DELE)TE|(FETC|WIT)H)\a"); |
|
|
|
|
|
|
final Regexp transbeginprefix |
= iregexp("^\a*(BEGIN|START)([; \t\f\r\n]|$)"); |
|
|
|
|
|
|
final Regexp transendprefix |
= iregexp("^\a*(COMMIT|ROLLBACK|END)([; \t\f\r\n]|$)"); |
|
|
|
|
|
final Regexp nodataresprefix |
= iregexp("^\a*(RESET|CLOSE|DISCARD)\a"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
private Regexp execfetchlimit |
= iregexp("^\a*((UPDA|DELE)TE|INSERT|CREATE|DROP|CALL" |
"|RESET|CLOSE|DISCARD)\a|\aLIMIT\a+[1-9][; \t\f\r\n]*$"); |
|
private void default_backend_runs() { |
cb_backend = Pike.DefaultBackend; |
} |
|
protected void create() { |
atexit(_destruct); |
|
shuffler->set_backend(cb_backend = local_backend = Pike.Backend()); |
call_out(default_backend_runs, 0); |
} |
|
protected void _destruct() { |
foreach (clients; proxy client; ) |
destruct(client); |
} |
|
private Regexp iregexp(string expr) { |
Stdio.Buffer ret = Stdio.Buffer(); |
foreach (expr; ; int c) |
if (c >= 'A' && c <= 'Z') |
ret->add('[', c, c + 'a' - 'A', ']'); |
else if (c == '\a') |
ret->add("[ \t\f\r\n]"); |
else |
ret->add_int8(c); |
return Regexp(ret->read()); |
} |
|
final void closestatement(bufcon|conxsess plugbuffer, string oldprep) { |
if (oldprep) { |
PD("Close statement %s\n", oldprep); |
CHAIN(plugbuffer)->add_int8('C')->add_hstring(({'S', oldprep, 0}), 4, 4); |
} |
} |
|
private void run_local_backend() { |
Thread.MutexKey lock; |
int looponce; |
do { |
looponce = 0; |
if (lock = backendmux->trylock()) { |
PD("Starting local backend\n"); |
while (sizeof(clients) |
|| sizeof(local_backend->call_out_info())) { |
mixed err; |
if (err = catch(local_backend(4096.0))) |
master()->handle_error(err); |
} |
PD("Terminating local backend\n"); |
lock = 0; |
looponce = sizeof(clients); |
} |
} while (looponce); |
} |
|
|
|
final void register_backend(proxy client) { |
int startbackend = !sizeof(clients); |
clients[client] = 1; |
if (startbackend) |
Thread.Thread(run_local_backend); |
} |
|
final void throwdelayederror(Result|proxy parent) { |
if (mixed err = parent->delayederror) { |
if (!objectp(parent->pgsqlsess)) |
parent->untolderror = 0; |
else if (parent->pgsqlsess) |
parent->pgsqlsess->untolderror = parent->pgsqlsess->delayederror = 0; |
parent->delayederror = 0; |
if (stringp(err)) |
err = ({err, backtrace()[..<2]}); |
throw(err); |
} |
} |
|
private int readoidformat(int oid) { |
switch (oid) { |
case BOOLOID: |
case BYTEAOID: |
case CHAROID: |
case INT8OID: |
case INT2OID: |
case INT4OID: |
case FLOAT4OID: |
#if !constant(__builtin.__SINGLE_PRECISION_FLOAT__) |
case FLOAT8OID: |
#endif |
case NUMERICOID: |
case TEXTOID: |
case OIDOID: |
case XMLOID: |
case DATEOID: |
case TIMEOID: |
case TIMETZOID: |
case TIMESTAMPOID: |
case TIMESTAMPTZOID: |
case INTERVALOID: |
case INT4RANGEOID: |
case INT8RANGEOID: |
case DATERANGEOID: |
case TSRANGEOID: |
case TSTZRANGEOID: |
case MACADDROID: |
case BPCHAROID: |
case VARCHAROID: |
case CIDROID: |
case INETOID: |
case CTIDOID: |
case UUIDOID: |
return 1; |
} |
return 0; |
} |
|
private int writeoidformat(int oid, array(string|int) paramValues, |
array(int) ai) { |
mixed value = paramValues[ai[0]++]; |
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; |
case NUMERICOID: |
case CIDROID: |
case INETOID: |
case DATEOID: |
case TIMEOID: |
case TIMETZOID: |
case TIMESTAMPOID: |
case TIMESTAMPTZOID: |
case INTERVALOID: |
case INT4RANGEOID: |
case INT8RANGEOID: |
case DATERANGEOID: |
case TSRANGEOID: |
case TSTZRANGEOID: |
case FLOAT4OID: |
#if !constant(__builtin.__SINGLE_PRECISION_FLOAT__) |
case FLOAT8OID: |
#endif |
if (!stringp(value)) |
return 1; |
} |
return 0; |
} |
|
#define DAYSEPOCHTO2000 10957 // 2000/01/01 00:00:00 UTC |
#define USEPOCHTO2000 (DAYSEPOCHTO2000*24*3600*1000000) |
|
private array timestamptotype |
= ({Val.Timestamp, 8, USEPOCHTO2000, "usecs", 8}); |
private array datetotype = ({Val.Date, 4, DAYSEPOCHTO2000, "days", 4}); |
|
private mapping(int:array) oidtotype = ([ |
DATEOID: datetotype, |
TIMEOID: ({Val.Time, 8, 0, "usecs", 8}), |
TIMETZOID: ({Val.TimeTZ, 12, 0, "usecs", 8, "timezone", 4}), |
INTERVALOID: ({Val.Interval, 16, 0, "usecs", 8, "days", 4, "months",4}), |
TIMESTAMPOID: timestamptotype, |
TIMESTAMPTZOID: timestamptotype, |
INT4RANGEOID: ({0, 4}), |
INT8RANGEOID: ({0, 8}), |
DATERANGEOID: datetotype, |
TSRANGEOID: timestamptotype, |
TSTZRANGEOID: timestamptotype, |
]); |
|
private inline mixed callout(function(mixed ...:void) f, |
float|int delay, mixed ... args) { |
return cb_backend->call_out(f, delay, @args); |
} |
|
|
|
class bufcon { |
inherit Stdio.Buffer; |
private Thread.ResourceCountKey dirty; |
|
#ifdef PG_DEBUGRACE |
final bufcon `chain() { |
return this; |
} |
#endif |
|
private conxion realbuffer; |
|
protected void create(conxion _realbuffer) { |
realbuffer = _realbuffer; |
} |
|
final Thread.ResourceCount `stashcount() { |
return realbuffer->stashcount; |
} |
|
final MUTEX `shortmux() { |
return realbuffer->shortmux; |
} |
|
final bufcon start(void|int|array(Thread.MutexKey) waitforreal) { |
dirty = stashcount->acquire(); |
#ifdef PG_DEBUG |
if (waitforreal) |
error("pgsql.bufcon not allowed here\n"); |
#endif |
return this; |
} |
|
final void sendcmd(int mode, void|Result portal) { |
Thread.MutexKey lock = shortmux->lock(); |
if (portal) |
realbuffer->stashqueue->write(portal); |
if (mode == SYNCSEND) { |
add(PGSYNC); |
realbuffer->stashqueue->write(1); |
mode = SENDOUT; |
} |
realbuffer->stash->add(this); |
PD("%d>Stashed mode %d > %d\n", |
realbuffer->socket->query_fd(), mode, realbuffer->stashflushmode); |
if (mode > realbuffer->stashflushmode) |
realbuffer->stashflushmode = mode; |
dirty = 0; |
lock = 0; |
this->clear(); |
if (lock = realbuffer->nostash->trylock(1)) { |
#ifdef PG_DEBUGRACE |
conxsess sess = conxsess(realbuffer); |
realbuffer->started = lock; |
lock = 0; |
sess->sendcmd(SENDOUT); |
#else |
realbuffer->started = lock; |
lock = 0; |
realbuffer->sendcmd(SENDOUT); |
#endif |
} |
} |
}; |
|
class conxiin { |
inherit Stdio.Buffer:i; |
|
final Thread.Condition fillread; |
final MUTEX fillreadmux; |
final int procmsg; |
private int didreadcb; |
|
#if PG_DEBUGHISTORY > 0 |
final array history = ({}); |
|
final int(-1..) input_from(Stdio.Stream stm, void|int(0..) nbytes) { |
int oldsize = sizeof(this); |
int ret = i::input_from(stm, nbytes); |
if (ret) { |
Stdio.Buffer tb = Stdio.Buffer(this); |
tb->consume(oldsize); |
history += ({"<<"+tb->read(ret)}); |
history = history[<PG_DEBUGHISTORY - 1 ..]; |
} |
return ret; |
} |
#endif |
|
protected final bool range_error(int howmuch) { |
#ifdef PG_DEBUG |
if (howmuch < 0) { |
int available = unread(0); |
unread(available); |
error("Out of range %d %O %O\n", howmuch, |
((string)this)[.. available-1], ((string)this)[available ..]); |
} |
#endif |
if (fillread) { |
Thread.MutexKey lock = fillreadmux->lock(); |
if (!didreadcb) |
fillread.wait(lock); |
didreadcb = 0; |
} else |
throw(MAGICTERMINATE); |
return true; |
} |
|
final int read_cb(mixed id, mixed b) { |
PD("Read callback %O\n", b && ((string)b) |
#ifndef PG_DEBUGMORE |
[..255] |
#endif |
); |
Thread.MutexKey lock = fillreadmux->lock(); |
if (procmsg && id) |
procmsg = 0, lock = 0, Thread.Thread(id); |
else if (fillread) |
didreadcb = 1, fillread.signal(); |
return 0; |
} |
|
protected void create() { |
i::create(); |
fillreadmux = MUTEX(); |
fillread = Thread.Condition(); |
} |
}; |
|
class sfile { |
inherit Stdio.File; |
final int query_fd() { |
return is_open() ? ::query_fd() : -1; |
} |
}; |
|
class conxion { |
inherit Stdio.Buffer:o; |
final conxiin i; |
|
private Thread.Queue qportals; |
final MUTEX shortmux; |
private int closenext; |
|
final sfile |
#if constant(SSL.File) |
|SSL.File |
#endif |
socket; |
final Shuffler.Shuffle shuffle; |
final multiset(Result) runningportals = (<>); |
|
final MUTEX nostash; |
final Thread.MutexKey started; |
final Thread.Queue stashqueue; |
final Stdio.Buffer stash; |
|
final int(KEEP..SYNCSEND) stashflushmode; |
|
final Thread.ResourceCount stashcount; |
final int synctransact; |
#ifdef PG_DEBUGRACE |
final mixed nostrack; |
#endif |
#ifdef PG_DEBUG |
final int queueoutidx; |
final int queueinidx = -1; |
#endif |
|
private inline void queueup(Result portal) { |
qportals->write(portal); portal->_synctransact = synctransact; |
PD("%d>%O %d %d Queue portal %d bytes\n", socket->query_fd(), |
portal._portalname, ++queueoutidx, synctransact, sizeof(this)); |
} |
|
final bufcon|conxsess start(void|int|array(Thread.MutexKey) waitforreal) { |
Thread.MutexKey lock; |
#ifdef PG_DEBUGRACE |
if (nostash->current_locking_thread()) |
PD("Nostash locked by %s\n", |
describe_backtrace(nostash->current_locking_thread()->backtrace())); |
#endif |
while (lock = nostash |
&& ((intp(waitforreal) && waitforreal > 0 |
? nostash->lock : nostash->trylock)(1))) { |
int mode; |
if (sizeof(stash) && (mode = getstash(KEEP)) > KEEP) |
sendcmd(mode); |
if (!stashcount->drained()) { |
lock = 0; |
if (arrayp(waitforreal)) |
waitforreal[0] = 0; |
stashcount->wait_till_drained(); |
if (arrayp(waitforreal)) |
return 0; |
continue; |
} |
#ifdef PG_DEBUGRACE |
conxsess sess = conxsess(this); |
#endif |
started = lock; |
#ifdef PG_DEBUGRACE |
return sess; |
#else |
return this; |
#endif |
} |
if (arrayp(waitforreal)) |
waitforreal[0] = 0; |
return !waitforreal && bufcon(this)->start(); |
} |
|
private void done_cb() { |
if (!i->fillread) |
close(); |
} |
|
private int getstash(int mode) { |
Thread.MutexKey lock = shortmux->lock(); |
add(stash); stash->clear(); |
foreach (stashqueue->try_read_array(); ; int|Result portal) |
if (intp(portal)) |
qportals->write(synctransact++); |
else |
queueup(portal); |
PD("%d>Got stash mode %d > %d\n", |
socket->query_fd(), stashflushmode, mode); |
if (stashflushmode > mode) |
mode = stashflushmode; |
stashflushmode = KEEP; |
return mode; |
} |
|
final void sendcmd(void|int mode, void|Result portal) { |
if (portal) |
queueup(portal); |
unfinalised: |
do { |
switch (mode) { |
default: |
break unfinalised; |
case SYNCSEND: |
PD("%d>Sync %d %d Queue\n", |
socket->query_fd(), synctransact, ++queueoutidx); |
add(PGSYNC); |
mode = SENDOUT; |
break; |
case FLUSHLOGSEND: |
PD("%d>%d Queue simplequery %d bytes\n", |
socket->query_fd(), ++queueoutidx, sizeof(this)); |
mode = FLUSHSEND; |
} |
qportals->write(synctransact++); |
} while (0); |
if (sizeof(stash)) |
mode = getstash(mode); |
#ifdef PG_DEBUG |
mixed err; |
#endif |
for(;;) { |
#ifdef PG_DEBUG |
err = |
#endif |
catch { |
outer: |
do { |
switch (mode) { |
default: |
PD("%d>Skip flush %d Queue %O\n", |
socket->query_fd(), mode, (string)this); |
break outer; |
case FLUSHSEND: |
PD("Flush\n"); |
add(PGFLUSH); |
case SENDOUT:; |
} |
if (sizeof(this)) { |
PD("%d>Sendcmd %O\n", socket->query_fd(), (string)this); |
#if PG_DEBUGHISTORY > 0 |
i->history += ({">>" + (string)this}); |
i->history = i->history[<PG_DEBUGHISTORY - 1 ..]; |
#endif |
shuffle->add_source(this); |
shuffle->start(1, 1); |
} |
} while (0); |
started = 0; |
if (sizeof(stash) && (started = nostash->trylock(2))) { |
#ifdef PG_DEBUGRACE |
conxsess sess = conxsess(this); |
sess->sendcmd(SENDOUT); |
#else |
mode = getstash(SENDOUT); |
continue; |
#endif |
} |
return; |
}; |
break; |
} |
started = 0; |
PD("Sendcmd failed %s\n", describe_backtrace(err)); |
destruct(this); |
} |
|
final int close() { |
if (!closenext && nostash) { |
closenext = 1; |
{ |
Thread.MutexKey lock = i->fillreadmux->lock(); |
if (i->fillread) { |
shuffle->set_done_callback(done_cb); |
i->fillread.signal(); |
i->fillread = 0; |
} |
} |
PD("%d>Delayed close, flush write\n", socket->query_fd()); |
i->read_cb(socket->query_id(), 0); |
return 0; |
} else |
return -1; |
} |
|
final void purge() { |
if (stashcount) { |
stashcount = 0; |
PD("%d>Purge conxion %d\n", socket ? socket->query_fd() : -1, !!nostash); |
int|Result portal; |
if (qportals) |
while (portal = qportals->try_read()) |
if (objectp(portal)) |
portal->_purgeportal(); |
if (nostash) { |
while (sizeof(runningportals)) |
catch { |
foreach (runningportals; Result result; ) |
if (!result.datarowtypes) { |
result.datarowtypes = ({}); |
if (result._state != PURGED && !result.delayederror) |
result.delayederror = LOSTERROR; |
result._ddescribe->broadcast(); |
runningportals[result] = 0; |
} else |
destruct(result); |
}; |
destruct(nostash); |
if (socket->set_non_blocking) |
socket->set_non_blocking(); |
PD("%d>Close socket\n", socket->query_fd()); |
shuffle->stop(); |
socket->close(); |
} |
destruct(this); |
} |
} |
|
protected void _destruct() { |
PD("%d>Close conxion %d\n", socket ? socket->query_fd() : -1, !!nostash); |
catch(purge()); |
} |
|
final void connectloop(proxy pgsqlsess, int nossl) { |
#ifdef PG_DEBUG |
mixed err = |
#endif |
catch { |
for (; ; clear()) { |
socket->connect(pgsqlsess. host, pgsqlsess. port); |
socket->set_nodelay(1); |
#if constant(SSL.File) |
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); |
string s = socket.read(1); |
switch (sizeof(s) && s[0]) { |
case 'S': |
SSL.File fcon = SSL.File(socket, SSL.Context()); |
if (fcon->connect()) { |
socket->set_backend(local_backend); |
socket = fcon; |
break; |
} |
default: |
PD("%d>Close socket short\n", socket->query_fd()); |
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; |
} |
if (!socket->is_open()) |
error(strerror(socket->errno()) + ".\n"); |
socket->set_backend(local_backend); |
socket->set_buffer_mode(i, 0); |
socket->set_nonblocking(i->read_cb, 0, close); |
shuffle = shuffler->shuffle(socket); |
if (nossl != 2) |
Thread.Thread(pgsqlsess->processloop, this); |
return; |
}; |
PD("Connect error %s\n", describe_backtrace(err)); |
catch(destruct(pgsqlsess->waitforauthready)); |
if (this) |
destruct(this); |
} |
|
protected string _sprintf(int type) { |
string res; |
if (!this) |
return "(destructed)"; |
switch (type) { |
case 'O': |
int fd = -1; |
if (socket) |
catch(fd = socket->query_fd()); |
if (!this) |
return "(destructed)"; |
res = predef::sprintf("conxion fd: %d input queue: %d/%d " |
"queued portals: %d output queue: %d/%d\n" |
"started: %d\n", |
fd, sizeof(i), i->_size_object(), |
qportals && qportals->size(), sizeof(this), _size_object(), |
!!started); |
break; |
} |
return res; |
} |
|
protected void create(proxy pgsqlsess, Thread.Queue _qportals, int nossl) { |
o::create(); |
qportals = _qportals; |
synctransact = 1; |
socket = sfile(); |
i = conxiin(); |
shortmux = MUTEX(); |
nostash = MUTEX(); |
closenext = 0; |
stashqueue = Thread.Queue(); |
stash = Stdio.Buffer(); |
stashcount = Thread.ResourceCount(); |
Thread.Thread(connectloop, pgsqlsess, nossl); |
} |
}; |
|
#ifdef PG_DEBUGRACE |
class conxsess { |
final conxion chain; |
|
protected void create(conxion parent) { |
if (parent->started) |
werror("Overwriting conxsess %s %s\n", |
describe_backtrace(({"new ", backtrace()[..<1]})), |
describe_backtrace(({"old ", parent->nostrack}))); |
parent->nostrack = backtrace(); |
chain = parent; |
} |
|
final void sendcmd(int mode, void|Result portal) { |
chain->sendcmd(mode, portal); |
chain = 0; |
} |
|
protected void _destruct() { |
if (chain) |
werror("Untransmitted conxsess %s\n", |
describe_backtrace(({"", backtrace()[..<1]}))); |
} |
}; |
#endif |
|
|
|
|
|
|
class Result { |
|
inherit __builtin.Sql.Result; |
|
private proxy pgsqlsess; |
private int(0..1) eoffound; |
private conxion c; |
private conxiin cr; |
final int(0..1) untolderror; |
final mixed delayederror; |
|
final int(PORTALINIT..PURGED) _state; |
|
final int _fetchlimit; |
private int(0..1) alltext; |
final int(0..1) _forcetext; |
private int syncparse; |
private int transtype; |
|
final string _portalname; |
|
private int inflight; |
int portalbuffersize; |
private MUTEX closemux; |
private Thread.Queue datarows; |
private Thread.ResourceCountKey stmtifkey, portalsifkey; |
private array(mapping(string:mixed)) datarowdesc; |
final array(int) datarowtypes; |
private string statuscmdcomplete; |
private int bytesreceived; |
final int _synctransact; |
final Thread.Condition _ddescribe; |
final MUTEX _ddescribemux; |
final Thread.MutexKey _unnamedportalkey, _unnamedstatementkey; |
final array _params; |
final string _query; |
final string _preparedname; |
final mapping(string:mixed) _tprepared; |
private function(:void) gottimeout; |
private int timeout; |
|
protected string _sprintf(int type) { |
string res; |
if (!this) |
return "(destructed)"; |
switch (type) { |
case 'O': |
int fd = -1; |
if (c && c->socket) |
catch(fd = c->socket->query_fd()); |
if (!this) |
return ""; |
res = sprintf( |
"Result state: %d numrows: %d eof: %d inflight: %d\n" |
"fd: %O portalname: %O coltypes: %d" |
" synctransact: %d laststatus: %s\n" |
"stash: %O\n" |
#if PG_DEBUGHISTORY > 0 |
"history: %O\n" |
#endif |
"query: %O\n%s\n", |
_state, index, eoffound, inflight, |
fd, _portalname, |
datarowtypes && sizeof(datarowtypes), _synctransact, |
statuscmdcomplete |
|| (_unnamedstatementkey ? "*parsing*" : ""), |
qalreadyprinted == this ? 0 |
: c && (string)c->stash, |
#if PG_DEBUGHISTORY > 0 |
qalreadyprinted == this ? 0 : c && c->i->history, |
#endif |
qalreadyprinted == this |
? "..." : replace(_query, "xxxx\n", "\\n"), |
qalreadyprinted == this ? "..." : _showbindings()*"\n"); |
qalreadyprinted = this; |
break; |
} |
return res; |
} |
|
protected void create(proxy _pgsqlsess, conxion _c, string query, |
int _portalbuffersize, int alltyped, array params, int forcetext, |
int _timeout, int _syncparse, int _transtype) { |
pgsqlsess = _pgsqlsess; |
if (c = _c) |
cr = c->i; |
else |
losterror(LOSTERROR); |
_query = query; |
datarows = Thread.Queue(); |
_ddescribe = Thread.Condition(); |
_ddescribemux = MUTEX(); |
closemux = MUTEX(); |
portalbuffersize = _portalbuffersize; |
alltext = !alltyped; |
_params = params; |
_forcetext = forcetext; |
_state = PORTALINIT; |
timeout = _timeout; |
syncparse = _syncparse; |
gottimeout = _pgsqlsess->cancelquery; |
c->runningportals[this] = 1; |
transtype = _transtype; |
} |
|
final array(string) _showbindings() { |
array(string) msgs = ({}); |
if (_params) { |
array from, to, paramValues; |
[from, to, paramValues] = _params; |
if (sizeof(paramValues)) { |
int i; |
string val, 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; |
} |
|
|
|
|
|
final string status_command_complete() { |
if (!statuscmdcomplete) { |
if (!datarowtypes) |
waitfordescribe(); |
{ |
Thread.MutexKey lock = closemux->lock(); |
if (_fetchlimit) { |
array(Thread.MutexKey) reflock = ({ _fetchlimit = 0 }); |
for (;;) { |
reflock[0] = lock; |
lock = 0; |
if (!_sendexecute(0, reflock)) { |
PD("Status_command_complete retry closemux %O\n", _portalname); |
lock = closemux->lock(); |
continue; |
} |
break; |
} |
} |
lock = 0; |
lock = _ddescribemux->lock(); |
if (!statuscmdcomplete) |
PT(_ddescribe->wait(lock)); |
} |
if (this) |
trydelayederror(); |
else |
error(LOSTERROR); |
} |
return statuscmdcomplete; |
} |
|
|
|
|
|
final int affected_rows() { |
int rows; |
sscanf(status_command_complete() || "", "%*s %d %d", rows, rows); |
return rows; |
} |
|
final void _storetiming() { |
if (_tprepared) { |
_tprepared.trun = gethrtime() - _tprepared.trunstart; |
m_delete(_tprepared, "trunstart"); |
_tprepared = 0; |
} |
} |
|
private void waitfordescribe() { |
{ |
Thread.MutexKey lock = _ddescribemux->lock(); |
if (!datarowtypes) |
PT(_ddescribe->wait(lock)); |
} |
if (this) |
trydelayederror(); |
else |
error(LOSTERROR); |
} |
|
|
|
final int num_fields() { |
if (!datarowtypes) |
waitfordescribe(); |
return sizeof(datarowtypes); |
} |
|
|
|
|
|
|
|
|
|
final int num_rows() { |
trydelayederror(); |
return index; |
} |
|
private void losterror(void|string err) { |
if (pgsqlsess) |
err = pgsqlsess->geterror(1) || err; |
if (err) |
error("%s\n", err); |
} |
|
private void trydelayederror() { |
if (delayederror) |
throwdelayederror(this); |
else if (_state == PURGED) |
losterror(); |
} |
|
|
|
final int eof() { |
trydelayederror(); |
return eoffound; |
} |
|
|
|
final array(mapping(string:mixed)) fetch_fields() { |
if (!datarowtypes) |
waitfordescribe(); |
if (!datarowdesc) |
error(LOSTERROR); |
return datarowdesc + ({}); |
} |
|
#ifdef PG_DEBUG |
#define INTVOID int |
#else |
#define INTVOID void |
#endif |
final INTVOID _decodedata(int msglen, string cenc) { |
_storetiming(); _releasestatement(); |
string serror; |
bytesreceived += msglen; |
int cols = cr->read_int16(); |
array a = allocate(cols, !alltext && Val.null); |
#ifdef PG_DEBUG |
msglen -= 2 + 4 * cols; |
#endif |
foreach (datarowtypes; int i; int typ) { |
int collen = cr->read_sint(4); |
if (collen > 0) { |
#ifdef PG_DEBUG |
msglen -= collen; |
#endif |
mixed value; |
switch (typ) { |
case FLOAT4OID: |
#if !constant(__builtin.__SINGLE_PRECISION_FLOAT__) |
case FLOAT8OID: |
#endif |
if (_forcetext) { |
if (!alltext) { |
value = (float)cr->read(collen); |
break; |
} |
} else { |
[ value ] = cr->sscanf(collen == 4 ? "%4F" : "%8F"); |
if (alltext) |
value = sprintf("%.*g", collen == 4 ? 9 : 17, value); |
break; |
} |
default:value = cr->read(collen); |
break; |
case CHAROID: |
value = alltext ? cr->read(1) : cr->read_int8(); |
break; |
case BOOLOID:value = cr->read_int8(); |
switch (value) { |
case 'f':value = 0; |
break; |
case 't':value = 1; |
} |
if (alltext) |
value = value ? "t" : "f"; |
break; |
case TEXTOID: |
case BPCHAROID: |
case VARCHAROID: |
value = cr->read(collen); |
if (cenc == UTF8CHARSET && catch(value = utf8_to_string(value)) |
&& !serror) |
serror = SERROR("%O contains non-%s characters\n", |
value, UTF8CHARSET); |
break; |
case NUMERICOID: |
if (_forcetext) { |
value = cr->read(collen); |
if (!alltext) { |
value = value/"."; |
if (sizeof(value) == 1) |
value = (int)value[0]; |
else { |
int i, denom; |
for (i = sizeof(value[1]), denom = 1; --i >= 0; denom *= 10); |
i = (int)value[0]; |
value = (int)value[1]; |
value = Gmp.mpq(i * denom + (i >= 0 ? value : -value), |
denom); |
} |
} |
} else { |
int nwords = cr->read_int16(); |
int magnitude = cr->read_sint(2); |
int sign = cr->read_int16(); |
cr->consume(2); |
if (nwords) { |
for (value = cr->read_int16(); --nwords; magnitude--) |
value = value * NUMERIC_MAGSTEP + cr->read_int16(); |
if (sign) |
value = -value; |
if (magnitude > 0) |
do |
value *= NUMERIC_MAGSTEP; |
while (--magnitude); |
else if (magnitude < 0) { |
for (sign = NUMERIC_MAGSTEP; |
++magnitude; |
sign *= NUMERIC_MAGSTEP); |
value = Gmp.mpq(value, sign); |
} |
} else |
value = 0; |
if (alltext) |
value = (string)value; |
} |
break; |
case INT4RANGEOID: |
case INT8RANGEOID: |
case DATERANGEOID: |
case TSRANGEOID: |
case TSTZRANGEOID: |
if (_forcetext) |
value = cr->read(collen); |
else { |
array totype = oidtotype[typ]; |
mixed from = -Math.inf, till = Math.inf; |
switch (cr->read_int8()) { |
case 1: from = till = 0; |
break; |
case 0x12: from = cr->read_sint(cr->read_int32()); |
break; |
case 2: from = cr->read_sint(cr->read_int32()); |
case 8: till = cr->read_sint(cr->read_int32()); |
} |
if (totype[0]) { |
if (intp(from)) { |
value = totype[0](); |
value[totype[3]] = from + totype[2]; |
from = value; |
} |
if (intp(till)) { |
value = totype[0](); |
value[totype[3]] = till + totype[2]; |
till = value; |
} |
} |
value = Val.Range(from, till); |
if (alltext) |
value = value->sql(); |
} |
break; |
case CIDROID: |
case INETOID: |
if (_forcetext) |
value = cr->read(collen); |
else { |
value = Val.Inet(); |
int iptype = cr->read_int8(); |
value->masklen = cr->read_int8() + (iptype == 2 && 12*8); |
cr->read_int8(); |
value->address = cr->read_hint(1); |
if (alltext) |
value = (string)value; |
} |
break; |
case TIMESTAMPOID: |
case TIMESTAMPTZOID: |
case INTERVALOID: |
case TIMETZOID: |
case TIMEOID: |
case DATEOID: |
if (_forcetext) |
value = cr->read(collen); |
else { |
array totype = oidtotype[typ]; |
value = totype[0](); |
value[totype[3]] = cr->read_sint(totype[4]) + totype[2]; |
int i = 5; |
while (i < sizeof(totype)) { |
value[totype[i]] = cr->read_sint(totype[i+1]); |
i += 2; |
} |
if (alltext) |
value = (string)value; |
} |
break; |
case INT8OID:case INT2OID: |
case OIDOID:case INT4OID: |
if (_forcetext) { |
value = cr->read(collen); |
if (!alltext) |
value = (int)value; |
} else { |
value = cr->read_sint(collen); |
if (alltext) |
value = (string)value; |
} |
} |
a[i]=value; |
} else if (!collen) |
a[i]=""; |
} |
_processdataready(a); |
if (serror) |
error(serror); |
#ifdef PG_DEBUG |
return msglen; |
#endif |
} |
|
final void _setrowdesc(array(mapping(string:mixed)) drowdesc, |
array(int) drowtypes) { |
Thread.MutexKey lock = _ddescribemux->lock(); |
datarowdesc = drowdesc; |
datarowtypes = drowtypes; |
_ddescribe->broadcast(); |
} |
|
final void _preparebind(array dtoid) { |
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)); |
PD("PrepareBind\n"); |
Thread.MutexKey lock = _ddescribemux->lock(); |
if (!_portalname) { |
_portalname |
= (_unnamedportalkey = pgsqlsess.unnamedportalmux->trylock(1)) |
? "" : PORTALPREFIX |
#ifdef PG_DEBUG |
+ (string)(c->socket->query_fd()) + "_" |
#endif |
+ String.int2hex(pgsqlsess.pportalcount++); |
lock = 0; |
#ifdef PG_DEBUGMORE |
PD("ParamValues to bind: %O\n", paramValues); |
#endif |
Stdio.Buffer plugbuffer = Stdio.Buffer(); |
{ array dta = ({sizeof(dtoid)}); |
plugbuffer->add(_portalname, 0, _preparedname, 0) |
->add_ints(dta |
+ map(dtoid, writeoidformat, paramValues, ({0})) + dta, 2); |
} |
string cenc = pgsqlsess.runtimeparameter[CLIENT_ENCODING]; |
foreach (paramValues; int i; mixed value) { |
if (undefinedp(value) || objectp(value) && value->is_val_null) |
plugbuffer->add_int32(-1); |
else if (stringp(value) && !sizeof(value)) { |
int k = 0; |
switch (dtoid[i]) { |
default: |
k = -1; |
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: |
if (has_value(value, 0)) |
SUSERERROR("NUL characters not allowed in PG-UTF-8: %O\n", |
value); |
else |
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; |
} |
if (!objectp(value) || typeof(value) != typeof(Stdio.Buffer())) |
|
|
|
|
if (multisetp(value) && sizeof(value) == 1) |
value = indices(value)[0]; |
else { |
value = (string)value; |
if (cenc == UTF8CHARSET |
&& (dtoid[i] != BYTEAOID || String.width(value) > 8)) |
|
|
|
|
|
value = string_to_utf8(value); |
else if (String.width(value) > 8) { |
SUSERERROR( |
"Wide string %O not supported for type OID %d\n", |
value, dtoid[i]); |
value = ""; |
} |
} |
plugbuffer->add_hstring(value, 4); |
break; |
} |
case BOOLOID: |
do { |
int tval; |
if (stringp(value)) |
tval = value[0]; |
else if (!intp(value)) { |
value = !!value; |
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("\0\0\0\1", 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); |
break; |
case 1: |
plugbuffer->add_hstring(value[0], 4); |
} |
} |
break; |
case NUMERICOID: |
if (stringp(value)) |
plugbuffer->add_hstring(value, 4); |
else { |
int num, den, sign, magnitude = 0; |
value = Gmp.mpq(value); |
num = value->num(); |
den = value->den(); |
for (value = den; |
value > NUMERIC_MAGSTEP; |
value /= NUMERIC_MAGSTEP); |
if (value > 1) { |
value = NUMERIC_MAGSTEP / value; |
num *= value; |
den *= value; |
} |
if (num < 0) |
sign = 0x4000, num = -num; |
else |
sign = 0; |
array stor = ({}); |
if (num) { |
while (!(num % NUMERIC_MAGSTEP)) |
num /= NUMERIC_MAGSTEP, magnitude++; |
do |
stor = ({num % NUMERIC_MAGSTEP}) + stor, magnitude++; |
while (num /= NUMERIC_MAGSTEP); |
num = --magnitude << 2; |
while (den > 1) |
magnitude--, den /= NUMERIC_MAGSTEP; |
} |
plugbuffer->add_int32(4 * 2 + (sizeof(stor) << 1)) |
->add_int16(sizeof(stor))->add_int16(magnitude) |
->add_int16(sign)->add_int16(num)->add_ints(stor, 2); |
} |
break; |
case INT4RANGEOID: |
case INT8RANGEOID: |
case DATERANGEOID: |
case TSRANGEOID: |
case TSTZRANGEOID: |
if (stringp(value)) |
plugbuffer->add_hstring(value, 4); |
else if (value->from >= value->till) |
plugbuffer->add("\0\0\0\1\1"); |
else { |
array totype = oidtotype[dtoid[i]]; |
int w = totype[1]; |
int from, till; |
if (totype[0]) |
from = value->from, till = value->till; |
else { |
from = value->from[totype[3]] - totype[2]; |
till = value->till[totype[3]] - totype[2]; |
} |
if (value->till == Math.inf) |
if (value->from == -Math.inf) |
plugbuffer->add("\0\0\0\1\30"); |
else |
plugbuffer->add("\0\0\0", 1 + 4 + w, "\22\0\0\0", w) |
->add_int(from, w); |
else { |
if (value->from == -Math.inf) |
plugbuffer->add("\0\0\0", 1 + 4 + w, 8); |
else |
plugbuffer->add("\0\0\0", 1 + 4 * 2 + w * 2, "\2\0\0\0", w) |
->add_int(from, w); |
plugbuffer->add_int32(w)->add_int(till, w); |
} |
} |
break; |
case CIDROID: |
case INETOID: |
if (stringp(value)) |
plugbuffer->add_hstring(value, 4); |
else if (value->address <= 0xffffffff) |
plugbuffer->add("\0\0\0\10\2", |
value->masklen - 12 * 8, dtoid[i] == CIDROID, 4) |
->add_int32(value->address); |
else |
plugbuffer->add("\0\0\0\24\3", |
value->masklen, dtoid[i] == CIDROID, 16) |
->add_int(value->address, 16); |
break; |
case DATEOID: |
case TIMEOID: |
case TIMETZOID: |
case INTERVALOID: |
case TIMESTAMPOID: |
case TIMESTAMPTZOID: |
if (stringp(value)) |
plugbuffer->add_hstring(value, 4); |
else { |
array totype = oidtotype[dtoid[i]]; |
if (!objectp(value)) |
value = totype[0](value); |
plugbuffer->add_int32(totype[1]) |
->add_int(value[totype[3]] - totype[2], totype[4]); |
int i = 5; |
while (i < sizeof(totype)) { |
plugbuffer->add_int(value[totype[i]], totype[i+1]); |
i += 2; |
} |
} |
break; |
case FLOAT4OID: |
#if !constant(__builtin.__SINGLE_PRECISION_FLOAT__) |
case FLOAT8OID: |
#endif |
if (stringp(value)) |
plugbuffer->add_hstring(value, 4); |
else { |
int w = dtoid[i] == FLOAT4OID ? 4 : 8; |
plugbuffer->add_int32(w) |
->sprintf(w == 4 ? "%4F" : "%8F", (float)value); |
} |
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; |
} |
} |
if (!datarowtypes) { |
if (_tprepared && dontcacheprefix->match(_query)) |
m_delete(pgsqlsess->prepareds, _query), _tprepared = 0; |
PD("WaitForDescribe\n"); |
waitfordescribe(); |
} |
PD("About to bind %d\n", _state); |
if (_state >= CLOSING) |
lock = _unnamedstatementkey = 0; |
else { |
plugbuffer->add_int16(sizeof(datarowtypes)); |
if (sizeof(datarowtypes)) { |
plugbuffer->add_ints(map(datarowtypes, readoidformat), 2); |
lock = 0; |
} else if (syncparse < 0 && !pgsqlsess->wasparallelisable |
&& !pgsqlsess->statementsinflight->drained(1)) { |
lock = 0; |
PD("Commit waiting for statements to finish\n"); |
catch(PT(pgsqlsess->statementsinflight->wait_till_drained(1))); |
} |
PD("Bind portal %O statement %O\n", _portalname, _preparedname); |
_fetchlimit = pgsqlsess->_fetchlimit; |
_bindportal(); |
conxsess bindbuffer = c->start(); |
stmtifkey = 0; |
CHAIN(bindbuffer)->add_int8('B')->add_hstring(plugbuffer, 4, 4); |
if (!_tprepared && sizeof(_preparedname)) |
closestatement(CHAIN(bindbuffer), _preparedname); |
_sendexecute(_fetchlimit |
&& !(transtype != NOTRANS |
|| sizeof(_query) >= MINPREPARELENGTH && |
execfetchlimit->match(_query)) |
&& _fetchlimit, bindbuffer); |
_unnamedstatementkey = 0; |
} |
} |
} |
|
final void _processrowdesc(array(mapping(string:mixed)) datarowdesc, |
array(int) datarowtypes) { |
_setrowdesc(datarowdesc, datarowtypes); |
if (_tprepared) { |
_tprepared.datarowdesc = datarowdesc; |
_tprepared.datarowtypes = datarowtypes; |
} |
} |
|
final void _parseportal() { |
for (;;) { |
Thread.MutexKey lock = closemux->lock(); |
if ((syncparse || syncparse < 0 && pgsqlsess->wasparallelisable) |
&& !pgsqlsess->statementsinflight->drained()) { |
lock = 0; |
PD("Commit waiting for statements to finish %d\n", |
pgsqlsess->statementsinflight->_count); |
catch(PT(pgsqlsess->statementsinflight->wait_till_drained())); |
PD("Parseportal retry closemux %O\n", _portalname); |
continue; |
} |
_state = PARSING; |
stmtifkey = pgsqlsess->statementsinflight->acquire(); |
break; |
} |
statuscmdcomplete = 0; |
pgsqlsess->wasparallelisable = paralleliseprefix->match(_query); |
} |
|
final void _releasestatement() { |
Thread.MutexKey lock = closemux->lock(); |
if (_state <= BOUND) { |
stmtifkey = 0; |
_state = COMMITTED; |
} |
} |
|
final void _bindportal() { |
Thread.MutexKey lock = closemux->lock(); |
_state = BOUND; |
portalsifkey = pgsqlsess->portalsinflight->acquire(); |
} |
|
final void _purgeportal() { |
PD("Purge portal\n"); |
datarows->write(1); |
{ |
Thread.MutexKey lock = closemux->lock(); |
_fetchlimit = 0; |
stmtifkey = portalsifkey = 0; |
_state = PURGED; |
} |
releaseconditions(); |
} |
|
final int _closeportal(conxsess cs, array(Thread.MutexKey) reflock) { |
void|bufcon|conxsess plugbuffer = CHAIN(cs); |
int retval = KEEP; |
PD("%O Try Closeportal %d\n", _portalname, _state); |
_fetchlimit = 0; |
stmtifkey = 0; |
switch (_state) { |
case PORTALINIT: |
case PARSING: |
_unnamedstatementkey = 0; |
_state = CLOSING; |
break; |
case COPYINPROGRESS: |
PD("CopyDone\n"); |
plugbuffer->add("c\0\0\0\4"); |
case COMMITTED: |
case BOUND: |
_state = CLOSING; |
if (reflock) |
reflock[0] = 0; |
PD("Close portal %O\n", _portalname); |
if (_portalname && sizeof(_portalname)) { |
plugbuffer->add_int8('C') |
->add_hstring(({'P', _portalname, 0}), 4, 4); |
retval = FLUSHSEND; |
} else |
_unnamedportalkey = 0; |
portalsifkey = 0; |
if (pgsqlsess->portalsinflight->drained()) { |
if (plugbuffer->stashcount->drained() && transtype != TRANSBEGIN) { |
|
|
|
|
Thread.MutexKey lock = plugbuffer->shortmux->lock(); |
if (plugbuffer->stashcount->drained()) |
pgsqlsess->readyforquerycount++, retval = SYNCSEND; |
} |
pgsqlsess->pportalcount = 0; |
} |
} |
return retval; |
} |
|
private void replenishrows() { |
if (_fetchlimit && datarows->size() <= _fetchlimit >> 1 |
&& _state >= COMMITTED) { |
Thread.MutexKey lock; |
for (;;) { |
lock = closemux->lock(); |
if (_fetchlimit) { |
_fetchlimit = pgsqlsess._fetchlimit; |
if (bytesreceived) |
_fetchlimit = min((portalbuffersize >> 1) |
* index / bytesreceived || 1, _fetchlimit); |
if (_fetchlimit) |
if (inflight <= (_fetchlimit - 1) >> 1) { |
array(Thread.MutexKey) reflock = ({ lock }); |
lock = 0; |
if (!_sendexecute(_fetchlimit, reflock)) { |
PD("Replenishrows retry closemux %O\n", _portalname); |
continue; |
} |
} else |
PD("<%O _fetchlimit %d, inflight %d, skip execute\n", |
_portalname, _fetchlimit, inflight); |
} |
break; |
} |
} |
} |
|
final void _processdataready(array datarow, void|int msglen) { |
bytesreceived += msglen; |
inflight--; |
if (_state < CLOSED) |
datarows->write(datarow); |
if (++index == 1) |
PD("<%O _fetchlimit %d=min(%d||1,%d), inflight %d\n", _portalname, |
_fetchlimit, (portalbuffersize >> 1) * index / bytesreceived, |
pgsqlsess._fetchlimit, inflight); |
replenishrows(); |
} |
|
private void releaseconditions(void|int aborted) { |
_unnamedportalkey = _unnamedstatementkey = 0; |
if (!datarowtypes) { |
if (_state != PURGED && !delayederror) |
delayederror = LOSTERROR; |
datarowtypes = ({}); |
_ddescribe->broadcast(); |
} |
if (aborted && delayederror && pgsqlsess && !pgsqlsess.delayederror) |
pgsqlsess.delayederror = delayederror; |
pgsqlsess = 0; |
} |
|
final void _releasesession(void|string statusccomplete) { |
int aborted = statusccomplete == "ABORT" ? 2 : 0; |
c->runningportals[this] = 0; |
if (statusccomplete && !statuscmdcomplete) { |
Thread.MutexKey lock = _ddescribemux->lock(); |
statuscmdcomplete = statusccomplete; |
_ddescribe->broadcast(); |
} |
inflight = 0; |
conxsess plugbuffer; |
array(Thread.MutexKey) reflock = ({ 0 }); |
for (;;) { |
reflock[0] = closemux->lock(aborted); |
if (!catch(plugbuffer = c->start(reflock))) { |
if (!this) |
return; |
if (plugbuffer) |
plugbuffer->sendcmd(_closeportal(plugbuffer, reflock)); |
else { |
PD("Releasesession retry closemux %O\n", _portalname); |
continue; |
} |
} |
break; |
} |
reflock[0] = 0; |
if (_state < CLOSED) { |
stmtifkey = 0; |
_state = CLOSED; |
} |
datarows->write(1); |
releaseconditions(aborted); |
} |
|
protected void _destruct() { |
if (_state < CLOSED) |
catch { |
_releasesession("ABORT"); |
}; |
} |
|
final int _sendexecute(int fetchlimit, |
void|array(Thread.MutexKey)|bufcon|conxsess plugbuffer) { |
int flushmode; |
array(Thread.MutexKey) reflock; |
PD("Execute portal %O fetchlimit %d transtype %d\n", _portalname, |
fetchlimit, transtype); |
if (arrayp(plugbuffer)) { |
reflock = plugbuffer; |
if (!(plugbuffer = c->start(reflock))) |
return 0; |
} |
CHAIN(plugbuffer)->add_int8('E')->add_hstring(({_portalname, 0}), 4, 8) |
->add_int32(fetchlimit); |
if (!fetchlimit) { |
if (transtype != NOTRANS) |
pgsqlsess.intransaction = transtype == TRANSBEGIN; |
flushmode = _closeportal(plugbuffer, reflock) == SYNCSEND |
|| transtype == TRANSEND ? SYNCSEND : FLUSHSEND; |
} else |
inflight += fetchlimit, flushmode = FLUSHSEND; |
plugbuffer->sendcmd(flushmode, this); |
if (reflock) |
reflock[0] = 0; |
return 1; |
} |
|
inline private array setuptimeout() { |
return local_backend->call_out(gottimeout, timeout); |
} |
|
inline private void scuttletimeout(array cid) { |
if (local_backend) |
local_backend->remove_call_out(cid); |
} |
|
|
|
|
|
|
|
|
|
final array(mixed) fetch_row() { |
int|array datarow; |
if (!this) |
return 0; |
replenishrows(); |
if (arrayp(datarow = datarows->try_read())) |
return datarow; |
if (!eoffound) { |
if (!datarow) { |
PD("%O Block for datarow\n", _portalname); |
array cid = setuptimeout(); |
PT(datarow = datarows->read()); |
if (!this) |
return 0; |
scuttletimeout(cid); |
if (arrayp(datarow)) |
return datarow; |
} |
eoffound = 1; |
datarows->write(1); |
} |
trydelayederror(); |
return 0; |
} |
|
|
|
|
|
|
|
|
|
final array(array(mixed)) fetch_row_array() { |
if (eoffound) |
return 0; |
replenishrows(); |
array(array|int) datarow = datarows->try_read_array(); |
if (!sizeof(datarow)) { |
array cid = setuptimeout(); |
PT(datarow = datarows->read_array()); |
if (!this) |
return 0; |
scuttletimeout(cid); |
} |
replenishrows(); |
if (arrayp(datarow[-1])) |
return datarow; |
do datarow = datarow[..<1]; |
while (sizeof(datarow) && !arrayp(datarow[-1])); |
trydelayederror(); |
eoffound = 1; |
datarows->write(1); |
return datarow; |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
final void send_row(void|string|array(string) copydata) { |
trydelayederror(); |
if (copydata) { |
PD("CopyData\n"); |
void|bufcon|conxsess cs = c->start(); |
CHAIN(cs)->add_int8('d')->add_hstring(copydata, 4, 4); |
cs->sendcmd(SENDOUT); |
} else |
_releasesession(); |
} |
|
private void run_result_cb( |
function(Result, array(mixed), mixed ...:void) callback, |
array(mixed) args) { |
int|array datarow; |
for (;;) { |
array cid = setuptimeout(); |
PT(datarow = datarows->read()); |
if (!this) |
return 0; |
scuttletimeout(cid); |
if (!arrayp(datarow)) |
break; |
callout(callback, 0, this, datarow, @args); |
} |
eoffound = 1; |
callout(callback, 0, this, 0, @args); |
} |
|
|
|
|
|
|
|
final void set_result_callback( |
function(Result, array(mixed), mixed ...:void) callback, |
mixed ... args) { |
if (callback) |
Thread.Thread(run_result_cb, callback, args); |
} |
|
private void run_result_array_cb( |
function(Result, array(array(mixed)), mixed ...:void) callback, |
array(mixed) args) { |
array(array|int) datarow; |
for (;;) { |
array cid = setuptimeout(); |
PT(datarow = datarows->read_array()); |
if (!this) |
return 0; |
scuttletimeout(cid); |
if (!datarow || !arrayp(datarow[-1])) |
break; |
callout(callback, 0, this, datarow, @args); |
} |
eoffound = 1; |
if (sizeof(datarow)>1) |
callout(callback, 0, this, datarow = datarow[..<1], @args); |
callout(callback, 0, this, 0, @args); |
} |
|
|
|
|
|
|
|
final void set_result_array_callback( |
function(Result, array(array(mixed)), mixed ...:void) callback, |
mixed ... args) { |
if (callback) |
Thread.Thread(run_result_array_cb, callback, args); |
} |
|
}; |
|
class proxy { |
final int _fetchlimit = FETCHLIMIT; |
final MUTEX unnamedportalmux; |
final MUTEX unnamedstatement; |
private Thread.MutexKey|int termlock; |
final Thread.ResourceCount portalsinflight, statementsinflight; |
final int(0..1) wasparallelisable; |
final int(0..1) intransaction; |
|
final conxion c; |
private string cancelsecret; |
private int backendpid; |
final int(-128..127) backendstatus; |
final mapping(string:mixed) options; |
private array(string) lastmessage = ({}); |
final int(0..1) clearmessage; |
final int(0..1) untolderror; |
final mixed delayederror; |
private mapping(string:array(mixed)) notifylist = ([]); |
final mapping(string:string) runtimeparameter; |
final mapping(string:mapping(string:mixed)) prepareds = ([]); |
final int pportalcount; |
final int totalhits; |
final int msgsreceived; |
final int bytesreceived; |
final int warningsdropcount; |
private int warningscollected; |
final int(0..1) invalidatecache; |
private Thread.Queue qportals; |
final function (:void) readyforquery_cb; |
|
final string host; |
final int(0..65535) port; |
final string database, user, pass; |
private Crypto.Hash.SCRAM SASLcontext; |
final Thread.Condition waitforauthready; |
final MUTEX shortmux; |
final int readyforquerycount; |
|
protected string _sprintf(int type) { |
string res; |
if (!this) |
return "(destructed)"; |
switch (type) { |
case 'O': |
res = sprintf(DRIVERNAME".proxy(%s@%s:%d/%s,%d,%d)", |
user, host, port, database, c && c->socket && c->socket->query_fd(), |
backendpid); |
break; |
} |
return res; |
} |
|
protected void create(void|string host, void|string database, |
void|string user, void|string pass, |
void|mapping(string:mixed) options) { |
if (this::pass = 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; |
register_backend(this); |
shortmux = MUTEX(); |
PD("Connect\n"); |
waitforauthready = Thread.Condition(); |
qportals = Thread.Queue(); |
readyforquerycount = 1; |
qportals->write(1); |
if (!(c = conxion(this, qportals, 0))) |
error("Couldn't connect to database on %s:%d\n", host, port); |
runtimeparameter = ([]); |
unnamedportalmux = MUTEX(); |
unnamedstatement = MUTEX(); |
readyforquery_cb = connect_cb; |
portalsinflight = Thread.ResourceCount(); |
statementsinflight = Thread.ResourceCount(); |
wasparallelisable = 0; |
} |
|
final int is_open() { |
return c && c->socket && c->socket->is_open(); |
} |
|
final string geterror(void|int clear) { |
untolderror = 0; |
string s = lastmessage * "\n"; |
if (clear) |
lastmessage = ({}); |
warningscollected = 0; |
return sizeof(s) && s; |
} |
|
final string host_info() { |
return sprintf("fd:%d TCP/IP %s:%d PID %d", |
c ? c->socket->query_fd() : -1, host, port, backendpid); |
} |
|
final void cancelquery() { |
if (cancelsecret > "") { |
PD("CancelRequest\n"); |
conxion lcon = conxion(this, 0, 2); |
#ifdef PG_DEBUG |
mixed err = |
#endif |
catch(lcon->add_int32(16)->add_int32(PG_PROTOCOL(1234, 5678)) |
->add_int32(backendpid)->add(cancelsecret)->sendcmd(FLUSHSEND)); |
#ifdef PG_DEBUG |
if (err) |
PD("CancelRequest failed to connect %s\n", describe_backtrace(err)); |
#endif |
destruct(lcon); |
#ifdef PG_DEBUGMORE |
PD("Closetrace %O\n", backtrace()); |
#endif |
} else |
error("Connection not established, cannot cancel any query\n"); |
} |
|
private string a2nls(array(string) msg) { |
return msg * "\n" + "\n"; |
} |
|
private 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; |
} |
|
private void connect_cb() { |
PD("%O\n", runtimeparameter); |
} |
|
private array(string) showbindings(Result portal) { |
return portal ? portal._showbindings() : ({}); |
} |
|
private 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||"")}); |
} |
|
private int|Result portal; |
#ifdef PG_DEBUG |
private string datarowdebug; |
private int datarowdebugcount; |
#endif |
|
final void processloop(conxion ci) { |
(c = ci)->socket->set_id(procmessage); |
cancelsecret = 0; |
portal = 0; |
{ |
Stdio.Buffer plugbuffer = Stdio.Buffer()->add_int32(PG_PROTOCOL(3, 0)); |
if (user) |
plugbuffer->add("user\0", user, 0); |
if (database) |
plugbuffer->add("database\0", database, 0); |
foreach (options - censoroptions; string name; mixed value) |
plugbuffer->add(name, 0, (string)value, 0); |
plugbuffer->add_int8(0); |
PD("%O\n", (string)plugbuffer); |
void|bufcon|conxsess cs; |
if (catch(cs = ci->start())) { |
if (this) { |
destruct(waitforauthready); |
unnamedstatement = 0; |
termlock = 1; |
} |
return; |
} else { |
CHAIN(cs)->add_hstring(plugbuffer, 4, 4); |
cs->sendcmd(SENDOUT); |
} |
} |
procmessage(); |
} |
|
private void stasherror(int|object portal, mixed err) { |
if (stringp(err)) { |
if (!objectp(portal)) |
portal = this; |
if (!portal->delayederror) |
portal->delayederror = err; |
} |
if (objectp(portal) && portal->_purgeportal) |
portal->_purgeportal(); |
} |
|
private void tryprepbind(Result portal, array dtoid) { |
mixed err = catch(portal->_preparebind(dtoid)); |
if (err) { |
stasherror(portal, err); |
if (!stringp(err)) |
throw(err); |
} |
} |
|
private void procmessage() { |
mixed err; |
int terminating = 0; |
err = catch { |
conxion ci = c; |
conxiin cr = ci->i; |
#ifdef PG_DEBUG |
PD("Processloop\n"); |
|
#ifdef PG_DEBUGMORE |
void showportalstack(string label) { |
PD(sprintf(">>>>>>>>>>> Portalstack %s: %O\n", label, portal)); |
foreach (qportals->peek_array(); ; int|Result qp) |
PD(" =========== Portal: %O\n", qp); |
PD("<<<<<<<<<<<<<< Portalstack end\n"); |
}; |
#endif |
void showportal(int msgtype) { |
if (objectp(portal)) |
PD("%d<%O %d %c switch portal\n", |
ci->socket->query_fd(), portal._portalname, ++ci->queueinidx, msgtype); |
else if (portal > 0) |
PD("%d<Sync %d %d %c portal\n", |
ci->socket->query_fd(), ++ci->queueinidx, portal, msgtype); |
}; |
#endif |
int msgisfatal(mapping(string:string) msgresponse) { |
int isfatal = (has_prefix(msgresponse.C, "53") |
|| has_prefix(msgresponse.C, "3D") |
|| has_prefix(msgresponse.C, "57P")) && MAGICTERMINATE; |
if (isfatal && !terminating) |
runcallback(backendpid, "_lost", ""); |
return isfatal; |
}; |
for (;;) { |
err = catch { |
#ifdef PG_DEBUG |
if (!portal && datarowdebug) { |
PD("%s rows %d\n", datarowdebug, datarowdebugcount); |
datarowdebug = 0; datarowdebugcount = 0; |
} |
#endif |
#ifdef PG_DEBUGMORE |
showportalstack("LOOPTOP"); |
#endif |
if (!sizeof(cr)) { |
Thread.MutexKey lock = cr->fillreadmux->lock(); |
if (!sizeof(cr)) { |
if (!cr->fillread) { |
lock = 0; |
throw(MAGICTERMINATE); |
} |
cr->procmsg = 1; |
return; |
} |
} |
int msgtype = cr->read_int8(); |
if (!portal) { |
portal = qportals->try_read(); |
#ifdef PG_DEBUG |
showportal(msgtype); |
#endif |
} |
int msglen = cr->read_int32(); |
msgsreceived++; |
bytesreceived += 1 + msglen; |
int errtype = NOERROR; |
PD("%d<", ci->socket->query_fd()); |
switch (msgtype) { |
array getcols() { |
int bintext = cr->read_int8(); |
int cols = cr->read_int16(); |
#ifdef PG_DEBUG |
array a; |
msglen -= 4 + 1 + 2 + 2 * cols; |
foreach (a = allocate(cols, ([])); ; mapping m) |
m.type = cr->read_int16(); |
#else |
cr->consume(cols << 1); |
#endif // Discard column info, and make it line oriented |
return ({ ({(["name":"line"])}), ({bintext?BYTEAOID:TEXTOID}) }); |
}; |
array(string) reads() { |
#ifdef PG_DEBUG |
if (msglen < 1) |
errtype = PROTOCOLERROR; |
#endif |
array ret = ({}), aw = ({0}); |
do { |
string w = cr->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': { |
void authresponse(string|array msg) { |
void|bufcon|conxsess cs = ci->start(); |
CHAIN(cs)->add_int8('p')->add_hstring(msg, 4, 4); |
cs->sendcmd(SENDOUT); |
}; |
PD("Authentication "); |
msglen -= 4 + 4; |
int authtype, k; |
switch (authtype = cr->read_int32()) { |
case 0: |
PD("Ok\n"); |
if (SASLcontext) { |
PD("Authentication validation still in progress\n"); |
errtype = PROTOCOLUNSUPPORTED; |
} else |
cancelsecret = ""; |
break; |
case 2: |
PD("KerberosV5\n"); |
errtype = PROTOCOLUNSUPPORTED; |
break; |
case 3: |
PD("ClearTextPassword\n"); |
authresponse(({pass, 0})); |
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)) |
authresponse(({"md5", |
md5hex(md5hex(pass + user) + cr->read(msglen)), 0})); |
#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; |
cr->read(msglen); |
#ifdef PG_DEBUG |
if (msglen<1) |
errtype = PROTOCOLERROR; |
msglen = 0; |
#endif |
break; |
case 10: { |
string word; |
PD("AuthenticationSASL\n"); |
k = 0; |
while (sizeof(word = cr->read_cstring())) { |
switch (word) { |
case "SCRAM-SHA-256": |
k = 1; |
} |
#ifdef PG_DEBUG |
msglen -= sizeof(word) + 1; |
if (msglen < 1) |
break; |
#endif |
} |
if (k) { |
SASLcontext = Crypto.SHA256.SCRAM(); |
word = SASLcontext.client_1(); |
authresponse(({ |
"SCRAM-SHA-256", 0, sprintf("%4c", sizeof(word)), word |
})); |
} else |
errtype = PROTOCOLUNSUPPORTED; |
#ifdef PG_DEBUG |
if (msglen != 1) |
errtype = PROTOCOLERROR; |
msglen = 0; |
#endif |
break; |
} |
case 11: { |
PD("AuthenticationSASLContinue\n"); |
string response; |
if (response |
= SASLcontext.client_2(cr->read(msglen), pass)) |
authresponse(response); |
else |
errtype = PROTOCOLERROR; |
#ifdef PG_DEBUG |
msglen = 0; |
#endif |
break; |
} |
case 12: |
PD("AuthenticationSASLFinal\n"); |
if (SASLcontext.client_3(cr->read(msglen))) |
SASLcontext = 0; |
else |
errtype = PROTOCOLERROR; |
#ifdef PG_DEBUG |
msglen = 0; |
#endif |
break; |
default: |
PD("Unknown Authentication Method %c\n", authtype); |
errtype = PROTOCOLUNSUPPORTED; |
break; |
} |
switch (errtype) { |
default: |
case PROTOCOLUNSUPPORTED: |
error("Unsupported authenticationmethod %c\n", authtype); |
case NOERROR: |
break; |
} |
break; |
} |
case 'K': |
msglen -= 4 + 4; backendpid = cr->read_int32(); |
cancelsecret = cr->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 = cr->read_int8(); |
#ifdef PG_DEBUG |
msglen -= 4 + 1; |
PD("ReadyForQuery %c\n", backendstatus); |
#endif |
#ifdef PG_DEBUGMORE |
showportalstack("READYFORQUERY"); |
#endif |
int keeplooking; |
do |
for (keeplooking = 0; |
objectp(portal); |
portal = qportals->read()) { |
#ifdef PG_DEBUG |
showportal(msgtype); |
#endif |
if (backendstatus == 'I' && intransaction |
&& portal->transtype != TRANSEND) |
keeplooking = 1; |
portal->_purgeportal(); |
} |
while (keeplooking && (portal = qportals->read())); |
if (backendstatus == 'I') |
intransaction = 0; |
foreach (qportals->peek_array(); ; 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; |
#ifdef PG_DEBUGMORE |
showportalstack("AFTER READYFORQUERY"); |
#endif |
readyforquerycount--; |
function (:void) cb; |
destruct(waitforauthready); |
if (cb = readyforquery_cb) |
readyforquery_cb = 0, cb(); |
break; |
} |
case '1': |
#ifdef PG_DEBUG |
PD("ParseComplete portal %O\n", portal); |
msglen -= 4; |
#endif |
break; |
case 't': { |
array a; |
#ifdef PG_DEBUG |
int cols = cr->read_int16(); |
PD("%O ParameterDescription %d values\n", portal._query, cols); |
msglen -= 4 + 2 + 4 * cols; |
a = cr->read_ints(cols, 4); |
#else |
a = cr->read_ints(cr->read_int16(), 4); |
#endif |
#ifdef PG_DEBUGMORE |
PD("%O\n", a); |
#endif |
if (portal._tprepared) |
portal._tprepared.datatypeoid = a; |
Thread.Thread(tryprepbind, portal, a); |
break; |
} |
case 'T': { |
array a, at; |
int cols = cr->read_int16(); |
#ifdef PG_DEBUG |
PD("RowDescription %d columns %O\n", cols, portal._query); |
msglen -= 4 + 2; |
#endif |
at = allocate(cols); |
foreach (a = allocate(cols); int i; ) { |
string s = cr->read_cstring(); |
mapping(string:mixed) res = (["name":s]); |
#ifdef PG_DEBUG |
msglen -= sizeof(s) + 1 + 4 + 2 + 4 + 2 + 4 + 2; |
res.tableoid = cr->read_int32() || UNDEFINED; |
res.tablecolattr = cr->read_int16() || UNDEFINED; |
#else |
cr->consume(6); |
#endif |
at[i] = cr->read_int32(); |
#ifdef PG_DEBUG |
res.type = at[i]; |
{ |
int len = cr->read_sint(2); |
res.length = len >= 0 ? len : "variable"; |
} |
res.atttypmod = cr->read_int32(); |
|
|
|
|
res.formatcode = cr->read_int16(); |
#else |
cr->consume(8); |
#endif |
a[i] = res; |
} |
#ifdef PG_DEBUGMORE |
PD("%O\n", a); |
#endif |
if (portal._forcetext) |
portal->_setrowdesc(a, at); |
else { |
portal->_processrowdesc(a, at); |
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 %O\n", 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)) { |
PD("Finalising stored statement %s\n", |
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; |
#ifdef PG_DEBUG |
#ifdef PG_DEBUGMORE |
PD("%O DataRow %d bytes\n", portal._portalname, msglen); |
#endif |
datarowdebugcount++; |
if (!datarowdebug) |
datarowdebug = sprintf( |
"%O DataRow %d bytes", portal._portalname, msglen); |
#endif |
#ifdef PG_DEBUG |
msglen= |
#endif |
portal->_decodedata(msglen, runtimeparameter[CLIENT_ENCODING]); |
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 = cr->read(msglen - 1); |
PD("%O CommandComplete %O\n", |
objectp(portal) && portal._portalname, s); |
#ifdef PG_DEBUG |
if (cr->read_int8()) |
errtype = PROTOCOLERROR; |
msglen = 0; |
#else |
cr->consume(1); |
#endif |
#ifdef PG_DEBUGMORE |
showportalstack("COMMANDCOMPLETE"); |
#endif |
if (!portal._forcetext) { |
portal->_storetiming(); |
portal->_releasesession(s); |
portal = 0; |
} |
break; |
} |
case 'I': |
#ifdef PG_DEBUG |
PD("EmptyQueryResponse %O\n", portal._portalname); |
msglen -= 4; |
#endif |
#ifdef PG_DEBUGMORE |
showportalstack("EMPTYQUERYRESPONSE"); |
#endif |
if (!portal._forcetext) { |
portal->_releasesession(); |
portal = 0; |
} |
break; |
case 'd': |
PD("%O CopyData\n", portal._portalname); |
portal->_storetiming(); |
msglen -= 4; |
#ifdef PG_DEBUG |
if (msglen < 0) |
errtype = PROTOCOLERROR; |
#endif |
portal->_processdataready(({cr->read(msglen)}), msglen); |
#ifdef PG_DEBUG |
msglen = 0; |
#endif |
break; |
case 'G': |
portal->_releasestatement(); |
portal->_setrowdesc(@getcols()); |
PD("%O CopyInResponse\n", portal._portalname); |
portal._state = COPYINPROGRESS; |
break; |
case 'c': |
#ifdef PG_DEBUG |
PD("%O CopyDone\n", portal._portalname); |
msglen -= 4; |
#endif |
if (!portal._forcetext) |
portal = 0; |
break; |
case 'E': { |
#ifdef PG_DEBUGMORE |
showportalstack("ERRORRESPONSE"); |
#endif |
if (portalsinflight->drained() && !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; |
untolderror = 1; |
switch (msgresponse.C) { |
case "P0001": |
lastmessage |
= ({sprintf("%s: %s", msgresponse.S, msgresponse.M)}); |
USERERROR(a2nls(lastmessage |
+({pinpointerror(portal._query, msgresponse.P)}) |
+showbindings(portal))); |
case "53000":case "53100":case "53200":case "53300":case "53400": |
case "57P01":case "57P02":case "57P03":case "57P04":case "3D000": |
case "34000":case "08P01": |
preplastmessage(msgresponse); |
PD(a2nls(lastmessage)); throw(msgisfatal(msgresponse)); |
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)); |
} |
case "25P02": |
USERERROR(a2nls(lastmessage)); |
} |
break; |
} |
case 'N': { |
PD("NoticeResponse\n"); |
mapping(string:string) msgresponse = getresponse(); |
switch (msgresponse.C) { |
default: |
if (clearmessage) { |
warningsdropcount += warningscollected; |
clearmessage = warningscollected = 0; |
lastmessage = ({}); |
} |
warningscollected++; |
lastmessage = ({sprintf("%s %s: %s", |
msgresponse.S, msgresponse.C, msgresponse.M)}); |
int val; |
if (val = msgisfatal(msgresponse)) { |
preplastmessage(msgresponse); |
PD(a2nls(lastmessage)); throw(val); |
} |
case "25P01": |
break; |
} |
break; |
} |
case 'A': { |
PD("NotificationResponse\n"); |
msglen -= 4 + 4; |
int pid = cr->read_int32(); |
string condition, extrainfo; |
{ |
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 = cr->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)}); |
runcallback(backendpid, "_lost", ""); |
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) { |
catch { |
void|bufcon|conxsess cs = ci->start(); |
CHAIN(cs)->add(PGSYNC)->add("X\0\0\0\4"); |
cs->sendcmd(SENDOUT); |
}; |
terminating = 1; |
err = 0; |
} else if (stringp(err)) { |
#ifdef PG_DEBUGMORE |
showportalstack("THROWN"); |
#endif |
stasherror(portal, err); |
portal = 0; |
if (!waitforauthready) |
continue; |
} |
break; |
} |
PD("Closing database processloop %s\n", err ? describe_backtrace(err) : ""); |
delayederror = err; |
if (objectp(portal)) { |
#ifdef PG_DEBUG |
showportal(0); |
#endif |
portal->_purgeportal(); |
} |
destruct(waitforauthready); |
termlock = 1; |
if (err && !stringp(err)) |
throw(err); |
}; |
catch { |
unnamedstatement = 0; |
termlock = 1; |
if (err) { |
PD("Terminating processloop due to %s\n", describe_backtrace(err)); |
delayederror = err; |
} |
destruct(waitforauthready); |
c->purge(); |
}; |
} |
|
final void close() { |
throwdelayederror(this); |
{ |
Thread.MutexKey lock; |
if (unnamedstatement && !termlock) |
termlock = unnamedstatement->lock(1); |
foreach (c->runningportals; Result result; ) |
if (result->_state < CLOSED) |
catch(result->status_command_complete()); |
if (c) |
c->close(); |
if (unnamedstatement) |
lock = unnamedstatement->lock(1); |
if (c) |
c->purge(); |
} |
destruct(waitforauthready); |
} |
|
protected void _destruct() { |
string errstring; |
mixed err = catch(close()); |
clients[this] = 0; |
if (untolderror) { |
|
|
|
|
|
catch { |
lastmessage = filter(lastmessage, lambda(string val) { |
return has_prefix(val, "ERROR ") || has_prefix(val, "FATAL "); }); |
if (err || (err = catch(errstring = geterror(1)))) |
werror(describe_backtrace(err)); |
else if (errstring && sizeof(errstring)) |
werror("%s\n", errstring); |
}; |
} |
} |
|
final void sendsync() { |
readyforquerycount++; |
c->start()->sendcmd(SYNCSEND); |
} |
|
private void runcallback(int pid, string condition, string extrainfo) { |
array cb; |
if (condition == "_lost") |
destruct(c); |
if ((cb = notifylist[condition] || notifylist[""]) |
&& (pid != backendpid || sizeof(cb) > 1 && cb[1])) |
callout(cb[0], 0, pid, condition, extrainfo, @cb[2..]); |
} |
|
private inline void closestatement( |
bufcon|conxsess plugbuffer, string oldprep) { |
closestatement(plugbuffer, oldprep); |
} |
}; |
|
#pragma deprecation_warnings |
|
|
|
|
|
|
__deprecated__(program(Result)) sql_result = |
(__deprecated__(program(Result)))Result; |
|
|