Re: libpq object hooks (libpq events) - Mailing list pgsql-patches

From Andrew Chernow
Subject Re: libpq object hooks (libpq events)
Date
Msg-id 482ECF62.7010901@esilo.com
Whole thread Raw
In response to Re: libpq object hooks  (Andrew Chernow <ac@esilo.com>)
Responses Re: libpq object hooks (libpq events)  ("Merlin Moncure" <mmoncure@gmail.com>)
Re: libpq object hooks (libpq events)  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-patches
Here is an updated patch for what was called object hooks.  This is now
called libpq events.  If someone has a better name or hates ours, let us
know.

I am continuing to use the object hooks thread to avoid confusing anyone.

Terminology:
I got rid of calling it object events because it is possible to add
other non-object-related events in the future; maybe a query event.
This system can be used for any type of event, I think it is pretty generic.

event proc - the callback procedure/function implemented outside of
libpq ... PGEventProc.  The address of the event proc is used as a
lookup key for getting a conn/result's event data.

event data - the state data managed by the event proc but tracked by
libpq.  This allows the event proc implementor to basically add a
dynamic struct member to a conn or result.  This is an instance based
value, unlike the "arg pointer".

arg pointer - this is the passthrough/user pointer.  I called it 'arg'
as other libpq callbacks used this term for this type of value.  This
value can be retrieved via PQeventData, PQresultEventData ... its an
optional double pointer argument.

event state - an internal structure, PGEventState, which contains the
event data, event proc and the 'arg' pointer.  Internally, PGconn and
PGresult contain arrays of event states.

event id - PGEventId enum values, PGEVT_xxx.  This tells the event proc
which event has occurred.

event info - These are the structures for event ids, like
PGEVT_RESULTDESTROY has a corresponding PGEventResultDestroy structure.
  The PGEventProc function's 2nd argument is "void *info".  The first
argument is an event id which tells the proc implementor how to cast the
void *.  This replaced the initial va_arg idea.

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/
Index: exports.txt
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/exports.txt,v
retrieving revision 1.19
diff -C6 -r1.19 exports.txt
*** exports.txt    19 Mar 2008 00:39:33 -0000    1.19
--- exports.txt    17 May 2008 12:06:10 -0000
***************
*** 138,143 ****
--- 138,149 ----
  PQsendDescribePortal      136
  lo_truncate               137
  PQconnectionUsedPassword  138
  pg_valid_server_encoding_id 139
  PQconnectionNeedsPassword 140
  lo_import_with_oid          141
+ PQcopyResult              142
+ PQsetvalue                143
+ PQresultAlloc             144
+ PQregisterEventProc       145
+ PQeventData               146
+ PQresultEventData         147
Index: fe-connect.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v
retrieving revision 1.358
diff -C6 -r1.358 fe-connect.c
*** fe-connect.c    16 May 2008 18:30:53 -0000    1.358
--- fe-connect.c    17 May 2008 12:06:11 -0000
***************
*** 998,1009 ****
--- 998,1014 ----
               * We really shouldn't have been polled in these two cases, but we
               * can handle it.
               */
          case CONNECTION_BAD:
              return PGRES_POLLING_FAILED;
          case CONNECTION_OK:
+             if(!pqRegisterGlobalEvents(conn))
+             {
+                 conn->status = CONNECTION_BAD;
+                 return PGRES_POLLING_FAILED;
+             }
              return PGRES_POLLING_OK;

              /* These are reading states */
          case CONNECTION_AWAITING_RESPONSE:
          case CONNECTION_AUTH_OK:
              {
***************
*** 1816,1827 ****
--- 1821,1837 ----
                      conn->next_eo = EnvironmentOptions;
                      return PGRES_POLLING_WRITING;
                  }

                  /* Otherwise, we are open for business! */
                  conn->status = CONNECTION_OK;
+                 if(!pqRegisterGlobalEvents(conn))
+                 {
+                     conn->status = CONNECTION_BAD;
+                     return PGRES_POLLING_FAILED;
+                 }
                  return PGRES_POLLING_OK;
              }

          case CONNECTION_SETENV:

              /*
***************
*** 1848,1859 ****
--- 1858,1874 ----
                  default:
                      goto error_return;
              }

              /* We are open for business! */
              conn->status = CONNECTION_OK;
