libpq patch for pqtypes hook api and PGresult creation - Mailing list pgsql-patches

From Andrew Chernow
Subject libpq patch for pqtypes hook api and PGresult creation
Date
Msg-id 47FFA44E.2070204@esilo.com
Whole thread Raw
Responses Re: libpq patch for pqtypes hook api and PGresult creation  ("Merlin Moncure" <mmoncure@gmail.com>)
List pgsql-patches
Here are the changes to libpq.  It adds the ability to register an
Object Hook and create a home-grown result.  Patch adds 4 functions.

We changed the name of PQresultSetFieldValue to PQsetvalue, which better
compliments PQgetvalue.  If this patch is acceptable, we will move on to
making the required changes to pqtypes; some work has already been done.

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/
Index: src/interfaces/libpq/exports.txt
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/exports.txt,v
retrieving revision 1.19
diff -c -r1.19 exports.txt
*** src/interfaces/libpq/exports.txt    19 Mar 2008 00:39:33 -0000    1.19
--- src/interfaces/libpq/exports.txt    11 Apr 2008 17:36:26 -0000
***************
*** 141,143 ****
--- 141,147 ----
  pg_valid_server_encoding_id 139
  PQconnectionNeedsPassword 140
  lo_import_with_oid          141
+ PQaddObjectHook           142
+ PQremoveObjectHook        143
+ PQmakeResult              144
+ PQsetvalue                145
\ No newline at end of file
Index: src/interfaces/libpq/fe-connect.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v
retrieving revision 1.357
diff -c -r1.357 fe-connect.c
*** src/interfaces/libpq/fe-connect.c    31 Mar 2008 02:43:14 -0000    1.357
--- src/interfaces/libpq/fe-connect.c    11 Apr 2008 17:36:26 -0000
***************
*** 866,872 ****
--- 866,887 ----
       * we are in PGRES_POLLING_WRITING connection state.
       */
      if (PQconnectPoll(conn) == PGRES_POLLING_WRITING)
+     {
+         int objHooksCount;
+         const PGobjectHooks *objHooks;
+
+         if((objHooksCount = pqGetObjectHooks(&objHooks)) > 0)
+         {
+             int i;
+
+             for(i=0; i < objHooksCount; i++)
+                 if(objHooks[i].connCreate)
+                     objHooks[i].connCreate(objHooks[i].name, conn);
+         }
+         pqReleaseObjectHooks();
+
          return 1;
+     }

  connect_errReturn:
      if (conn->sock >= 0)
