Branch: Tag:

2012-04-10

2012-04-10 08:35:36 by Stephen R. van den Berg <srb@cuci.nl>

pgsql: Fix reconnect option; more efficient decoding of selectresult;
support text connection mode in queries

Text mode connection support is possible through a connection option and/or
through a query option. Turning this on will allow multiple statements
per query, but will also force communication to and from the database for
a query to be converted to text. For some queries, this is more efficient
than the binary method.

74:   private array(string) lastmessage=({});   private int clearmessage;   private int earlyclose; - private int reconnectp; +    private mapping(string:array(mixed)) notifylist=([]);   mapping(string:string) _runtimeparameter;   state _mstate;
190:   //! @member int "force_ssl"   //! If the database supports and allows SSL connections, the session   //! will be SSL encrypted, if not, the connection will abort + //! @member int "text_query" + //! Send queries to and retrieve results from the database using text + //! instead of the, generally more efficient, default native binary method. + //! Turning this on will allow multiple statements per query separated + //! by semicolons   //! @member int "cache_autoprepared_statements"   //! If set to zero, it disables the automatic statement prepare and   //! cache logic; caching prepared statements can be problematic
764:    { int len=_c.getint16();    res->length=len>=0?len:"variable";    } -  res->atttypmod=_c.getint32();res->formatcode=_c.getint16(); +  res->atttypmod=_c.getint32(); +  res->formatcode=_c.getint16(); // Currently broken in Postgres    a[i]=res;    }   #ifdef DEBUGMORE
798:    datarowdesc=_c.portal->_datarowdesc;    int cols=_c.getint16();    int atext = _c.portal->_alltext; // cache locally for speed +  int forcetext = _c.portal->_forcetext; // cache locally for speed    string cenc=_runtimeparameter[CLIENT_ENCODING];    a=allocate(cols,UNDEFINED);    msglen-=2+4*cols; -  foreach(a;int i;) +  foreach(datarowdesc;int i;mapping m)    { int collen=_c.getint32();    if(collen>0)    { msglen-=collen;    mixed value; -  switch(datarowdesc[i]->type) -  { default:value=_c.getstring(collen); +  switch(int typ=m->type) +  { case FLOAT4OID: + #if SIZEOF_FLOAT>=8 +  case FLOAT8OID: + #endif +  if(!atext) +  { value=(float)_c.getstring(collen);    break; -  case TEXTOID: -  case BPCHAROID: -  case VARCHAROID: -  value=_c.getstring(collen); -  if(cenc==UTF8CHARSET && catch(value=utf8_to_string(value))) -  ERROR("%O contains non-%s characters\n",value,UTF8CHARSET); +  } +  default:value=_c.getstring(collen);    break; -  case CHAROID:value=atext?_c.getstring(1):_c.getbyte(); +  case CHAROID: +  value=atext?_c.getstring(1):_c.getbyte();    break;    case BOOLOID:value=_c.getbyte(); -  +  switch(value) +  { case 'f':value=0; +  break; +  case 't':value=1; +  }    if(atext)    value=value?"t":"f";    break; -  case INT8OID:value=_c.getint64(); -  break; - #if SIZEOF_FLOAT>=8 -  case FLOAT8OID: - #endif -  case FLOAT4OID: +  case TEXTOID: +  case BPCHAROID: +  case VARCHAROID:    value=_c.getstring(collen); -  +  if(cenc==UTF8CHARSET && catch(value=utf8_to_string(value))) +  ERROR("%O contains non-%s characters\n",value,UTF8CHARSET); +  break; +  case INT8OID:case INT2OID: +  case OIDOID:case INT4OID: +  if(forcetext) +  { value=_c.getstring(collen);    if(!atext) -  value=(float)value; +  value=(int)value; +  } +  else +  { switch(typ) +  { case INT8OID:value=_c.getint64();    break;    case INT2OID:value=_c.getint16();    break;    case OIDOID:    case INT4OID:value=_c.getint32();    } -  if(atext&&!stringp(value)) +  if(atext)    value=(string)value; -  +  } +  }    a[i]=value;    }    else if(!collen)
1054:    _c=0;    foreach(prepareds;;mapping tp)    m_delete(tp,"preparedname"); -  if(!reconnectp || !(connectmtxkey = _stealmutex.trylock(2))) +  if(!options->reconnect || !(connectmtxkey = _stealmutex.trylock(2)))    return 0; // Recursive reconnect, bailing out    }    if(!(_c=getsocket()))
1075:    plugbuf+=({"user\0",user,"\0"});    if(database)    plugbuf+=({"database\0",database,"\0"}); -  if(intp(options->reconnect)) -  reconnectp=options->reconnect; +  options->reconnect=zero_type(options->reconnect) || options->reconnect;    foreach(options -  -(<"use_ssl","force_ssl","cache_autoprepared_statements","reconnect">); +  -(<"use_ssl","force_ssl","cache_autoprepared_statements","reconnect", +  "text_query">);    string name;mixed value)    plugbuf+=({name,"\0",(string)value,"\0"});    plugbuf+=({"\0"});
1601:   //! @mapping   //! @member int ":_cache"   //! Forces caching on or off for the query at hand. + //! @member int ":_text" + //! Forces text mode in communication with the database for queries on or off + //! for the query at hand. Potentially more efficient than the default + //! binary method for simple queries with small or no result sets.   //! @endmapping   //!   //! @returns
1620:   //! database backends.   //!   //! @note - //! This function does not support multiple queries in one querystring. + //! This function, by default, does not support multiple queries in one + //! querystring.   //! I.e. it allows for but does not require a trailing semicolon, but it   //! simply ignores any commands after the first unquoted semicolon. This can   //! be viewed as a limited protection against SQL-injection attacks. -  + //! To make it support multiple queries in one querystring, use the + //! @ref{:_text@} option.   //!   //! @seealso   //! @[big_typed_query()], @[Sql.Sql], @[Sql.sql_result],
1633:   { string preparedname="";    string portalname="";    int forcecache=-1; +  int forcetext=options->text_query;    string cenc=_runtimeparameter[CLIENT_ENCODING];    switch(cenc)    { case UTF8CHARSET:
1657:    { switch(name)    { case ":_cache":forcecache=(int)value;    break; +  case ":_text":forcetext=(int)value; +  break;    }    continue;    }
1686:    ERROR("Querystring %O contains invalid literal nul-characters\n",q);    mapping(string:mixed) tp;    int tstart; -  if(forcecache==1 +  if(forcetext) +  { if(bindings) +  q = .sql_util.emulate_bindings(q, bindings, this); +  if(unnamedportalinuse) +  throw("Unnamed portal in use, needed for simple query"); +  } +  else if(forcecache==1    || forcecache!=0 && (sizeof(q)>=MINPREPARELENGTH || cachealways[q]))    { array(string) plugbuf=({});    if(tp=prepareds[q])
1731:    tp=UNDEFINED;    connectionclosed=0;    for(;;) -  { .pgsql_util.pgsql_result(this,q,_fetchlimit,portalbuffersize,_alltyped, -  from); +  { .pgsql_util.pgsql_result(this,q,_fetchlimit, +  portalbuffersize,_alltyped,from,forcetext);    if(unnamedportalinuse)    portalname=PORTALPREFIX+(string)pportalcount++;    else
1743:    clearmessage=1;    mixed err;    if(!(err = catch +  { if(forcetext) +  { _c.sendcmd(({"Q",_c.plugint32(4+sizeof(q)+1),q,"\0"}),1); +  PD("Simple query: %O\n",q); +  } +  else    { if(!sizeof(preparedname) || !tp || !tp->preparedname)    { PD("Parse statement %s\n",preparedname);    // Even though the protocol doesn't require the Parse command to be
1912:    plugbuf[1]=_c.plugint32(len);    PD("Bind portal %s statement %s\n",portalname,preparedname);    _c.sendcmd(plugbuf); - #ifdef DEBUGMORE + #ifdef DEBUGMORE    PD("%O\n",plugbuf);   #endif    }
1940:    }    tprepared=tp;    } +  }    }))    break;    PD("%O\n",err);