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

version» Context lines:

pike.git/src/modules/Postgres/postgres.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 and PostgreSQL support for Pike 0.5 and up. +  */ +  + #include "module.h" + #include "pgres_config.h" + #include "module_support.h" +  + /* Some versions of Postgres define this, and it conflicts with pike_error.h */ + #undef JMP_BUF +  + #ifdef HAVE_POSTGRES +  + /* #define PGDEBUG */ +  + /* Pike includes */ + #include "las.h" + #include "threads.h" + #include "operators.h" +  + #include "pgresult.h" +  + #define THIS ((struct pgres_object_data *) (Pike_fp->current_storage)) +  + /* Actual code */ + #ifdef PIKE_THREADS + # ifdef PQ_THREADSAFE + # define PQ_FETCH() PIKE_MUTEX_T *pg_mutex = &THIS->mutex; + # define PQ_LOCK() mt_lock(pg_mutex) + # define PQ_UNLOCK() mt_unlock(pg_mutex) + # else + PIKE_MUTEX_T pike_postgres_mutex STATIC_MUTEX_INIT; + # 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" +  +  + #ifdef PGDEBUG + #define pgdebug printf + #else + static void pgdebug (char * UNUSED(a), ...) {} + #endif +  + #define DEFAULT_CMOD_STORAGE +  + DECLARATIONS; +  + /*! @module Postgres +  *! +  *! @class postgres +  *! +  *! This is an interface to the Postgres (Postgres95, PostgreSQL) +  *! database server using libpq. +  *! +  *! This module may or may not be available on your Pike, depending on +  *! whether or not the appropriate include and library files +  *! could be found at compile-time. Note that you @b{do not@} +  *! need to have a Postgres server running on your host to use this module: +  *! you can connect to the database over a TCP/IP socket. +  *! +  *! @note +  *! Please notice that unless you wish to specifically connect to a Postgres +  *! server, you'd better use the @[Sql.Sql], which is a server-independent +  *! sql-server-class. The interfaces to all existing sql-classes +  *! are consistent. Using @[Sql.Sql] ensures that your Pike +  *! applications will run with any supported SQL server without changing +  *! a single line of code, at least for most common (and simple) operations. +  *! +  *! The program @[Postgres.postgres] provides the @b{raw@} interface +  *! to the database. Many functions are @b{not@} available +  *! for this program. Therefore, its use is DEPRECATED. +  *! It is included in this documentation only for completeness' sake. +  *! Use @[Sql.postgres] instead, or even better @[Sql.Sql] +  *! +  *! @note +  *! This driver is based on libpq and is DEPRECATED. There is a newer driver +  *! called @ref{Sql.pgsql@} which is faster and more robust than this driver +  *! and does not depend on any libraries. +  *! +  *! @note +  *! There is no testsuite for this module, since to test anything would +  *! require a working Postgres server. You can try to use the included scripts +  *! in the "pike/src/modules/Postgres/extras" directory but you'll probably +  *! have to patch them to reflect your site's settings. +  *! +  *! The behavior of the Postgres C API also depends on certain environment variables +  *! defined in the environment of the pike interpreter. +  *! +  *! @string +  *! @value "PGHOST" +  *! Sets the name of the default host to connect to. It defaults +  *! to @expr{"localhost"@}. +  *! @value "PGHOSTADDR" +  *! Set the numeric IP address to connect to. This may be set instead of +  *! or in addition to PGHOST to avoid DNS lookups. +  *! @value "PGPORT" +  *! Sets the default port or unix domain socket file extension to connect to, +  *! otherwise it will use compile-time defaults (that is: the time you compiled +  *! the postgres library, not the Pike driver). +  *! @value "PGDATABASE" +  *! Sets the default database to connect to. +  *! @value "PGUSER" +  *! Sets the default username used to connect to the database. +  *! @value "PGPASSWORD" +  *! Set the default password used to connect to the database. This is not +  *! recommended for security reasons, some operating systems allow non-root +  *! users to see process environment variables. Use your ~/.pgpass file instead. +  *! @value "PGSERVICE" +  *! Sets the service name to be looked up in pg_service.conf. This is a +  *! shorter way to set all the parameters. +  *! @value "PGSSLMODE" +  *! This determines how SSL connections will be negotiated. If set to @b{disable@}, +  *! it will require an unencrypted connection; @b{allow@} will negotiate a non-SSL +  *! connection, and if it fails try an SSL connection; @b{prefer@} will attempt SSL +  *! connections first, falling back to non-SSL if SSL fails; and @b{require@} will +  *! force an SSL connection or cause an error if SSL is not available. +  *! @value "PGOPTIONS" +  *! Sets some extra flags for the frontend-backend connection. +  *! @b{do not set@} unless you're sure of what you're doing. +  *! @value "PGREALM" +  *! Sets the Kerberos realm for authentication, if it is different +  *! from the local realm. PostgreSQL will attempt to authenticate +  *! with servers for this realm and use seperate ticket files to avoid +  *! conflicts with local ticket files. This variable is only used if +  *! Keberos authentication is selected in PostgreSQL. +  *! @value "PGTTY" +  *! Sets the file to be used for Postgres frontend debugging. +  *! Do not use, unless you're sure of what you're doing. +  *! This variable is ignored in recent versions of PostgreSQL. +  *! @endstring +  *! +  *! Refer to the libpq documentation for further details. +  *! +  *! @seealso +  *! @[Sql.Sql], @[Sql.postgres], @[Sql.postgres_result] +  */ +  + PIKECLASS postgres + { +  +  CVAR PGconn *dblink; +  CVAR struct pike_string *last_error; +  CVAR struct svalue notify_callback; +  CVAR int lastcommit; +  CVAR int last_rows; + #if defined(PIKE_THREADS) && defined(PQ_THREADSAFE) +  CVAR PIKE_MUTEX_T mutex; + #endif +  +  DECLARE_STORAGE; +  + static void set_error (char * newerror) + { +  pgdebug("set_error(%s).\n",newerror); +  if (THIS->last_error) +  free_string(THIS->last_error); +  THIS->last_error=make_shared_string(newerror); +  return; + } +  +  INIT +  { +  pgdebug ("pgres_create().\n"); +  THIS->dblink=NULL; +  THIS->last_error=NULL; +  mark_free_svalue (&THIS->notify_callback); +  THIS->lastcommit=0; +  THIS->last_rows=-1; + #if defined(PIKE_THREADS) && defined(PQ_THREADSAFE) +  mt_init(&THIS->mutex); + #endif +  +  } +  +  EXIT +  { +  PGconn * conn; +  PQ_FETCH(); +  pgdebug ("pgres_destroy().\n"); +  if ((conn=THIS->dblink)) { +  THREADS_ALLOW(); +  PQ_LOCK(); +  PQfinish(conn); +  PQ_UNLOCK(); +  THREADS_DISALLOW(); +  THIS->dblink=NULL; +  } +  if(THIS->last_error) { +  if (THIS->last_error) +  free_string(THIS->last_error); +  THIS->last_error=NULL; +  } +  if (TYPEOF(THIS->notify_callback) != PIKE_T_FREE) { +  free_svalue(&THIS->notify_callback); +  mark_free_svalue(&THIS->notify_callback); +  } + #if defined(PIKE_THREADS) && defined(PQ_THREADSAFE) +  mt_destroy(&THIS->mutex); + #endif +  } +  +  PGconn *pike_get_pgconn(struct Postgres_postgres_struct *this) +  { +  return this->dblink; +  } +  + #if defined(PIKE_THREADS) && defined(PQ_THREADSAFE) +  PIKE_MUTEX_T *pike_get_pgmux(struct Postgres_postgres_struct *this) +  { +  return &this->mutex; +  } + #endif +  +  void pike_pg_set_lastcommit(struct Postgres_postgres_struct *this) +  { +  this->lastcommit = 1; +  } +  + /*! @decl string version +  *! +  *! Should you need to report a bug to the author, please submit along with +  *! the report the driver version number, as returned by this call. +  */ +  + /*! @decl void create() +  *! @decl void create(string host, void|string database, void|int port) +  *! +  *! With no arguments, this function initializes (reinitializes if a connection +  *! had been previously set up) a connection to the Postgres backend. +  *! Since Postgres requires a database to be selected, it will try +  *! to connect to the default database. The connection may fail however for a +  *! variety of reasons, in this case the most likely of all is because +  *! you don't have enough authority to connect to that database. +  *! So use of this particular syntax is discouraged. +  *! +  *! The host argument allows you to connect to databases residing on different +  *! hosts. If it is 0 or "", it will try to connect to localhost. +  *! +  *! The database argument specifies the database to connect to. If 0 or "", it +  *! will try to connect to the default database. +  *! +  *! @note +  *! Notice that this function @b{can@} raise exceptions if the db +  *! server doesn't respond, if the database doesn't exist or is not accessible +  *! by you. +  *! +  *! You don't need bothering about syncronizing the connection to the database: +  *! it is automatically closed (and the database is sync-ed) when the +  *! object is destroyed. +  *! +  *! @seealso +  *! @[Sql.postgres], @[Sql.Sql], @[select_db] +  */ +  + /* create (host,database,username,password,port) */ +  /* That is: create(hostname,database,port) +  * It depends on the environment variables: +  * PGHOST, PGOPTIONS, PGPORT, PGTTY(don't use!), PGDATABASE +  * Notice: Postgres _requires_ a database to be selected upon connection +  */ +  PIKEFUN void create(string|void host_, string|void db_, +  string|void user_, string|void pass_, +  int|void port_no_, mapping|void options_) + { +  /*port will be used as a string with a sprintf()*/ +  char * host=NULL, *db=NULL, *user=NULL, *pass=NULL, *port=NULL; +  struct mapping *options = NULL; +  int port_no = -1; +  char port_buffer[10]; /*it's enough, we need only 6*/ +  PGconn * conn; +  PQ_FETCH(); +  +  get_all_args(NULL,args, +  ".%s%s%s%s%d%G", +  &host, +  &db, +  &user, +  &pass, +  &port_no, +  &options); +  +  if (port_no > 0) { +  if (port_no < 65536) { +  sprintf(port = port_buffer, "%d", port_no); +  } else { +  SIMPLE_ARG_TYPE_ERROR("create", 5, "int(0..65535)"); +  } +  } +  +  conn=THIS->dblink; +  THIS->dblink = NULL; +  +  /* FIXME: Race-condition! */ +  +  THREADS_ALLOW(); +  PQ_LOCK(); +  if (conn) { +  PQfinish(conn); +  } + #ifdef HAVE_PQSETDBLOGIN +  conn=PQsetdbLogin(host,port,NULL,NULL,db,user,pass); + #else +  conn=PQsetdb(host,port,NULL,NULL,db); + #endif +  PQ_UNLOCK(); +  THREADS_DISALLOW(); +  +  if (!conn) +  Pike_error ("Could not conneect to server\n"); +  if (PQstatus(conn)!=CONNECTION_OK) { +  set_error(PQerrorMessage(conn)); +  THREADS_ALLOW(); +  PQ_LOCK(); +  PQfinish(conn); +  PQ_UNLOCK(); +  THREADS_DISALLOW(); +  Pike_error("Could not connect to database. Reason: \"%S\".\n", +  THIS->last_error); +  conn=NULL; +  } +  THIS->dblink=conn; + } +  +  + /*! @decl void select_db (string dbname) +  *! +  *! This function allows you to connect to a database. Due to restrictions +  *! of the Postgres frontend-backend protocol, you always have to be connected +  *! to a database, so in fact this function just allows you to connect +  *! to a different database on the same server. +  *! +  *! @note +  *! This function @b{can@} raise exceptions if something goes wrong (backend process +  *! not running, not enough permissions..) +  *! +  *! @seealso +  *! create +  */ +  + PIKEFUN void select(string db_) + { +  char *host, *port, *options, *db; +  PGconn * conn, *newconn; +  PQ_FETCH(); +  +  get_all_args(NULL,args,"%s", &db); +  +  if (!THIS->dblink) +  Pike_error ("Driver error. How can you possibly not be linked to a " +  "database already?\n"); +  conn=THIS->dblink; + #if 0 +  /* This is an optimization, but people may want to reset a connection +  * by re-selecting its database. +  */ +  if (!strcmp(db, PQdb(conn))) +  return; + #endif +  THREADS_ALLOW(); +  PQ_LOCK(); +  host=PQhost(conn); +  port=PQport(conn); +  options=PQoptions(conn); +  +  /* This could be really done calling f_create, but it's more efficient this +  * way */ +  /* using newconn is necessary or otherwise the datastructures I use +  * as arguments get freed by PQfinish. Could be a problem under extreme +  * situations (i.e. if the temporary use of _one_ more filedescriptor +  * is not possible. +  */ +  newconn=PQsetdb(host,port,options,NULL,db); +  PQfinish(conn); +  conn=newconn; +  PQ_UNLOCK(); +  THREADS_DISALLOW(); +  if (PQstatus(conn)==CONNECTION_BAD) { +  set_error(PQerrorMessage(conn)); +  PQ_LOCK(); +  PQfinish(conn); +  PQ_UNLOCK(); +  Pike_error("Could not connect to database.\n"); +  conn=NULL; +  } +  THIS->dblink=conn; + } +  + /*! @decl object(Sql.postgres_result) big_query (string sqlquery) +  *! @decl object(Sql.postgres_result) big_query (string sqlquery, @ +  *! array bindings) +  *! +  *! This is the only provided interface which allows you to query the +  *! database. If you wish to use the simpler "query" function, you need to +  *! use the @[Sql.Sql] generic sql-object. +  *! +  *! It returns a postgres_result object (which conforms to the @[Sql.sql_result] +  *! standard interface for accessing data). I recommend using query() for +  *! simpler queries (because it is easier to handle, but stores all the result +  *! in memory), and big_query for queries you expect to return huge amounts +  *! of data (it's harder to handle, but fectches results on demand). +  *! +  *! @note +  *! This function @b{can@} raise exceptions. +  *! +  *! The program @[Sql.postgres_result] is a superset of +  *! the raw @[Postgres.postgres_result] which does not do chunking to +  *! avoid excessive memory usage in case of large resultsets. +  *! +  *! @seealso +  *! @[Sql.Sql], @[Sql.sql_result] +  */ +  + PIKEFUN object big_query(string query_, array|void bnds) + { +  PGconn *conn = THIS->dblink; +  PGresult * res; +  PGnotify * notification; +  char *query = " "; +  int lastcommit; +  unsigned int flags = 0; +  int cnt; +  int nParams = 0; +  const char** paramValues = 0; +  int* paramLengths = 0; +  int* paramFormats = 0; +  int resultFormat = 0; +  +  PQ_FETCH(); +  lastcommit=THIS->lastcommit; +  +  if (!conn) +  Pike_error ("Not connected.\n"); +  +  if(query_->len) +  query = query_->str; +  +  if(bnds && (cnt=bnds->size)) { +  int i; +  struct svalue *item; +  +  nParams=cnt; +  +  /* NB: We assume that sizeof(char *) is >= sizeof(int). */ +  paramValues = xalloc(nParams*sizeof(char *)*3); +  paramLengths = (int*)(paramValues + nParams); +  paramFormats = paramLengths + nParams; +  +  for (i=0,item=bnds->item; cnt--; item++,i++) { +  int sendbin=0; +  switch(TYPEOF(*item)) +  { case PIKE_T_STRING: { +  long len; +  const char*p; +  paramLengths[i] = len = item->u.string->len; +  paramValues[i] = p = item->u.string->str; +  if( len >= BINARYCUTOFF) +  sendbin = 1; +  else { +  while(len--) { +  switch(*p++) { +  default: +  continue; +  case '\0':case '\\': +  sendbin = 1; +  } +  break; +  } +  } +  break; +  } +  case PIKE_T_INT: +  case T_VOID: +  paramValues[i] = 0; /* NULL */ +  paramLengths[i] = 0; +  break; +  default: +  Pike_error("Expected string or UNDEFINED element, Got %d.\n", +  TYPEOF(*item)); +  break; +  } +  paramFormats[i] = sendbin; +  } +  } +  +  THREADS_ALLOW(); +  PQ_LOCK(); +  pgdebug("f_big_query(\"%s\")\n",query); +  /* +  * libpq implements partial reads from a SELECT through +  * a mechanism of a CURSOR incombination with FETCH. +  * +  * Cursors are only used if the query starts with "SELECT " +  * and does not end in "LIMIT 1" or "LIMIT 1;" +  * The checks are case-sensitive by design. +  * +  * Cursors can only be used within a transaction, so you'll end +  * up trying to start a transaction; if a transaction already is +  * in progress, that will be noticed and it will not be disrupted. +  */ + #define SELECTSTR "SELECT " + #define LIMIT1STR "LIMIT 1" + #define LIMIT1STRSC LIMIT1STR";" + #define LIMITLENSC (sizeof(LIMIT1STRSC)-1) + #define LIMITLEN (sizeof(LIMIT1STR)-1) +  res = 0; +  if(!strncmp(query,SELECTSTR,sizeof(SELECTSTR)-1)) +  { + #define CURSORPREFIX "DECLARE "CURSORNAME" CURSOR FOR " + #define CPREFLEN sizeof(CURSORPREFIX) +  unsigned int qlen=strlen(query); +  char *nquery; +  if(qlen>LIMITLENSC && strcmp(query+qlen-LIMITLENSC,LIMIT1STRSC) +  && strcmp(query+qlen-LIMITLEN,LIMIT1STR) +  &&(nquery=malloc(CPREFLEN+qlen))) { +  strcpy(nquery,CURSORPREFIX); +  strcpy(nquery+CPREFLEN-1,query); +  if(lastcommit) +  goto yupbegin; +  res=PQexecParams(conn,nquery, +  nParams,0,paramValues,paramLengths,paramFormats,resultFormat); +  if(PQstatus(conn) != CONNECTION_OK) { +  PQclear(res); +  PQreset(conn); +  res=PQexecParams(conn,nquery, +  nParams,0,paramValues,paramLengths,paramFormats,resultFormat); +  } +  if(res) +  switch(PQresultStatus(res)) { +  case PGRES_FATAL_ERROR: +  PQclear(res); + yupbegin: res=PQexec(conn,"BEGIN"); +  if(res && PQresultStatus(res)==PGRES_COMMAND_OK) { +  PQclear(res); +  res=PQexecParams(conn,nquery,nParams,0, +  paramValues,paramLengths,paramFormats,resultFormat); +  if(res && PQresultStatus(res)==PGRES_COMMAND_OK) +  flags |= PIKE_PG_COMMIT; +  else { +  PQclear(res); +  res=PQexec(conn,"COMMIT"); +  PQclear(res); +  res=0; +  break; +  } +  } else +  break; +  case PGRES_COMMAND_OK: +  flags |= PIKE_PG_FETCH; +  PQclear(res); +  res=PQexec(conn,FETCHCMD); +  break; +  default: +  PQclear(res); +  res=0; +  } +  free(nquery); +  } +  } +  lastcommit=0; +  if(!res) +  res=PQexecParams(conn,query, +  nParams,0,paramValues,paramLengths,paramFormats,resultFormat); +  /* A dirty hack to fix the reconnect bug. +  * we don't need to store the host/user/pass/db... etc.. +  * PQreset() does all the job. +  * Zsolt Varga <redax@agria.hu> 2000-apr-04 +  */ +  if((PQstatus(conn) != CONNECTION_OK) || +  (PQresultStatus(res) == PGRES_FATAL_ERROR) || +  (PQresultStatus(res) == PGRES_BAD_RESPONSE)) { +  PQclear(res); +  PQreset(conn); +  res=PQexecParams(conn,query, +  nParams,0,paramValues,paramLengths,paramFormats,resultFormat); +  } +  +  notification=PQnotifies(conn); +  PQ_UNLOCK(); +  THREADS_DISALLOW(); +  THIS->lastcommit=lastcommit; +  +  if (paramValues) { +  xfree(paramValues); +  } +  +  pop_n_elems(args); +  if (notification!=NULL) { +  pgdebug("Incoming notification: \"%s\"\n",notification->relname); +  push_text(notification->relname); +  apply_svalue(&THIS->notify_callback,1); +  /* apply_svalue simply returns if the first argument is a PIKE_T_INT */ +  free (notification); +  } +  if (!res) { +  set_error(PQerrorMessage(conn)); +  if (!strncmp(THIS->last_error->str,"WARN",4)) { +  /* Sigh... woldn't a NONFATAL_ERROR be MUCH better? */ +  push_int(1); +  return; +  } +  Pike_error ("Error in query.\n"); +  } +  +  switch (PQresultStatus(res)) +  { +  case PGRES_EMPTY_QUERY: +  case PGRES_COMMAND_OK: +  pgdebug("\tOk.\n"); +  THIS->last_rows=atoi(PQcmdTuples(res)); +  PQclear(res); +  push_int(0); +  return; +  case PGRES_NONFATAL_ERROR: +  pgdebug ("Warning.\n"); +  set_error(PQerrorMessage(conn)); +  push_int(1); +  return; +  case PGRES_BAD_RESPONSE: +  case PGRES_FATAL_ERROR: +  pgdebug("\tBad.\n"); +  set_error(PQerrorMessage(conn)); +  PQclear(res); +  Pike_error("Postgres error: %S\n", THIS->last_error); +  case PGRES_TUPLES_OK: +  pgdebug("\tResult.\n"); +  THIS->last_rows=PQntuples(res); +  push_object(make_postgres_result(Pike_fp->current_object, +  res, flags)); +  ref_push_object(this_object()); +  push_object(clone_object(Postgres_postgres_result_program,1)); +  return; +  default: +  Pike_error ("Unimplemented server feature.\n"); +  } +  Pike_error ("Internal error in postgres module.\n"); + } +  +  + /*! @decl string error() +  *! +  *! This function returns the textual description of the last server-related +  *! error. Returns 0 if no error has occurred yet. It is not cleared upon +  *! reading (can be invoked multiple times, will return the same result +  *! until a new error occurs). +  *! +  *! @seealso +  *! big_query +  */ +  + PIKEFUN string error(void|int clear) + { +  int clearit = clear && clear->u.integer; +  pop_n_elems(args); +  +  if (THIS->last_error) { +  ref_push_string(THIS->last_error); +  if (clearit) { +  free_string(THIS->last_error); +  THIS->last_error = 0; +  } +  } else +  push_int(0); +  return; + } +  +  + /*! @decl void reset() +  *! +  *! This function resets the connection to the backend. Can be used for +  *! a variety of reasons, for example to detect the status of a connection. +  *! +  *! @note +  *! This function is Postgres-specific, and thus it is not available +  *! through the generic SQL-interface. +  */ +  + PIKEFUN void reset() + { +  PGconn * conn; +  PQ_FETCH(); +  +  if (!THIS->dblink) +  Pike_error ("Not connected.\n"); +  conn=THIS->dblink; +  THREADS_ALLOW(); +  PQ_LOCK(); +  PQreset(conn); +  PQ_UNLOCK(); +  THREADS_DISALLOW(); +  if (PQstatus(conn)==CONNECTION_BAD) { +  set_error(PQerrorMessage(conn)); +  Pike_error("Bad connection.\n"); +  } + } +  + #if 0 + /* This was cut (for now) because of the difficulty of obtaining +  * a valid FILE * from a fileobject. +  */ + PIKEFUN void trace(int trace_level) + { +  if (args!=1) +  Pike_error ("Wrong args for trace().\n"); +  if (TYPEOF(Pike_sp[-args]) == PIKE_T_INT) +  if (Pike_sp[-args].u.integer==0) +  PQuntrace(THIS->dblink); +  else +  Pike_error ("Wrong argument for postgres->trace().\n"); +  else + /* I really don't know how to check that the argument is an instance of +  * /precompiled/file... I guess there's the name stored somewhere.. +  * For now let's presume that if it's an object, then it's a /precompiled/file*/ +  PQtrace(THIS->dblink, +  ((struct file_struct*)Pike_sp[-args].u.object->storage)->fd); + } + #endif +  + /*! @decl void _set_notify_callback() +  *! @decl void _set_notify_callback(function f) +  *! +  *! With Postgres you can associate events and notifications to tables. +  *! This function allows you to detect and handle such events. +  *! +  *! With no arguments, resets and removes any callback you might have +  *! put previously, and any polling cycle. +  *! +  *! With one argument, sets the notification callback (there can be only +  *! one for each sqlobject). +  *! +  *! The callback function must return no value, and takes a string argument, +  *! which will be the name of the table on which the notification event +  *! has occurred. In future versions, support for user-specified arguments +  *! will be added. +  *! +  *! @note +  *! The @[Sql.postgres] program adds support for automatic delivery of +  *! messages (see it for explanation on the inner workings of this feature). +  *! +  *! This function is Postgres-specific, and thus it is not available +  *! through the generic SQL-interface +  *! +  *! @seealso +  *! Sql.postgres +  */ +  + PIKEFUN void _set_notify_callback(function|void cb) + { +  check_all_args(NULL, args, BIT_INT|BIT_FUNCTION, 0); +  +  if (TYPEOF(Pike_sp[-args]) == PIKE_T_INT) { +  if (TYPEOF(THIS->notify_callback) != PIKE_T_FREE) { +  free_svalue(&THIS->notify_callback); +  mark_free_svalue (&THIS->notify_callback); +  } +  return; +  } +  /*let's assume it's a function otherwise*/ +  assign_svalue(&THIS->notify_callback,Pike_sp-args); + } +  +  + /*! @decl string host_info() +  *! +  *! This function returns a string describing what host are we talking to, +  *! and how (TCP/IP or UNIX sockets). +  */ +  + PIKEFUN string host_info() + { +  pop_n_elems(args); +  +  if (PQstatus(THIS->dblink)!=CONNECTION_BAD) { +  char buf[64]; +  sprintf(buf,"TCP/IP %p connection to ",THIS->dblink); +  push_text(buf); +  pgdebug("adding reason\n"); +  if(PQhost(THIS->dblink)) +  push_text(PQhost(THIS->dblink)); +  else +  push_static_text("<unknown>"); +  pgdebug("done\n"); +  f_add(2); +  return; +  } +  set_error(PQerrorMessage(THIS->dblink)); +  Pike_error ("Bad connection.\n"); + } +  + /*! @decl int affected_rows() +  *! +  *! This function returns the number of rows affected by the last query. +  */ +  + PIKEFUN int affected_rows() + { +  pop_n_elems(args); +  push_int(THIS->last_rows); + } +  + /*! @decl string _quote(string s) +  *! +  *! Escape a string to prevent SQL injection, using the current connection's +  *! character encoding settings. +  */ + #if defined(HAVE_PQESCAPESTRINGCONN) || defined(HAVE_PQESCAPESTRING) + PIKEFUN string _quote(string s_) + { +  int err; +  int len; +  struct pike_string *ret; +  struct pike_string *s; +  char *err_msg; +  +  get_all_args(NULL, args, "%S", &s); +  +  ret = begin_shared_string(s->len * 2 + 1); + #ifdef HAVE_PQESCAPESTRINGCONN +  len = PQescapeStringConn(THIS->dblink, ret->str, s->str, s->len, &err); +  if (err != 0) { +  err_msg = PQerrorMessage(THIS->dblink); +  set_error(err_msg); +  Pike_error(err_msg); +  } + #else +  len = PQescapeString(ret->str, s->str, s->len); + #endif +  pop_n_elems(args); +  push_string(end_and_resize_shared_string(ret, len)); + } + #endif /* HAVE_PQESCAPESTRINGCONN || HAVE_PQESCAPESTRING */ +  + } + /*! @endclass +  *! +  *! @endmodule +  */ +  + PIKE_MODULE_INIT + { +  INIT; +  +  add_string_constant("version","Postgres/1.0.2",0); +  +  pgresult_init(); + } +  + PIKE_MODULE_EXIT + { +  EXIT; +  +  pgresult_exit(); + } +  + #else /* HAVE_POSTGRES */ + #include "program.h" + PIKE_MODULE_INIT { +  HIDE_MODULE(); + } + PIKE_MODULE_EXIT {} + #endif /* HAVE_POSTGRES */   Newline at end of file added.