+             if(!pqRegisterGlobalEvents(conn))
+             {
+                 conn->status = CONNECTION_BAD;
+                 return PGRES_POLLING_FAILED;
+             }
              return PGRES_POLLING_OK;

          default:
              printfPQExpBuffer(&conn->errorMessage,
                                libpq_gettext(
                                              "invalid connection state %c, "
***************
*** 1970,1981 ****
--- 1985,2016 ----
   * release data that is to be held for the life of the PGconn structure.
   * If a value ought to be cleared/freed during PQreset(), do it there not here.
   */
  static void
  freePGconn(PGconn *conn)
  {
+     int i;
+     PGEventConnDestroy info;
+
+     /* Let the event procs cleanup their state data */
+     for(i=0; i < conn->evtStateCount; i++)
+     {
+         info.conn = conn;
+         info.data = conn->evtStates[i].data;
+         (void)conn->evtStates[i].proc(PGEVT_CONNDESTROY,
+             &info, conn->evtStates[i].arg);
+     }
+
+     /* free the PGEventState array */
+     if(conn->evtStates)
+     {
+         free(conn->evtStates);
+         conn->evtStates = NULL;
+         conn->evtStateCount = conn->evtStateSize = 0;
+     }
+
      if (conn->pghost)
          free(conn->pghost);
      if (conn->pghostaddr)
          free(conn->pghostaddr);
      if (conn->pgport)
          free(conn->pgport);
***************
*** 2151,2164 ****
  PQreset(PGconn *conn)
  {
      if (conn)
      {
          closePGconn(conn);

!         if (connectDBStart(conn))
!             (void) connectDBComplete(conn);
      }
  }


  /*
   * PQresetStart:
--- 2186,2218 ----
  PQreset(PGconn *conn)
  {
      if (conn)
      {
          closePGconn(conn);

!         if (connectDBStart(conn) && connectDBComplete(conn))
!         {
!             int i;
!             PGEventConnReset info;
!
!             for(i=0; i < conn->evtStateCount; i++)
!             {
!                 info.conn = conn;
!                 info.data = conn->evtStates[i].data;
!
!                 if(!conn->evtStates[i].proc(PGEVT_CONNRESET, &info,
!                     conn->evtStates[i].arg))
!                 {
!                     conn->status = CONNECTION_BAD;
!                     printfPQExpBuffer(&conn->errorMessage,
!                         libpq_gettext("PGEventProc %p failed during PGEVT_CONNRESET event\n"),
!                         conn->evtStates[i].proc);
!                     break;
!                 }
!             }
!         }
      }
  }


  /*
   * PQresetStart:
***************
*** 2186,2198 ****
   * closes the existing connection and makes a new one
   */
  PostgresPollingStatusType
  PQresetPoll(PGconn *conn)
  {
      if (conn)
!         return PQconnectPoll(conn);

      return PGRES_POLLING_FAILED;
  }

  /*
   * PQcancelGet: get a PGcancel structure corresponding to a connection.
--- 2240,2278 ----
   * closes the existing connection and makes a new one
   */
  PostgresPollingStatusType
  PQresetPoll(PGconn *conn)
  {
      if (conn)
!     {
!         PostgresPollingStatusType status = PQconnectPoll(conn);
!
!         if(status == PGRES_POLLING_OK)
!         {
!             int i;
!             PGEventConnReset info;
!
!             for(i=0; i < conn->evtStateCount; i++)
!             {
!                 info.conn = conn;
!                 info.data = conn->evtStates[i].data;
!
!                 if(!conn->evtStates[i].proc(PGEVT_CONNRESET, &info,
!                     conn->evtStates[i].arg))
!                 {
!                     conn->status = CONNECTION_BAD;
!                     printfPQExpBuffer(&conn->errorMessage,
!                         libpq_gettext("PGEventProc %p failed during PGEVT_CONNRESET event\n"),
!                         conn->evtStates[i].proc);
!                     return PGRES_POLLING_FAILED;
!                 }
!             }
!         }
!
!         return status;
!     }

      return PGRES_POLLING_FAILED;
  }

  /*
   * PQcancelGet: get a PGcancel structure corresponding to a connection.
Index: fe-exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v
retrieving revision 1.194
diff -C6 -r1.194 fe-exec.c
*** fe-exec.c    1 Jan 2008 19:46:00 -0000    1.194
--- fe-exec.c    17 May 2008 12:06:12 -0000
***************
*** 60,72 ****
                  int resultFormat);
  static void parseInput(PGconn *conn);
  static bool PQexecStart(PGconn *conn);
  static PGresult *PQexecFinish(PGconn *conn);
  static int PQsendDescribe(PGconn *conn, char desc_type,
                 const char *desc_target);
!

  /* ----------------
   * Space management for PGresult.
   *
   * Formerly, libpq did a separate malloc() for each field of each tuple
   * returned by a query.  This was remarkably expensive --- malloc/free
--- 60,72 ----
                  int resultFormat);
  static void parseInput(PGconn *conn);
  static bool PQexecStart(PGconn *conn);
  static PGresult *PQexecFinish(PGconn *conn);
  static int PQsendDescribe(PGconn *conn, char desc_type,
                 const char *desc_target);
! static int check_field_number(const PGresult *res, int field_num);

  /* ----------------
   * Space management for PGresult.
   *
   * Formerly, libpq did a separate malloc() for each field of each tuple
   * returned by a query.  This was remarkably expensive --- malloc/free
***************
*** 121,141 ****
--- 121,167 ----
  #define PGRESULT_DATA_BLOCKSIZE        2048
  #define PGRESULT_ALIGN_BOUNDARY        MAXIMUM_ALIGNOF        /* from configure */
  #define PGRESULT_BLOCK_OVERHEAD        Max(sizeof(PGresult_data), PGRESULT_ALIGN_BOUNDARY)
  #define PGRESULT_SEP_ALLOC_THRESHOLD    (PGRESULT_DATA_BLOCKSIZE / 2)


+ /* Does not duplicate the private state data, sets this to NULL */
+ static PGEventState *
+ dupEventStates(PGEventState *states, int count)
+ {
+     int i;
+     PGEventState *newStates;
+
+     if(!states || count <= 0)
+         return NULL;
+
+     newStates = (PGEventState *)malloc(count * sizeof(PGEventState));
+     if(!newStates)
+         return NULL;
+
+     memcpy(newStates, states, count * sizeof(PGEventState));
+
+     /* NULL out the data pointer */
+     for(i=0; i < count; i++)
+         newStates[i].data = NULL;
+
+     return newStates;
+ }
+
  /*
   * PQmakeEmptyPGresult
   *     returns a newly allocated, initialized PGresult with given status.
   *     If conn is not NULL and status indicates an error, the conn's
   *     errorMessage is copied.
   *
   * Note this is exported --- you wouldn't think an application would need
   * to build its own PGresults, but this has proven useful in both libpgtcl
   * and the Perl5 interface, so maybe it's not so unreasonable.
+  *
+  * Updated April 2008 - If conn is not NULL, event states will be copied
+  * from the PGconn to the created PGresult.
   */

  PGresult *
  PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
  {
      PGresult   *result;
***************
*** 157,175 ****
--- 183,218 ----
      result->errMsg = NULL;
      result->errFields = NULL;
      result->null_field[0] = '\0';
      result->curBlock = NULL;
      result->curOffset = 0;
      result->spaceLeft = 0;
+     result->evtStateCount = 0;
+     result->evtStates = NULL;

      if (conn)
      {
          /* copy connection data we might need for operations on PGresult */
          result->noticeHooks = conn->noticeHooks;
          result->client_encoding = conn->client_encoding;

+         /* copy event states from connection */
+         if(conn->evtStateCount > 0)
+         {
+             result->evtStates = dupEventStates(
+                 conn->evtStates, conn->evtStateCount);
+
+             if(!result->evtStates)
+             {
+                 PQclear(result);
+                 return NULL;
+             }
+
+             result->evtStateCount = conn->evtStateCount;
+         }
+
          /* consider copying conn's errorMessage */
          switch (status)
          {
              case PGRES_EMPTY_QUERY:
              case PGRES_COMMAND_OK:
              case PGRES_TUPLES_OK:
***************
*** 193,204 ****
--- 236,490 ----
      }

      return result;
  }

  /*
+  * PQcopyResult
+  * Returns a deep copy of the provided 'src' PGresult, which cannot be NULL.
+  * The 'options' argument controls which portions of the result will or will
+  * NOT be copied.  If this value is 0, the entire result is deep copied.
+  * The created result is always put into the PGRES_TUPLES_OK status.  The
+  * source result error message is not copied, although cmdStatus is.
+  *
+  * Options:
+  *   PG_COPYRES_NO_TUPLES - Do not copy the tuples.  This option is
+  *   automatically enabled when PG_COPYRES_USE_ATTRS is set.
+  *
+  *   PG_COPYRES_USE_ATTRS - Indicates that the 'numAttributes' and 'attDescs'
+  *   arguments should be used as the fields for the created result, rather
+  *   than copying them from the source result.  When this option is set,
+  *   the tuples will NOT be copied; behaving identically to setting the
+  *   PG_COPYRES_NO_TUPLES option.  One must use PQsetvalue to manually
+  *   add tuples to the returned result.  NOTE: numAttributes and attDescs
+  *   arguments are ignored unless this option is set!
+  *
+  *   PG_COPYRES_NO_EVENTPROCS - Indicates that the source result's
+  *   PGEventProcs should NOT be copied to the created result.
+  *
+  *   PG_COPYRES_NO_NOTICEHOOKS - Indicates that the source result's
+  *   NoticeHooks should NOT be copied to the created result.
+  */
+
+ PGresult *
+ PQcopyResult(const PGresult *src, int numAttributes,
+     PGresAttDesc *attDescs, int options)
+ {
+     int i;
+     PGresult *dest;
+     PGEventResultCopy info;
+
+     if(!src)
+         return NULL;
+
+     /* Automatically turn on no_tuples since use_attrs is set.  It makes
+      * no sense to copy tuples into an unknown set of columns.
+      */
+     if(options & PG_COPYRES_USE_ATTRS)
+         options |= PG_COPYRES_NO_TUPLES;
+
+     /* If use_attrs is set, verify attr arguments. */
+     if((options & PG_COPYRES_USE_ATTRS) && (numAttributes <= 0 || !attDescs))
+         return NULL;
+
+     dest = PQmakeEmptyPGresult((PGconn *)NULL, PGRES_TUPLES_OK);
+     if(!dest)
+         return NULL;
+
+     /* always copy these over.  Is cmdStatus useful here? */
+     dest->client_encoding = src->client_encoding;
+     strcpy(dest->cmdStatus, src->cmdStatus);
+
+     /* Wants to copy notice hooks */
+     if(!(options & PG_COPYRES_NO_NOTICEHOOKS))
+         dest->noticeHooks = src->noticeHooks;
+
+     /* Copy from src result when not supplying attrs */
+     if(!(options & PG_COPYRES_USE_ATTRS) && src->numAttributes > 0)
+     {
+         numAttributes = src->numAttributes;
+         attDescs = src->attDescs;
+     }
+
+     /* copy attrs */
+     if(numAttributes > 0)
+     {
+         dest->attDescs = (PGresAttDesc *)PQresultAlloc(dest,
+             numAttributes * sizeof(PGresAttDesc));
+
+         if(!dest->attDescs)
+         {
+             PQclear(dest);
+             return NULL;
+         }
+
+         dest->numAttributes = numAttributes;
+         memcpy(dest->attDescs, attDescs, numAttributes * sizeof(PGresAttDesc));
+
+         /* resultalloc the attribute names.  The above memcpy has the attr
+          * names pointing at the source result's private memory (or at the
+          * callers provided attDescs memory).
+          */
+         dest->binary = 1;
+         for(i=0; i < numAttributes; i++)
+         {
+             if(attDescs[i].name)
+                 dest->attDescs[i].name = pqResultStrdup(dest, attDescs[i].name);
+             else
+                 dest->attDescs[i].name = dest->null_field;
+
+             if(!dest->attDescs[i].name)
+             {
+                 PQclear(dest);
+                 return NULL;
+             }
+
+             /* Although deprecated, because results can have text+binary columns,
+              * its easy enough to deduce so set it for completeness.
+              */
+             if(dest->attDescs[i].format == 0)
+                 dest->binary = 0;
+         }
+     }
+
+     /* Wants to copy result tuples: use PQsetvalue(). */
+     if(!(options & PG_COPYRES_NO_TUPLES) && src->ntups > 0)
+     {
+         int tup, field;
+         for(tup=0; tup < src->ntups; tup++)
+             for(field=0; field < src->numAttributes; field++)
+                 PQsetvalue(dest, tup, field, src->tuples[tup][field].value,
+                     src->tuples[tup][field].len);
+     }
+
+     /* Wants to copy PGEventProcs. */
+     if(!(options & PG_COPYRES_NO_EVENTPROCS) && src->evtStateCount > 0)
+     {
+         dest->evtStates = dupEventStates(src->evtStates, src->evtStateCount);
+
+         if(!dest->evtStates)
+         {
+             PQclear(dest);
+             return NULL;
+         }
+
+         dest->evtStateCount = src->evtStateCount;
+     }
+
+     /* Trigger PGEVT_RESULTCOPY event for each event proc */
+     for(i=0; i < dest->evtStateCount; i++)
+     {
+         info.src = src;
+         info.srcData = src->evtStates[i].data;
+         info.dest = dest;
+         info.destData = NULL;
+
+         if(!src->evtStates[i].proc(PGEVT_RESULTCOPY, &info,
+             src->evtStates[i].arg))
+         {
+             PQclear(dest);
+             return NULL;
+         }
+
+         dest->evtStates[i].data = info.destData;
+     }
+
+     return dest;
+ }
+
+ int
+ PQsetvalue(PGresult *res, int tup_num, int field_num,
+     char *value, int len)
+ {
+     PGresAttValue *attval;
+
+     if(!check_field_number(res, field_num))
+         return FALSE;
+
+     /* Invalid tup_num, must be <= ntups */
+     if(tup_num > res->ntups)
+         return FALSE;
+
+     /* need to grow the tuple table */
+     if(res->ntups >= res->tupArrSize)
+     {
+         int n = res->tupArrSize ? (res->tupArrSize*3)/2 : 64;
+         PGresAttValue **tups = (PGresAttValue **)
+             (res->tuples ? realloc(res->tuples, n*sizeof(PGresAttValue *)) :
+              malloc(n*sizeof(PGresAttValue *)));
+
+         if(!tups)
+             return FALSE;
+
+         memset(tups + res->tupArrSize, 0,
+             (n - res->tupArrSize) * sizeof(PGresAttValue *));
+         res->tuples = tups;
+         res->tupArrSize = n;
+     }
+
+     /* need to allocate a new tuple */
+     if(tup_num == res->ntups && !res->tuples[tup_num])
+     {
+         int i;
+         PGresAttValue *tup = (PGresAttValue *)PQresultAlloc(
+             res, res->numAttributes * sizeof(PGresAttValue));
+
+         if(!tup)
+             return FALSE;
+
+         /* initialize each column to NULL */
+         for(i=0; i < res->numAttributes; i++)
+         {
+             tup[i].len = NULL_LEN;
+             tup[i].value = res->null_field;
+         }
+
+         res->tuples[tup_num] = tup;
+         res->ntups++;
+     }
+
+     attval = &res->tuples[tup_num][field_num];
+
+     /* On top of NULL_LEN, treat a NULL value as a NULL field */
+     if(len == NULL_LEN || value == NULL)
+     {
+         attval->len = NULL_LEN;
+         attval->value = res->null_field;
+     }
+     else
+     {
+         if(len < 0)
+             len = 0;
+
+         if(len == 0)
+         {
+             attval->len = 0;
+             attval->value = res->null_field;
+         }
+         else
+         {
+             attval->value = (char *)PQresultAlloc(res, len + 1);
+             if(!attval->value)
+                 return FALSE;
+
+             attval->len = len;
+             memcpy(attval->value, value, len);
+             attval->value[len] = '\0';
+         }
+     }
+
+     return TRUE;
+ }
+
+ void *
+ PQresultAlloc(PGresult *res, size_t nBytes)
+ {
+     return pqResultAlloc(res, nBytes, TRUE);
+ }
+
+ /*
   * pqResultAlloc -
   *        Allocate subsidiary storage for a PGresult.
   *
   * nBytes is the amount of space needed for the object.
   * If isBinary is true, we assume that we need to align the object on
   * a machine allocation boundary.
***************
*** 349,365 ****
--- 635,669 ----
   * PQclear -
   *      free's the memory associated with a PGresult
   */
  void
  PQclear(PGresult *res)
  {
+     int i;
      PGresult_data *block;
+     PGEventResultDestroy info;

      if (!res)
          return;

+     for(i=0; i < res->evtStateCount; i++)
+     {
+         info.result = res;
+         info.data = res->evtStates[i].data;
+
+         (void)res->evtStates[i].proc(PGEVT_RESULTDESTROY, &info,
+             res->evtStates[i].arg);
+     }
+
+     if(res->evtStates)
+     {
+         free(res->evtStates);
+         res->evtStates = NULL;
+         res->evtStateCount = 0;
+     }
+
      /* Free all the subsidiary blocks */
      while ((block = res->curBlock) != NULL)
      {
          res->curBlock = block->next;
          free(block);
      }
***************
*** 1190,1202 ****
   *      memory).
   */

  PGresult *
  PQgetResult(PGconn *conn)
  {
!     PGresult   *res;

      if (!conn)
          return NULL;

      /* Parse any available data, if our state permits. */
      parseInput(conn);
--- 1494,1506 ----
   *      memory).
   */

  PGresult *
  PQgetResult(PGconn *conn)
  {
!     PGresult   *res=NULL;

      if (!conn)
          return NULL;

      /* Parse any available data, if our state permits. */
      parseInput(conn);
***************
*** 1265,1276 ****
--- 1569,1611 ----
                                libpq_gettext("unexpected asyncStatus: %d\n"),
                                (int) conn->asyncStatus);
              res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR);
              break;
      }

+     if(res && (res->resultStatus == PGRES_COMMAND_OK ||
+          res->resultStatus == PGRES_TUPLES_OK ||
+          res->resultStatus == PGRES_EMPTY_QUERY))
+     {
+         int i;
+         PGEventResultCreate info;
+
+         info.conn = conn;
+         info.result = res;
+
+         for(i=0; i < res->evtStateCount; i++)
+         {
+             info.connData = conn->evtStates[i].data;
+             info.resultData = NULL;
+
+             if(!res->evtStates[i].proc(PGEVT_RESULTCREATE,
+                 &info, res->evtStates[i].arg))
+             {
+                 char msg[256];
+                 sprintf(msg,
+                     "PGEventProc %p failed during PGEVT_RESULTCREATE event",
+                     res->evtStates[i].proc);
+                 pqSetResultError(res, msg);
+                 res->resultStatus = PGRES_FATAL_ERROR;
+                 break;
+             }
+
+             res->evtStates[i].data = info.resultData;
+         }
+     }
+
      return res;
  }


  /*
   * PQexec
Index: fe-misc.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v
retrieving revision 1.133
diff -C6 -r1.133 fe-misc.c
*** fe-misc.c    1 Jan 2008 19:46:00 -0000    1.133
--- fe-misc.c    17 May 2008 12:06:12 -0000
***************
*** 1152,1157 ****
--- 1152,1301 ----
      }

      return dgettext("libpq", msgid);
  }

  #endif   /* ENABLE_NLS */
+
+
+ /* ---------------------
+  * Events
+  */
+
+ static int g_evtStateCount = 0;
+ static int g_evtStateSize = 0;
+ static PGEventState *g_evtStates = NULL;
+
+ int
+ PQregisterEventProc(PGconn *conn, PGEventProc proc, void *arg)
+ {
+     int i;
+     int *count;
+     int *size;
+     PGEventState **states;
+
+     if(!proc)
+         return FALSE;
+
+     /* global register */
+     if(!conn)
+     {
+         count = &g_evtStateCount;
+         size = &g_evtStateSize;
+         states = &g_evtStates;
+     }
+     /* per-conn */
+     else
+     {
+         count = &conn->evtStateCount;
+         size = &conn->evtStateSize;
+         states = &conn->evtStates;
+     }
+
+     for(i=0; i < *count; i++)
+         if((*states)[i].proc == proc)
+             return FALSE; /* already registered */
+
+     if(*count >= *size)
+     {
+         PGEventState *e;
+         int newSize = *size ? (*size * 3) / 2 : 8;
+
+         if(*states)
+             e = (PGEventState *)realloc(*states, newSize*sizeof(PGEventState));
+         else
+             e = (PGEventState *)malloc(newSize*sizeof(PGEventState));
+
+         if(!e)
+             return FALSE;
+
+         *size = newSize;
+         *states = e;
+     }
+
+     (*states)[*count].arg  = arg;
+     (*states)[*count].data = NULL;
+     (*states)[*count].proc = proc;
+
+     /* per-conn register requires triggering an initstate event */
+     if(conn)
+     {
+         PGEventInitState initState;
+         initState.data = NULL;
+         initState.conn = conn;
+         if(!proc(PGEVT_INITSTATE, &initState, arg))
+             return FALSE;
+         (*states)[*count].data = initState.data;
+     }
+
+     (*count)++;
+     return TRUE;
+ }
+
+ void *
+ PQeventData(const PGconn *conn, PGEventProc proc, void **arg)
+ {
+     int i;
+
+     if(arg)
+         *arg = NULL;
+
+     if(!conn || !proc)
+         return NULL;
+
+     for(i=0; i < conn->evtStateCount; i++)
+     {
+         if(conn->evtStates[i].proc == proc)
+         {
+             if(arg)
+                 *arg = conn->evtStates[i].arg;
+             return conn->evtStates[i].data;
+         }
+     }
+
+     return NULL;
+ }
+
+ void *
+ PQresultEventData(const PGresult *result, PGEventProc proc, void **arg)
+ {
+     int i;
+
+     if(arg)
+         *arg = NULL;
+
+     if(!result || !proc)
+         return NULL;
+
+     for(i=0; i < result->evtStateCount; i++)
+     {
+         if(result->evtStates[i].proc == proc)
+         {
+             if(arg)
+                 *arg = result->evtStates[i].arg;
+             return result->evtStates[i].data;
+         }
+     }
+
+     return NULL;
+ }
+
+ /* Registers the global events procs for a conn object.  If the procs
+  * were already registered, the request is ignored and a success value
+  * is returned.  This will happen during a PQreset and PQresetPoll.
+  * Returns non-zero for success and zero on error.
+  */
+ int
+ pqRegisterGlobalEvents(PGconn *conn)
+ {
+     int i;
+
+     /* Already registered, occurs on PQreset and PQresetPoll */
+     if(conn->evtStateCount > 0)
+         return TRUE;
+
+     for(i=0; i < g_evtStateCount; i++)
+         if(!PQregisterEventProc(conn, g_evtStates[i].proc, g_evtStates[i].arg))
+             return FALSE;
+
+     return TRUE;
+ }
Index: libpq-fe.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-fe.h,v
retrieving revision 1.142
diff -C6 -r1.142 libpq-fe.h
*** libpq-fe.h    19 Mar 2008 00:39:33 -0000    1.142
--- libpq-fe.h    17 May 2008 12:06:12 -0000
***************
*** 25,36 ****
--- 25,45 ----
  /*
   * postgres_ext.h defines the backend's externally visible types,
   * such as Oid.
   */
  #include "postgres_ext.h"

+ /* -----------------------
+  * Options for PQcopyResult
+  */
+
+ #define PG_COPYRES_NO_TUPLES      0x01
+ #define PG_COPYRES_USE_ATTRS      0x02
+ #define PG_COPYRES_NO_EVENTPROCS  0x04
+ #define PG_COPYRES_NO_NOTICEHOOKS 0x08
+
  /* Application-visible enum types */

  typedef enum
  {
      /*
       * Although it is okay to add to this list, values which become unused
***************
*** 190,201 ****
--- 199,283 ----
          int           *ptr;        /* can't use void (dec compiler barfs)     */
          int            integer;
      }            u;
  } PQArgBlock;

  /* ----------------
+  * PGresAttDesc -- Data about a single attribute (column) of a query result
+  * ----------------
+  */
+ typedef struct pgresAttDesc
+ {
+     char       *name;            /* column name */
+     Oid            tableid;        /* source table, if known */
+     int            columnid;        /* source column, if known */
+     int            format;            /* format code for value (text/binary) */
+     Oid            typid;            /* type id */
+     int            typlen;            /* type size */
+     int            atttypmod;  /* type-specific modifier info */
+ } PGresAttDesc;
+
+ /* ----------------
+  * Events - Allows receiving events from libpq.
+  * ----------------
+  */
+
+ /* Event Ids */
+ typedef enum
+ {
+     PGEVT_INITSTATE,
+     PGEVT_CONNRESET,
+     PGEVT_CONNDESTROY,
+     PGEVT_RESULTCREATE,
+     PGEVT_RESULTCOPY,
+     PGEVT_RESULTDESTROY
+ } PGEventId;
+
+ typedef struct
+ {
+     void *data; /* create/assign during this event if needed */
+     const PGconn *conn;
+ } PGEventInitState;
+
+ typedef struct
+ {
+     void *data;
+     const PGconn *conn;
+ } PGEventConnReset;
+
+ typedef struct
+ {
+     void *data;
+     const PGconn *conn;
+ } PGEventConnDestroy;
+
+ typedef struct
+ {
+     void *connData; /* conn's event data */
+     const PGconn *conn;
+     void *resultData; /* create/assign during this event if needed */
+     const PGresult *result;
+ } PGEventResultCreate;
+
+ typedef struct
+ {
+     void *srcData; /* src result's event data */
+     const PGresult *src;
+     void *destData; /* create/assign during this event if needed */
+     PGresult *dest;
+ } PGEventResultCopy;
+
+ typedef struct
+ {
+     void *data;
+     const PGresult *result;
+ } PGEventResultDestroy;
+
+ typedef int (*PGEventProc)(PGEventId evtId, void *evtInfo, void *arg);
+
+ /* ----------------
   * Exported functions of libpq
   * ----------------
   */

  /* ===    in fe-connect.c === */