***************
*** 1973,1978 ****
--- 1988,2006 ----
  static void
  freePGconn(PGconn *conn)
  {
+     int objHooksCount;
+     const PGobjectHooks *objHooks;
+
+     if((objHooksCount = pqGetObjectHooks(&objHooks)) > 0)
+     {
+         int i;
+
+         for(i=0; i < objHooksCount; i++)
+             if(objHooks[i].connDestroy)
+                 objHooks[i].connDestroy(objHooks[i].name, conn);
+     }
+     pqReleaseObjectHooks();
+
      if (conn->pghost)
          free(conn->pghost);
      if (conn->pghostaddr)
Index: src/interfaces/libpq/fe-exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v
retrieving revision 1.194
diff -c -r1.194 fe-exec.c
*** src/interfaces/libpq/fe-exec.c    1 Jan 2008 19:46:00 -0000    1.194
--- src/interfaces/libpq/fe-exec.c    11 Apr 2008 17:36:27 -0000
***************
*** 63,69 ****
  static PGresult *PQexecFinish(PGconn *conn);
  static int PQsendDescribe(PGconn *conn, char desc_type,
                 const char *desc_target);
!

  /* ----------------
   * Space management for PGresult.
--- 63,69 ----
  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.
***************
*** 139,144 ****
--- 139,146 ----
  PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
  {
      PGresult   *result;
+     int objHooksCount;
+     const PGobjectHooks *objHooks;

      result = (PGresult *) malloc(sizeof(PGresult));
      if (!result)
***************
*** 192,200 ****
--- 194,341 ----
          result->client_encoding = PG_SQL_ASCII;
      }

+     if((objHooksCount = pqGetObjectHooks(&objHooks)) > 0)
+     {
+         int i;
+
+         for(i=0; i < objHooksCount; i++)
+             if(objHooks[i].resultCreate)
+                 objHooks[i].resultCreate(objHooks[i].name, conn, result);
+     }
+     pqReleaseObjectHooks();
+
      return result;
  }

+ PGresult *PQmakeResult(
+   PGconn *conn,
+   int numAttributes,
+   PGresAttDesc *attDescs)
+ {
+     int i;
+     PGresult *res;
+
+     if(numAttributes <= 0 || !attDescs)
+         return NULL;
+
+     res = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK);
+     if(!res)
+         return NULL;
+
+     res->attDescs = (PGresAttDesc *)
+         pqResultAlloc(res, numAttributes * sizeof(PGresAttDesc), TRUE);
+
+     if(!res->attDescs)
+     {
+         PQclear(res);
+         return NULL;
+     }
+
+     res->numAttributes = numAttributes;
+     memcpy(res->attDescs, attDescs, numAttributes * sizeof(PGresAttDesc));
+
+     /* resultalloc the attribute names. */
+     res->binary = 1;
+     for(i=0; i < numAttributes; i++)
+     {
+         if(attDescs[i].name)
+             res->attDescs[i].name = pqResultStrdup(res, attDescs[i].name);
+         else
+             res->attDescs[i].name = res->null_field;
+
+         if(!res->attDescs[i].name)
+         {
+             PQclear(res);
+             return NULL;
+         }
+
+         /* Although deprecated, because results can have text+binary columns,
+          * its easy enough to deduce so set it for completeness.
+          */
+         if(res->attDescs[i].format == 0)
+             res->binary = 0;
+     }
+
+     return res;
+ }
+
+ 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;
+
+         res->tuples = tups;
+         res->tupArrSize = n;
+     }
+
+     /* new 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), TRUE);
+
+         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;
+
+         attval->value = (char *)pqResultAlloc(res, len + 1, TRUE);
+         if(!attval->value)
+             return FALSE;
+
+         attval->len = len;
+         memcpy(attval->value, value, len);
+         attval->value[len] = '\0';
+     }
+
+     return TRUE;
+ }
+
  /*
   * pqResultAlloc -
   *        Allocate subsidiary storage for a PGresult.
***************
*** 353,362 ****
--- 494,515 ----
  PQclear(PGresult *res)
  {
      PGresult_data *block;
+     int objHooksCount;
+     const PGobjectHooks *objHooks;

      if (!res)
          return;

+     if((objHooksCount = pqGetObjectHooks(&objHooks)) > 0)
+     {
+         int i;
+
+         for(i=0; i < objHooksCount; i++)
+             if(objHooks[i].resultDestroy)
+                 objHooks[i].resultDestroy(objHooks[i].name, res);
+     }
+     pqReleaseObjectHooks();
+
      /* Free all the subsidiary blocks */
      while ((block = res->curBlock) != NULL)
      {
Index: src/interfaces/libpq/fe-misc.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v
retrieving revision 1.133
diff -c -r1.133 fe-misc.c
*** src/interfaces/libpq/fe-misc.c    1 Jan 2008 19:46:00 -0000    1.133
--- src/interfaces/libpq/fe-misc.c    11 Apr 2008 17:36:27 -0000
***************
*** 1155,1157 ****
--- 1155,1332 ----
  }

  #endif   /* ENABLE_NLS */
