pike.git / src / modules / Postgres / pgresult.cmod

version» Context lines:

pike.git/src/modules/Postgres/pgresult.cmod:1: + /* -*- c -*- + || This file is part of Pike. For copyright information see COPYRIGHT. + || Pike is distributed under GPL, LGPL and MPL. See the file COPYING + || for more information. + */    -  + /* +  * Postgres95 support for pike/0.5 and up +  * +  * This code is provided AS IS, and may be copied and distributed freely, +  * under the terms of the GNU General Public License, version 2. +  * +  * You might notice that this code resembles Henrik Grubbestrom's mysql +  * module. It's just the most efficient way of doing things. This doesn't +  * imply I didn't peek at his code ^_^' +  */ +  + /* +  * Although Postgres allows great flexibility in returning values from the +  * backend connection, in order to keep this code interface-compliant +  * I'll have to do some serious emulation stuff, somehow making pike +  * code less easy to write. +  * I'm sticking to the object result interface because it allows +  * for bigger query results. +  * For now I'm handling only text. (external) type handling for postgres +  * is _REALLY_ messy. Not to talk the big_objects handling, and COPY and +  * the status... yuck. +  * The potential to powerfully store (also) binary data is there, but +  * it's dampened by a really messy interface implementation. +  */ +  + /* +  * Notes for Henrik: +  * - I suggest allowing negative seek() arguments for the generic interface, +  * moving the check inside the actual sql classes. +  */ +  + #include "global.h" + #include "pgres_config.h" + /* Some versions of Postgres define this, and it conflicts with pike_error.h */ + #undef JMP_BUF +  + #ifdef HAVE_POSTGRES +  + /* #define PGRESDEBUG */ +  + /* <sigh> Postgres stores strings internally padding with whitespaces +  * to their field length. If CUT_TRAILING_SPACES is defined, all +  * trailing spaces will be cut, regardless they were meant or not. +  * This is meant for 'compatibility' versus other database servers, like +  * Msql, where a declaration of char(20) means 'any string long at most +  * 20 characters', where in Postgres it means 'any string long exactly 20 +  * characters'. With Postgres you have to use type varchar otherwise, but +  * this makes its SQL incompatible with other servers'. +  * +  * Newer versions of Postgres seem to do this by themselves already. +  * Newer meaning at least 7.4 and later. Since Postgres 7.4 is the oldest +  * Postgres version that is still supported (by the Postgres team itself), +  * we might as well drop support for any earlier versions as well. +  * +  * FIXME: test the CUT_TRAILING_SPACES macro, and delete it if it is already +  * being done by the DB. +  */ +  + #define CUT_TRAILING_SPACES +  + /* Pike includes */ + #include "stralloc.h" + #include "object.h" + #include "threads.h" + #include "array.h" + #include "mapping.h" + #include "builtin_functions.h" + #include "module_support.h" + #include "pike_types.h" +  + /* <server/postgres_fe.h> doesn't suffice to be able to include +  * <server/catalog/pg_type.h>. +  */ + #ifdef HAVE_POSTGRESQL_SERVER_POSTGRES_H + #include <postgresql/server/postgres.h> + #elif defined(HAVE_SERVER_POSTGRES_H) + #include <server/postgres.h> + #elif defined(HAVE_POSTGRES_H) + #include <postgres.h> + #endif + #ifdef HAVE_POSTGRESQL_SERVER_CATALOG_PG_TYPE_H + #include <postgresql/server/catalog/pg_type.h> + #elif defined(HAVE_SERVER_CATALOG_PG_TYPE_H) + #include <server/catalog/pg_type.h> + #elif defined(HAVE_CATALOG_PG_TYPE_H) + #include <catalog/pg_type.h> + #endif +  + #ifdef _REENTRANT + # ifdef PQ_THREADSAFE + # define PQ_FETCH() PIKE_MUTEX_T *pg_mutex = pike_get_pgmux(THIS->pgod); + # define PQ_LOCK() mt_lock(pg_mutex) + # define PQ_UNLOCK() mt_unlock(pg_mutex) + # else + extern PIKE_MUTEX_T pike_postgres_mutex; + # define PQ_FETCH() + #define PQ_LOCK() mt_lock(&pike_postgres_mutex) + #define PQ_UNLOCK() mt_unlock(&pike_postgres_mutex) + # endif + #else + # define PQ_FETCH() + # define PQ_LOCK() + # define PQ_UNLOCK() + #endif +  + #include "pg_types.h" + #include "pgresult.h" +  + #ifndef BYTEAOID + #define BYTEAOID 17 + #define BPCHAROID 1042 + #endif +  + #ifdef PGRESDEBUG + #define pgdebug printf + #else + static void pgdebug (char * UNUSED(a), ...) {} + #endif +  + #define DEFAULT_CMOD_STORAGE +  + DECLARATIONS; +  + /*! @module Postgres +  *! +  *! @class postgres_result +  *! +  *! Contains the result of a Postgres-query. +  *! +  *! @seealso +  *! Sql.postgres, Postgres.postgres, Sql.Sql, Sql.sql_result +  */ +  + PIKECLASS postgres_result + { +  CVAR PGresult *result; +  CVAR struct Postgres_postgres_struct *pgod; +  CVAR struct object *pgo; +  CVAR int cursor; +  CVAR unsigned int flags; +  CVAR int rows; +  +  DECLARE_STORAGE; +  +  INIT +  { +  pgdebug("result_init().\n"); +  THIS->result=NULL; +  THIS->cursor=0; +  THIS->pgod = NULL; +  THIS->pgo = NULL; +  THIS->flags = 0; +  THIS->rows = 0; +  } +  +  EXIT +  { +  pgdebug("result_exit().\n"); +  THIS->pgod = NULL; +  if (THIS->pgo) { +  free_object(THIS->pgo); +  THIS->pgo = NULL; +  } +  PQclear(THIS->result); +  THIS->result = NULL; +  } +  + /*! @decl void create(object o) +  *! +  *! You can't create istances of this object yourself. +  *! The only way to create it is via a big_query to a Postgres +  *! database. +  */ +  + PIKEFUN void create(object o) +  flags ID_PROTECTED; + { +  char *storage; +  check_all_args(NULL,args,BIT_OBJECT,0); +  pgdebug("result->f_create(%d).\n",args); +  +  storage=get_storage(Pike_sp[-args].u.object, Postgres_postgres_program); +  if (!storage) +  Pike_error ("I need a Postgres object or an heir of it.\n"); +  +  if (!THIS->result) /*this ensures we _DO_ have a result*/ +  Pike_error ("Bad result.\n"); + #ifdef PGRESDEBUG +  pgdebug("Got %d tuples.\n",PQntuples(THIS->result)); + #endif + } +  +  PIKEFUN void _destruct() +  flags ID_PROTECTED; +  { +  if (THIS->pgod) { +  PGconn *conn = pike_get_pgconn(THIS->pgod); +  if (THIS->result) { +  PGresult *res = THIS->result; +  THIS->result = NULL; +  PQclear(res); +  } +  if (THIS->flags & PIKE_PG_COMMIT) { +  PGresult *res; +  PQ_FETCH(); +  THIS->flags &= ~PIKE_PG_COMMIT; +  THREADS_ALLOW(); +  PQ_LOCK(); +  res=PQexec(conn,"COMMIT"); +  PQ_UNLOCK(); +  THREADS_DISALLOW(); +  PQclear(res); +  pike_pg_set_lastcommit(THIS->pgod); +  } +  } +  } +  + struct object *make_postgres_result(struct object *postgres, +  PGresult *result, +  unsigned int flags) + { +  /* Return the result-object */ +  struct Postgres_postgres_result_struct *res; +  struct object *o; +  +  /* Create the object */ +  ref_push_object(postgres); +  o = clone_object(Postgres_postgres_result_program, 1); +  +  /* Set the result. */ +  if ((!(res = get_storage(o, Postgres_postgres_result_program))) || +  res->result) { +  PQclear(result); +  free_object(o); +  Pike_error("Bad postgres result object!\n"); +  } +  res->result = result; +  res->flags = flags; +  +  return o; + } +  +  + /*! @decl int num_rows() +  *! +  *! Returns the number of rows in the result. +  */ +  + PIKEFUN int num_rows() + { +  int rows; +  check_all_args(NULL,args,0); +  if (PQresultStatus(THIS->result)!=PGRES_TUPLES_OK) { +  push_int(0); +  return; +  } +  rows=PQntuples(THIS->result); +  push_int(THIS->rows>rows?THIS->rows:rows); +  return; + } +  +  + /*! @decl int num_fields() +  *! +  *! Returns the number of fields in the result. +  */ +  + PIKEFUN int num_fields() + { +  check_all_args(NULL,args,0); +  if (PQresultStatus(THIS->result)!=PGRES_TUPLES_OK) { +  push_int(0); +  return; +  } +  push_int(PQnfields(THIS->result)); +  return; + } +  +  + /*! @decl array(mapping(string:mixed)) fetch_fields() +  *! +  *! Returns an array with an entry for each field, each entry is +  *! a mapping with the following fields: +  *! +  *! @mapping +  *! @member string "name" +  *! Name of the column +  *! @member int "type" +  *! The type ID of the field. This is the database's internal +  *! representation type ID. +  *! @member int|string "length" +  *! Can be an integer (the size of the contents in +  *! bytes) or the word "variable". +  *! @endmapping +  *! +  *! @note +  *! For char() fields, length is to be intended as the MAXIMUM length +  *! of the field. This is not part of the interface specifications in fact, +  *! but a driver-choice. In fact char() fields are for Postgres _FIXED_ +  *! length fields, and are space-padded. If CUT_TRAILING_SPACES is defined +  *! when the driver is compiled (default behavior) it will cut such spaces. +  */ +  + PIKEFUN array(mapping(string:mixed)) fetch_fields() + { +  int j, numfields, tmp; +  PGresult * res=THIS->result; +  +  check_all_args(NULL,args,0); +  numfields=PQnfields(res); +  for (j=0;j<numfields;j++) +  { +  push_static_text("name"); +  push_text(PQfname(res,j)); +  /* no table information is available */ +  /* no default value information is available */ +  ref_push_string(literal_type_string); +  push_int(PQftype(res,j)); +  /* ARGH! I'd kill 'em! How am I supposed to know how types are +  * coded internally!?!?!?!? +  * +  * The internal encoding is well defined for the standard +  * types (big endian). The problem are the extended types. +  */ +  push_static_text("length"); +  tmp=PQfsize(res,j); +  if (tmp>=0) +  push_int(tmp); +  else +  push_text("variable"); +  f_aggregate_mapping(6); +  } +  f_aggregate(numfields); +  return; + } +  +  + /*! @decl void seek(int howmuch) +  *! +  *! Moves the result cursor (ahead or backwards) the specified number of +  *! rows. Notice that when you fetch a row, the cursor is automatically +  *! moved forward one slot. +  */ +  + PIKEFUN void seek(int howmuch) + { +  if (THIS->cursor+howmuch < 0) +  Pike_error ("Cannot seek to negative result indexes!\n"); +  if (THIS->cursor+howmuch > PQntuples(THIS->result)) +  Pike_error ("Cannot seek past result's end!.\n"); +  THIS->cursor += howmuch; + } +  +  + /*! @decl array(string) fetch_row() +  *! +  *! Returns an array with the contents of the next row in the result. +  *! Advances the row cursor to the next row. Returns 0 at end of table. +  *! +  *! @bugs +  *! Since there's no generic way to know whether a type is numeric +  *! or not in Postgres, all results are returned as strings. +  *! You can typecast them in Pike to get the numeric value. +  *! +  *! @seealso +  *! @[seek()] +  */ + PIKEFUN array(string) fetch_row() + { +  int j,numfields; +  +  check_all_args(NULL,args,0); +  pgdebug("f_fetch_row(); cursor=%d.\n",THIS->cursor); +  if (THIS->cursor>=PQntuples(THIS->result)) { +  PGresult * res=THIS->result; +  if(THIS->flags & PIKE_PG_FETCH) { +  PGconn * conn = pike_get_pgconn(THIS->pgod); +  int docommit = THIS->flags & PIKE_PG_COMMIT; +  int dofetch=1; +  PQ_FETCH(); +  THIS->result = NULL; +  /* FIXME: Race-condition on THIS->result. */ +  THREADS_ALLOW(); +  PQ_LOCK(); +  PQclear(res); +  res=PQexec(conn,FETCHCMD); +  if(!res || PQresultStatus(res)!=PGRES_TUPLES_OK +  || !PQntuples(res)) { +  PQclear(res); +  res = NULL; +  if(docommit) { +  res=PQexec(conn,"COMMIT"); +  } else { +  res=PQexec(conn,"CLOSE "CURSORNAME); +  } +  dofetch=0; +  } +  PQ_UNLOCK(); +  THREADS_DISALLOW(); +  THIS->result = res; +  if(!dofetch) { +  THIS->flags &= ~(PIKE_PG_COMMIT|PIKE_PG_FETCH); +  goto badresult; +  } +  THIS->cursor=0; +  } else { + badresult: +  push_undefined(); +  return; +  } +  } +  numfields=PQnfields(THIS->result); +  for (j=0;j<numfields;j++) { +  if (PQgetisnull(THIS->result, THIS->cursor, j)) { +  push_undefined(); +  } else { + #if defined(HAVE_PQUNESCAPEBYTEA) && defined(BYTEAOID) +  void *binbuf = 0; +  size_t binlen; + #endif +  char *value = PQgetvalue(THIS->result, THIS->cursor, j); +  int len = PQgetlength(THIS->result, THIS->cursor, j); +  switch(PQftype(THIS->result, j)) { + #if defined(CUT_TRAILING_SPACES) && defined(BPCHAROID) +  case BPCHAROID: +  for(;len>0 && value[len]==' ';len--); +  break; + #endif + #if defined(HAVE_PQUNESCAPEBYTEA) && defined(BYTEAOID) +  case BYTEAOID: +  if((binbuf = PQunescapeBytea((unsigned char *)value, &binlen))) { +  value = binbuf; +  len = binlen; +  } +  break; + #endif +  } +  +  push_string(make_shared_binary_string(value, len)); +  + #if defined(HAVE_PQUNESCAPEBYTEA) && defined(BYTEAOID) +  if(binbuf) +  free(binbuf); + #endif +  } +  } +  f_aggregate(numfields); +  THIS->cursor++; +  THIS->rows++; +  return; + } +  + } + /*! @endclass +  *! +  *! @endmodule +  */ +  + void pgresult_init (void) + { +  INIT; + } +  + void pgresult_exit(void) + { +  EXIT; + } +  + #else +  + static int place_holder; /* Keep the compiler happy */ +  + #endif   Newline at end of file added.