***************
*** 434,445 ****
--- 516,546 ----
   * Make an empty PGresult with given status (some apps find this
   * useful). If conn is not NULL and status indicates an error, the
   * conn's errorMessage is copied.
   */
  extern PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status);

+ extern PGresult *
+ PQcopyResult(const PGresult *src, int numAttributes,
+     PGresAttDesc *attDescs, int options);
+
+ extern void *
+ PQresultAlloc(PGresult *res, size_t nBytes);
+
+ /*
+  * Sets the value for a tuple field.  The tup_num must be less than or
+  * equal to PQntuples(res).  This function will generate tuples as needed.
+  * A new tuple is generated when tup_num equals PQntuples(res) and there
+  * are no fields defined for that tuple.
+  *
+  * Returns a non-zero value for success and zero for failure.
+  */
+ extern int
+ PQsetvalue(PGresult *res, int tup_num, int field_num,
+     char *value, int len);
+

  /* Quoting strings before inclusion in queries. */
  extern size_t PQescapeStringConn(PGconn *conn,
                     char *to, const char *from, size_t length,
                     int *error);
  extern unsigned char *PQescapeByteaConn(PGconn *conn,
***************
*** 506,517 ****
--- 607,661 ----
  /* Determine display length of multibyte encoded char at *s */
  extern int    PQdsplen(const char *s, int encoding);

  /* Get encoding id from environment variable PGCLIENTENCODING */
  extern int    PQenv2encoding(void);

+ /*
+  * Registers an event proc with the given PGconn.  The arg parameter
+  * is an application specific pointer and can be set to NULL if not
+  * required.  The arg parameter will be passed to the event proc and
+  * is publically retrieveable via PQeventData or PQresultEventData.
+  *
+  * The proc parameter cannot be NULL.
+  *
+  * If the conn is NULL, this will register a global event proc.
+  * Global event procs are automatically registered on every PGconn
+  * created, removing the need for per-conn registration.
+  *
+  * WARNING: Global registrations should only be called from the application's
+  * main thread, prior to using any libpq functions and creating
+  * any application threads.  Global registration should be treated as an
+  * init function.
+  *
+  * The function returns a non-zero if successful.  If the function
+  * fails, zero is returned.
+  */
+ extern int
+ PQregisterEventProc(PGconn *conn, PGEventProc proc, void *arg);
+
+ /*
+  * Gets the event data created by the provided event proc for the given
+  * conn.  The event data is returned, which may be NULL if the event proc
+  * does not create any event data.  The arg parameter can be used to
+  * get the application specific pointer provided during event proc
+  * registration.  If arg is not needed, it can be set to NULL.
+  */
+ extern void *
+ PQeventData(const PGconn *conn, PGEventProc proc, void **arg);
+
+ /*
+  * Gets the event data created by the provided event proc for the given
+  * result.  The event data is returned, which may be NULL if the event proc
+  * does not create any event data.  The arg parameter can be used to
+  * get the application specific pointer provided during event proc
+  * registration.  If arg is not needed, it can be set to NULL.
+  */
+ extern void *
+ PQresultEventData(const PGresult *res, PGEventProc proc, void **arg);
+
  /* === in fe-auth.c === */

  extern char *PQencryptPassword(const char *passwd, const char *user);

  /* === in encnames.c === */

Index: libpq-int.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v
retrieving revision 1.130
diff -C6 -r1.130 libpq-int.h
*** libpq-int.h    16 May 2008 18:30:53 -0000    1.130
--- libpq-int.h    17 May 2008 12:06:12 -0000
***************
*** 97,121 ****
  union pgresult_data
  {
      PGresult_data *next;        /* link to next block, or NULL */
      char        space[1];        /* dummy for accessing block as bytes */
  };

- /* Data about a single attribute (column) of a query result */
-
- typedef struct pgresAttDesc
- {
-     char       *name;            /* column name */
-     Oid            tableid;        /* source table, if known */
-     int            columnid;        /* source column, if known */
-     int            format;            /* format code for value (text/binary) */
-     Oid            typid;            /* type id */
-     int            typlen;            /* type size */
-     int            atttypmod;        /* type-specific modifier info */
- } PGresAttDesc;
-
  /* Data about a single parameter of a prepared statement */
  typedef struct pgresParamDesc
  {
      Oid            typid;            /* type id */
  } PGresParamDesc;

--- 97,108 ----
***************
*** 159,170 ****
--- 146,166 ----
      PQnoticeReceiver noticeRec; /* notice message receiver */
      void       *noticeRecArg;
      PQnoticeProcessor noticeProc;        /* notice message processor */
      void       *noticeProcArg;
  } PGNoticeHooks;