+
+
+ /* ---------------------
+  * ObjectHooks
+  */
+
+ #ifdef ENABLE_THREAD_SAFETY
+ #    ifdef WIN32
+ #        include "pthread-win32.h"
+         static pthread_mutex_t objHookMutex = NULL;
+ #    else
+ #        include <pthread.h>
+         static pthread_mutex_t objHookMutex = PTHREAD_MUTEX_INITIALIZER;
+ #    endif
+ #endif
+
+ /* Object Hook array management variables.  The hooks array is
+  * dynamically grown as needed.  When removing an element, the
+  * element is memmove'd out.
+  */
+ static int objHooksCount = 0;
+ static int objHooksArrSize = 0;
+ static PGobjectHooks *objHooks = NULL;
+
+ int PQaddObjectHook(PGobjectHooks *hook, char **errMsg)
+ {
+     int i;
+
+     if(!hook)
+     {
+         if(errMsg)
+             *errMsg = "ObjectHook pointer cannot be NULL";
+         return FALSE;
+     }
+
+     /* hook names must be provided */
+     if(!hook->name || !*hook->name)
+     {
+         if(errMsg)
+             *errMsg = "ObjectHook name cannot be NULL or an empty string";
+         return FALSE;
+     }
+
+     /* At least one hook func must be provided (pointless otherwise) */
+     if(!hook->connCreate && !hook->connDestroy &&
+          !hook->resultCreate && !hook->resultDestroy)
+     {
+         if(errMsg)
+             *errMsg = "At least one ObjectHook function must be provided";
+         return FALSE;
+     }
+
+     (void)pqGetObjectHooks(NULL);
+
+     /* hook names must be unique, case ignored. */
+     for(i=0; i < objHooksCount; i++)
+     {
+         if(pg_strcasecmp(objHooks[i].name, hook->name)==0)
+         {
+             pqReleaseObjectHooks();
+             if(errMsg)
+                 *errMsg = "ObjectHook name already exists";
+             return FALSE;
+         }
+     }
+
+     /* grow objHooks array */
+     if(objHooksCount == objHooksArrSize)
+     {
+         int n = objHooksArrSize ? (objHooksArrSize*3)/2 : 4;
+
+         objHooks = (PGobjectHooks *)malloc(sizeof(PGobjectHooks) * n);
+         if(!objHooks)
+         {
+             if(errMsg)
+                 *errMsg = "Out of memory error";
+             return FALSE;
+         }
+
+         objHooksArrSize = n;
+     }
+
+     memcpy(objHooks + objHooksCount, hook, sizeof(PGobjectHooks));
+     objHooks[objHooksCount++].name = strdup(hook->name);
+
+     pqReleaseObjectHooks();
+
+     if(errMsg)
+         *errMsg = "";
+     return TRUE;
+ }
+
+ /* Removes a hook from the global hooks array. */
+ void PQremoveObjectHook(const char *name)
+ {
+     int i;
+
+     if(!name || !*name)
+         return;
+
+     (void)pqGetObjectHooks(NULL);
+
+     for(i=0; i < objHooksCount; i++)
+     {
+         if(pg_strcasecmp(objHooks[i].name, name)==0)
+         {
+             free((void *)objHooks[i].name);
+             memmove(objHooks + i, objHooks + i + 1,
+                 (objHooksCount - i - 1) * sizeof(PGobjectHooks));
+             objHooksCount--;
+             break;
+         }
+     }
+
+     pqReleaseObjectHooks();
+ }
+
+ /* Gets the object hooks array, storing the array at *hooks and returning
+  * the number of elements in the array.  The hooks array should be
+  * treated as read-only memory.
+  *
+  * When ENABLE_THREAD_SAFETY is set, this will lock a private mutex
+  * before returning.  To unlock the hooks, call pqReleaseObjectHooks.
+  * Warning, you must call pqReleaseObjectHooks for each call to
+  * pqGetObjectHooks.
+  *
+  *   const PGobjectHooks *hooks;
+  *   int count = pqGetObjectHooks(&hooks);
+  *   // ... do some work with hooks
+  *   pqReleaseObjectHooks();
+  *
+  * If the provided hooks argument is NULL, the internal mutex is still
+  * locked and the function returns the number of hooks registered.
+  * You still have to call pqReleaseObjectHooks.
+  *
+  * If there are no hooks registered, *hooks will be NULL and the
+  * function returns 0.
+  */
+ int pqGetObjectHooks(const PGobjectHooks **hooks)
+ {
+ #ifdef ENABLE_THREAD_SAFETY
+ #    ifdef WIN32
+     static long initlock = 0;
+
+     /* copied from init_ssl_system */
+     if(objHookMutex == NULL)
+     {
+         while (InterlockedExchange(&initlock, 1) == 1)
+              /* loop, another thread own the lock */ ;
+         if (objHookMutex == NULL)
+             pthread_mutex_init(&objHookMutex, NULL);
+         InterlockedExchange(&initlock, 0);
+     }
+ #    endif
+
+     pthread_mutex_lock(&objHookMutex);
+ #endif
+
+     if(hooks)
+         *hooks = (const PGobjectHooks *)objHooks;
+     return objHooksCount;
+ }
+
+ /* Must be called after pqGetObjectHooks, so the mutex can be unlocked
+  * when ENABLE_THREAD_SAFETY is set.
+  */
+ void pqReleaseObjectHooks(void)
+ {
+ #ifdef ENABLE_THREAD_SAFETY
+     pthread_mutex_unlock(&objHookMutex);
+ #endif
+ }
+
+
+
Index: src/interfaces/libpq/libpq-fe.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-fe.h,v
retrieving revision 1.142
diff -c -r1.142 libpq-fe.h
*** src/interfaces/libpq/libpq-fe.h    19 Mar 2008 00:39:33 -0000    1.142
--- src/interfaces/libpq/libpq-fe.h    11 Apr 2008 17:36:27 -0000
***************
*** 193,198 ****
--- 193,236 ----
  } 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;
