|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pike __REAL_VERSION__ |
#require constant(Thread.Thread) |
|
#include "pgsql.h" |
|
#define ERROR(X ...) predef::error(X) |
|
private .pgsql_util.proxy proxy; |
|
private int pstmtcount; |
private int ptstmtcount; |
|
|
#ifdef PG_STATS |
private int skippeddescribe; |
private int portalsopened; |
private int prepstmtused; |
#endif |
private int cachedepth = STATEMENTCACHEDEPTH; |
private int portalbuffersize = PORTALBUFFERSIZE; |
private int timeout = QUERYTIMEOUT; |
private array connparmcache; |
private int reconnected; |
|
protected string _sprintf(int type) { |
string res; |
switch(type) { |
case 'O': |
res = sprintf(DRIVERNAME"(%s@%s:%d/%s,%d,%d)", |
proxy.user, proxy.host, proxy.port, proxy.database, |
proxy.c?->socket && proxy.c->socket->query_fd(), proxy.backendpid); |
break; |
} |
return res; |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected void create(void|string host, void|string database, |
void|string user, void|string pass, |
void|mapping(string:mixed) options) { |
string spass = pass && pass != "" ? Standards.IDNA.to_ascii(pass) : pass; |
if(pass) { |
String.secure(pass); |
pass = "CENSORED"; |
} |
connparmcache = ({ host, database, |
user && user != "" ? Standards.IDNA.to_ascii(user, 1) : user, |
spass, options || ([])}); |
proxy = .pgsql_util.proxy(@connparmcache); |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final string error(void|int clear) { |
throwdelayederror(proxy); |
return proxy.geterror(clear); |
} |
|
|
|
|
|
|
final string host_info() { |
return proxy.host_info(); |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
final int is_open() { |
return proxy.is_open(); |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final int ping() { |
waitauthready(); |
return is_open() |
&& !catch(proxy.c->start()->sendcmd(FLUSHSEND)) ? !!reconnected : -1; |
} |
|
|
|
|
|
|
|
|
final void cancelquery() { |
proxy.cancelquery(); |
} |
|
|
|
|
|
|
|
|
|
|
final void set_charset(string charset) { |
if(charset) |
big_query(sprintf("SET CLIENT_ENCODING TO '%s'", quote(charset))); |
} |
|
|
|
|
|
|
|
final string get_charset() { |
return getruntimeparameters()[CLIENT_ENCODING]; |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final mapping(string:string) getruntimeparameters() { |
waitauthready(); |
return proxy.runtimeparameter + ([]); |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final mapping(string:mixed) getstatistics() { |
mapping(string:mixed) stats = ([ |
"warnings_dropped":proxy.warningsdropcount, |
"current_prepared_statements":sizeof(proxy.prepareds), |
"current_prepared_statement_hits":proxy.totalhits, |
"prepared_statement_count":pstmtcount, |
#ifdef PG_STATS |
"used_prepared_statements":prepstmtused, |
"skipped_describe_count":skippeddescribe, |
"portals_opened_count":portalsopened, |
#endif |
"messages_received":proxy.msgsreceived, |
"bytes_received":proxy.bytesreceived, |
]); |
return stats; |
} |
|
|
|
|
|
|
|
|
|
final int setcachedepth(void|int newdepth) { |
int olddepth = cachedepth; |
if (!undefinedp(newdepth) && newdepth >= 0) |
cachedepth = newdepth; |
return olddepth; |
} |
|
|
|
|
|
|
|
|
|
final int settimeout(void|int newtimeout) { |
int oldtimeout = timeout; |
if (!undefinedp(newtimeout) && newtimeout > 0) |
timeout = newtimeout; |
return oldtimeout; |
} |
|
|
|
|
|
|
|
|
|
final int setportalbuffersize(void|int newportalbuffersize) { |
int oldportalbuffersize = portalbuffersize; |
if (!undefinedp(newportalbuffersize) && newportalbuffersize>0) |
portalbuffersize = newportalbuffersize; |
return oldportalbuffersize; |
} |
|
|
|
|
|
|
|
|
|
final int setfetchlimit(void|int newfetchlimit) { |
int oldfetchlimit = proxy._fetchlimit; |
if (!undefinedp(newfetchlimit) && newfetchlimit >= 0) |
proxy._fetchlimit = newfetchlimit; |
return oldfetchlimit; |
} |
|
private string glob2reg(string glob) { |
if (!glob || !sizeof(glob)) |
return "%"; |
return replace(glob, ({"*", "?", "\\", "%", "_"}), |
({"%", "_", "\\\\", "\\%", "\\_"})); |
} |
|
private void waitauthready() { |
if (proxy.waitforauthready) { |
PD("%d Wait for auth ready %O\n", |
proxy.c?->socket && proxy.c->socket->query_fd(), backtrace()[-2]); |
Thread.MutexKey lock = proxy.shortmux->lock(); |
catch(PT(proxy.waitforauthready->wait(lock))); |
PD("%d Wait for auth ready released.\n", |
proxy.c?->socket && proxy.c->socket->query_fd()); |
} |
} |
|
|
|
|
|
|
final void close() { |
proxy.close(); |
} |
|
protected void destroy() { |
destruct(proxy); |
} |
|
|
|
|
|
final void reload() { |
resync(); |
} |
|
private void reset_dbsession() { |
proxy.statementsinflight->wait_till_drained(); |
proxy.delayederror = 0; |
error(1); |
big_query("ROLLBACK"); |
big_query("RESET ALL"); |
big_query("CLOSE ALL"); |
big_query("DISCARD TEMP"); |
} |
|
private void resync_cb() { |
switch (proxy.backendstatus) { |
case 'T':case 'E': |
foreach (proxy.prepareds; ; mapping tp) { |
m_delete(tp,"datatypeoid"); |
m_delete(tp,"datarowdesc"); |
m_delete(tp,"datarowtypes"); |
} |
Thread.Thread(reset_dbsession); |
} |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final void resync() { |
mixed err; |
if (is_open()) { |
err = catch { |
PD("Statementsinflight: %d Portalsinflight: %d\n", |
proxy.statementsinflight, proxy.portalsinflight); |
if(!proxy.waitforauthready) { |
proxy.readyforquery_cb = resync_cb; |
proxy.sendsync(); |
} |
return; |
}; |
PD("%O\n", err); |
} |
if (sizeof(proxy.lastmessage)) |
ERROR(proxy.a2nls(proxy.lastmessage)); |
} |
|
|
|
|
|
|
|
|
|
final void select_db(string dbname) { |
if (proxy.database != dbname) |
ERROR("Cannot switch databases from %O to %O" |
" in an already established connection\n", |
proxy.database, dbname); |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final void set_notify_callback(string condition, |
void|function(int,string,string,mixed ...:void) notify_cb, void|int selfnotify, |
mixed ... args) { |
if (!notify_cb) |
m_delete(proxy.notifylist, condition); |
else { |
array old = proxy.notifylist[condition]; |
if (!old) |
old = ({notify_cb}); |
if (selfnotify || args) |
old += ({selfnotify}); |
if (args) |
old += args; |
proxy.notifylist[condition] = old; |
} |
} |
|
|
|
|
|
|
|
|
|
|
|
final string quote(string s) { |
waitauthready(); |
string r = proxy.runtimeparameter.standard_conforming_strings; |
if (r && r == "on") |
return replace(s, "'", "''"); |
return replace(s, ({ "'", "\\" }), ({ "''", "\\\\" }) ); |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
final string quotebinary(string s) { |
return replace(s, ({ "'", "\\", "\0" }), ({ "''", "\\\\", "\\000" }) ); |
} |
|
|
|
|
|
|
|
|
|
final void create_db(string db) { |
big_query(sprintf("CREATE DATABASE %s", db)); |
} |
|
|
|
|
|
|
|
|
|
|
|
final void drop_db(string db) { |
big_query(sprintf("DROP DATABASE %s", db)); |
} |
|
|
|
|
|
|
|
|
|
final string server_info () { |
waitauthready(); |
return DRIVERNAME"/" + (proxy.runtimeparameter.server_version || "unknown"); |
} |
|
|
|
|
|
|
final array(string) list_dbs (void|string glob) { |
array row, ret = .pgsql_util.emptyarray; |
.pgsql_util.sql_result res=big_query("SELECT d.datname " |
"FROM pg_database d " |
"WHERE d.datname ILIKE :glob " |
"ORDER BY d.datname", |
([":glob":glob2reg(glob)])); |
while(row = res->fetch_row()) |
ret += ({row[0]}); |
return ret; |
} |
|
|
|
|
|
|
|
final array(string) list_tables (void|string glob) { |
array row, ret = .pgsql_util.emptyarray; |
.pgsql_util.sql_result res = big_query( |
|
"SELECT CASE WHEN 'public'=n.nspname THEN '' ELSE n.nspname||'.' END " |
" ||c.relname AS name " |
"FROM pg_catalog.pg_class c " |
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace " |
"WHERE c.relkind IN ('r','v') AND n.nspname<>'pg_catalog' " |
" AND n.nspname !~ '^pg_toast' AND pg_catalog.pg_table_is_visible(c.oid) " |
" AND c.relname ILIKE :glob " |
" ORDER BY 1", |
([":glob":glob2reg(glob)])); |
while(row = res->fetch_row()) |
ret += ({row[0]}); |
return ret; |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final array(mapping(string:mixed)) list_fields(void|string table, |
void|string glob) { |
array row, ret = .pgsql_util.emptyarray; |
string schema; |
|
sscanf(table||"*", "%s.%s", schema, table); |
|
.pgsql_util.sql_result res = big_typed_query( |
"SELECT a.attname, a.atttypid, t.typname, a.attlen, " |
" c.relhasindex, c.relhaspkey, CAST(c.reltuples AS BIGINT) AS reltuples, " |
" (c.relpages " |
" +COALESCE( " |
" (SELECT SUM(tst.relpages) " |
" FROM pg_catalog.pg_class tst " |
" WHERE tst.relfilenode=c.reltoastrelid) " |
" ,0) " |
" )*8192::BIGINT AS datasize, " |
" (COALESCE( " |
" (SELECT SUM(pin.relpages) " |
" FROM pg_catalog.pg_index pi " |
" JOIN pg_catalog.pg_class pin ON pin.relfilenode=pi.indexrelid " |
" WHERE pi.indrelid IN (c.relfilenode,c.reltoastrelid)) " |
" ,0) " |
" )*8192::BIGINT AS indexsize, " |
" c.relisshared, t.typdefault, " |
" n.nspname, c.relname, " |
" CASE c.relkind " |
" WHEN 'r' THEN 'table' " |
" WHEN 'v' THEN 'view' " |
" WHEN 'i' THEN 'index' " |
" WHEN 'S' THEN 'sequence' " |
" WHEN 's' THEN 'special' " |
" WHEN 't' THEN 'toastable' " |
" WHEN 'c' THEN 'composite' " |
" ELSE c.relkind::TEXT END AS relkind, " |
" r.rolname " |
"FROM pg_catalog.pg_class c " |
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace " |
" JOIN pg_catalog.pg_roles r ON r.oid=c.relowner " |
" JOIN pg_catalog.pg_attribute a ON c.oid=a.attrelid " |
" JOIN pg_catalog.pg_type t ON a.atttypid=t.oid " |
"WHERE c.relname ILIKE :table AND " |
" (n.nspname ILIKE :schema OR " |
" :schema IS NULL " |
" AND n.nspname<>'pg_catalog' AND n.nspname !~ '^pg_toast') " |
" AND a.attname ILIKE :glob " |
" AND (a.attnum>0 OR '*'=:realglob) " |
"ORDER BY n.nspname,c.relname,a.attnum,a.attname", |
([":schema":glob2reg(schema),":table":glob2reg(table), |
":glob":glob2reg(glob),":realglob":glob])); |
|
array colnames=res->fetch_fields(); |
{ |
mapping(string:string) renames = ([ |
"attname":"name", |
"nspname":"schema", |
"relname":"table", |
"rolname":"owner", |
"typname":"type", |
"attlen":"length", |
"typdefault":"default", |
"relisshared":"is_shared", |
"atttypid":"typeoid", |
"relkind":"kind", |
"relhasindex":"has_index", |
"relhaspkey":"has_primarykey", |
"reltuples":"rowcount", |
]); |
foreach(colnames; int i; mapping m) { |
string nf, field=m.name; |
if(nf=renames[field]) |
field=nf; |
colnames[i]=field; |
} |
} |
|
#define delifzero(m, field) if(!(m)[field]) m_delete(m, field) |
|
while(row=res->fetch_row()) { |
mapping m=mkmapping(colnames, row); |
delifzero(m,"is_shared"); |
delifzero(m,"has_index"); |
delifzero(m,"has_primarykey"); |
delifzero(m,"default"); |
ret += ({m}); |
} |
return ret; |
} |
|
private string trbackendst(int c) { |
switch(c) { |
case 'I': return "idle"; |
case 'T': return "intransaction"; |
case 'E': return "infailedtransaction"; |
} |
return ""; |
} |
|
|
|
|
|
|
|
|
|
|
|
final string status_commit() { |
return trbackendst(proxy.backendstatus); |
} |
|
private inline void closestatement( |
.pgsql_util.bufcon|.pgsql_util.conxsess plugbuffer, string oldprep) { |
.pgsql_util.closestatement(plugbuffer, oldprep); |
} |
|
private inline string int2hex(int i) { |
return String.int2hex(i); |
} |
|
private inline void throwdelayederror(object parent) { |
.pgsql_util.throwdelayederror(parent); |
} |
|
private void startquery(int forcetext, .pgsql_util.sql_result portal, string q, |
mapping(string:mixed) tp, string preparedname) { |
.pgsql_util.conxion c = proxy.c; |
if (!c && (proxy.options["reconnect"] |
|| zero_type(proxy.options["reconnect"]))) { |
sleep(BACKOFFDELAY); |
if (!proxy.c) { |
reconnected++; |
proxy = .pgsql_util.proxy(@connparmcache); |
} |
c = proxy.c; |
} |
if (forcetext) { |
portal._unnamedportalkey = proxy.unnamedportalmux->lock(1); |
portal._portalname = ""; |
portal->_parseportal(); portal->_bindportal(); |
proxy.readyforquerycount++; |
{ |
Thread.MutexKey lock = proxy.unnamedstatement->lock(1); |
.pgsql_util.conxsess cs = c->start(1); |
CHAIN(cs)->add_int8('Q')->add_hstring(({q, 0}), 4, 4); |
cs->sendcmd(FLUSHLOGSEND, portal); |
} |
PD("Simple query: %O\n", q); |
} else { |
object plugbuffer; |
portal->_parseportal(); |
if (!sizeof(preparedname) || !tp || !tp.preparedname) { |
if (!sizeof(preparedname)) |
preparedname = |
(portal._unnamedstatementkey = proxy.unnamedstatement->trylock(1)) |
? "" : PTSTMTPREFIX + int2hex(ptstmtcount++); |
PD("Parse statement %O=%O\n", preparedname, q); |
plugbuffer = c->start(); |
CHAIN(plugbuffer)->add_int8('P') |
->add_hstring(({preparedname, 0, q, "\0\0\0"}), 4, 4) |
#if 0 |
|
|
|
|
|
->add(PGFLUSH) |
#endif |
; |
} else { |
preparedname = tp.preparedname; |
PD("Using prepared statement %s for %O\n", preparedname, q); |
} |
portal._preparedname = preparedname; |
if (!tp || !tp.datatypeoid) { |
PD("Describe statement %O\n", preparedname); |
if (!plugbuffer) |
plugbuffer = c->start(); |
CHAIN(plugbuffer)->add_int8('D') |
->add_hstring(({'S', preparedname, 0}), 4, 4); |
plugbuffer->sendcmd(FLUSHSEND, portal); |
} else { |
if (plugbuffer) |
plugbuffer->sendcmd(KEEP); |
#ifdef PG_STATS |
skippeddescribe++; |
#endif |
portal->_setrowdesc(tp.datarowdesc, tp.datarowtypes); |
} |
if ((portal._tprepared=tp) && tp.datatypeoid) { |
mixed e = catch(portal->_preparebind(tp.datatypeoid)); |
if (e && !portal.delayederror) { |
portal._unnamedstatementkey = 0; |
throw(e); |
} |
} |
if (!proxy.unnamedstatement) |
portal._unnamedstatementkey = 0; |
} |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final .pgsql_util.sql_result big_query(string q, |
void|mapping(string|int:mixed) bindings, |
void|int _alltyped) { |
throwdelayederror(proxy); |
string preparedname = ""; |
mapping(string:mixed) options = proxy.options; |
.pgsql_util.conxion c = proxy.c; |
int forcecache = -1, forcetext = options.text_query; |
int syncparse = zero_type(options.sync_parse) |
? -1 : options.sync_parse; |
if (proxy.waitforauthready) |
waitauthready(); |
string cenc = proxy.runtimeparameter[CLIENT_ENCODING]; |
switch(cenc) { |
case UTF8CHARSET: |
q = string_to_utf8(q); |
break; |
default: |
if (String.width(q) > 8) |
ERROR("Don't know how to convert %O to %s encoding\n", q, cenc); |
} |
array(string|int) paramValues; |
array from; |
if (bindings) { |
if (forcetext) |
q = .sql_util.emulate_bindings(q, bindings, this), |
paramValues = .pgsql_util.emptyarray; |
else { |
int pi = 0; |
paramValues = allocate(sizeof(bindings)); |
from = allocate(sizeof(bindings)); |
array(string) litfrom, litto, to = allocate(sizeof(bindings)); |
litfrom = litto = .pgsql_util.emptyarray; |
foreach (bindings; mixed name; mixed value) { |
if (stringp(name)) { |
if (name[0] != ':') |
name = ":" + name; |
if (name[1] == '_') { |
switch(name) { |
case ":_cache": |
forcecache = (int)value; |
break; |
case ":_text": |
forcetext = (int)value; |
break; |
case ":_sync": |
syncparse = (int)value; |
break; |
} |
continue; |
} |
if (!has_value(q, name)) |
continue; |
} |
if (multisetp(value)) { |
litto += ({indices(value)*","}); |
litfrom += ({name}); |
} else { |
paramValues[pi] = value; |
to[pi] = sprintf("$%d", pi + 1); |
from[pi++] = name; |
} |
} |
if (pi--) { |
paramValues = paramValues[.. pi]; |
q = replace(q, litfrom += from = from[.. pi], litto += to = to[.. pi]); |
} else { |
paramValues = .pgsql_util.emptyarray; |
if (sizeof(litfrom)) |
q = replace(q, litfrom, litto); |
} |
from = ({from, to, paramValues}); |
} |
} else |
paramValues = .pgsql_util.emptyarray; |
if (String.width(q) > 8) |
ERROR("Wide string literals in %O not supported\n", q); |
if (has_value(q, "\0")) |
ERROR("Querystring %O contains invalid literal nul-characters\n", q); |
mapping(string:mixed) tp; |
|
|
|
|
|
|
int transtype = .pgsql_util.transendprefix->match(q) ? TRANSEND |
: .pgsql_util.transbeginprefix->match(q) ? TRANSBEGIN : NOTRANS; |
if (transtype != NOTRANS) |
tp = .pgsql_util.describenodata; |
else if (!forcetext && forcecache == 1 |
|| forcecache && sizeof(q) >= MINPREPARELENGTH) { |
object plugbuffer = c->start(); |
if (tp = proxy.prepareds[q]) { |
if (tp.preparedname) { |
#ifdef PG_STATS |
prepstmtused++; |
#endif |
preparedname = tp.preparedname; |
} else if(tp.trun && tp.tparse*FACTORPLAN >= tp.trun |
&& (undefinedp(options.cache_autoprepared_statements) |
|| options.cache_autoprepared_statements)) |
preparedname = PREPSTMTPREFIX + int2hex(pstmtcount++); |
} else { |
if (proxy.totalhits >= cachedepth) |
foreach (proxy.prepareds; string ind; tp) { |
int oldhits = tp.hits; |
proxy.totalhits -= oldhits-(tp.hits = oldhits >> 1); |
if (oldhits <= 1) { |
closestatement(plugbuffer, tp.preparedname); |
m_delete(proxy.prepareds, ind); |
} |
} |
if (forcecache != 1 && .pgsql_util.createprefix->match(q)) { |
PD("Invalidate cache\n"); |
proxy.invalidatecache = 1; |
tp = 0; |
} else |
proxy.prepareds[q] = tp = ([]); |
} |
if (proxy.invalidatecache) { |
proxy.invalidatecache = 0; |
foreach (proxy.prepareds; ; mapping np) { |
closestatement(plugbuffer, np.preparedname); |
m_delete(np, "preparedname"); |
} |
} |
if (sizeof(CHAIN(plugbuffer))) { |
PD("%O\n", (string)CHAIN(plugbuffer)); |
plugbuffer->sendcmd(FLUSHSEND); |
} else |
plugbuffer->sendcmd(KEEP); |
} else |
tp = 0; |
.pgsql_util.sql_result portal; |
portal = .pgsql_util.sql_result(proxy, c, q, portalbuffersize, _alltyped, |
from, forcetext, timeout, syncparse, transtype); |
portal._tprepared = tp; |
#ifdef PG_STATS |
portalsopened++; |
#endif |
proxy.clearmessage = 1; |
|
if (Thread.this_thread() == .pgsql_util.local_backend.executing_thread()) |
Thread.Thread(startquery, forcetext, portal, q, tp, preparedname); |
else |
startquery(forcetext, portal, q, tp, preparedname); |
throwdelayederror(portal); |
return portal; |
} |
|
|
|
|
|
|
final inline .pgsql_util.sql_result streaming_query(string q, |
void|mapping(string|int:mixed) bindings) { |
return big_query(q, bindings); |
} |
|
|
|
|
|
|
final inline .pgsql_util.sql_result big_typed_query(string q, |
void|mapping(string|int:mixed) bindings) { |
return big_query(q, bindings, 1); |
} |
|
|