+ typedef struct
+ {
+     /* pointer supplied by user */
+     void *arg;
+     /* state data, optionally generated by event proc */
+     void *data;
+     PGEventProc proc;
+ } PGEventState;
+
  struct pg_result
  {
      int            ntups;
      int            numAttributes;
      PGresAttDesc *attDescs;
      PGresAttValue **tuples;        /* each PGresTuple is an array of
***************
*** 181,192 ****
--- 177,192 ----
       * These fields are copied from the originating PGconn, so that operations
       * on the PGresult don't have to reference the PGconn.
       */
      PGNoticeHooks noticeHooks;
      int            client_encoding;    /* encoding id */

+     /* Event states */
+     int evtStateCount;
+     PGEventState *evtStates;
+
      /*
       * Error information (all NULL if not an error result).  errMsg is the
       * "overall" error message returned by PQresultErrorMessage.  If we have
       * per-field info then it is stored in a linked list.
       */
      char       *errMsg;            /* error message, or NULL if no error */
***************
*** 300,311 ****
--- 300,316 ----
      /* Optional file to write trace info to */
      FILE       *Pfdebug;

      /* Callback procedures for notice message processing */
      PGNoticeHooks noticeHooks;

+     /* Event states */
+     int evtStateCount;
+     int evtStateSize;
+     PGEventState *evtStates;
+
      /* Status indicators */
      ConnStatusType status;
      PGAsyncStatusType asyncStatus;
      PGTransactionStatusType xactStatus; /* never changes to ACTIVE */
      PGQueryClass queryclass;
      char       *last_query;        /* last SQL command, or NULL if unknown */
***************
*** 527,538 ****
--- 532,544 ----
  extern int    pqFlush(PGconn *conn);
  extern int    pqWait(int forRead, int forWrite, PGconn *conn);
  extern int pqWaitTimed(int forRead, int forWrite, PGconn *conn,
              time_t finish_time);
  extern int    pqReadReady(PGconn *conn);
  extern int    pqWriteReady(PGconn *conn);
+ extern int pqRegisterGlobalEvents(PGconn *conn);

  /* === in fe-secure.c === */

  extern int    pqsecure_initialize(PGconn *);
  extern void pqsecure_destroy(void);
  extern PostgresPollingStatusType pqsecure_open_client(PGconn *);

pgsql-patches by date:

Previous
From: Simon Riggs
Date:
Subject: Re: [HACKERS] TRUNCATE TABLE with IDENTITY
Next
From: Tom Lane
Date:
Subject: Re: [HACKERS] TRUNCATE TABLE with IDENTITY