+
+ /* ----------------
+  * PGobjectHooks -- structure for adding libpq object hooks
+  * Monitors the creation and deletion of objects.
+  * ----------------
+  */
+
+ typedef struct
+ {
+     const char *name;
+
+     /* Invoked on PQconnectdb, PQsetdbLogin, PQconnectStart or PQreset */
+     int (*connCreate)(const char *hookName, PGconn *conn);
+
+     /* Invoked on PQfinish. */
+     int (*connDestroy)(const char *hookName, PGconn *conn);
+
+     /* Invoked when PQmakeEmptyResult is called */
+     int (*resultCreate)(const char *hookName, PGconn *conn, PGresult *result);
+
+     /* Invoked when PQclear is called */
+     int (*resultDestroy)(const char *hookName, PGresult *result);
+ } PGobjectHooks;
+
+ /* ----------------
   * Exported functions of libpq
   * ----------------
   */
***************
*** 437,442 ****
--- 475,500 ----
   */
  extern PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status);

+ /*
+  * Makes a new result using the provided field descriptors.  If conn is
+  * not NULL, some information will be copied to the new result.
+  * The returned result has zero tuples and a resultStatus of PGRES_TUPLES_OK.
+  * To add tuples and set field values, see PQsetvalue.
+  */
+ extern PGresult *PQmakeResult(PGconn *conn, int numAttributes,
+   PGresAttDesc *attDescs);
+
+ /*
+  * 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,
***************
*** 509,514 ****
--- 567,581 ----
  /* Get encoding id from environment variable PGCLIENTENCODING */
  extern int    PQenv2encoding(void);

+ /* Add a libpq global Object Hook.  If errMsg is not NULL, it will
+  * be set to an error message if the function fails.  This returns
+  * non-zero on success and zero on failure.
+  */
+ extern int PQaddObjectHook(PGobjectHooks *hook, char **errMsg);
+
+ /* Removes an Object Hook previously added with PQaddObjectHook */
+ extern void PQremoveObjectHook(const char *name);
+
  /* === in fe-auth.c === */

  extern char *PQencryptPassword(const char *passwd, const char *user);
Index: src/interfaces/libpq/libpq-int.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v
retrieving revision 1.129
diff -c -r1.129 libpq-int.h
*** src/interfaces/libpq/libpq-int.h    1 Jan 2008 19:46:00 -0000    1.129
--- src/interfaces/libpq/libpq-int.h    11 Apr 2008 17:36:27 -0000
***************
*** 100,118 ****
      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
  {
--- 100,105 ----
***************
*** 205,210 ****
--- 192,198 ----
      int            spaceLeft;        /* number of free bytes remaining in block */
  };

+
  /* PGAsyncStatusType defines the state of the query-execution state machine */
  typedef enum
  {
***************
*** 524,529 ****
--- 512,522 ----
  extern int    pqReadReady(PGconn *conn);
  extern int    pqWriteReady(PGconn *conn);

+ /* === in objhooks.c === */
+
+ extern int pqGetObjectHooks(const PGobjectHooks **hooks);
+ extern void pqReleaseObjectHooks(void);
+
  /* === in fe-secure.c === */

  extern int    pqsecure_initialize(PGconn *);

pgsql-patches by date:

Previous
From: Bruce Momjian
Date:
Subject: Re: Typo in README
Next
From: "Merlin Moncure"
Date:
Subject: Re: libpq patch for pqtypes hook api and PGresult creation