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	30 Apr 2008 20:05:25 -0000
***************
*** 138,143 ****
--- 138,150 ----
  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
+ PQaddObjectHooks          145
+ PQaddGlobalObjectHooks    146
+ PQhookData                147
+ PQresultHookData          148
\ No newline at end of file
Index: fe-connect.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v
retrieving revision 1.357
diff -C6 -r1.357 fe-connect.c
*** fe-connect.c	31 Mar 2008 02:43:14 -0000	1.357
--- fe-connect.c	30 Apr 2008 20:05:25 -0000
***************
*** 241,253 ****
  				 PQExpBuffer errorMessage);
  static char *pwdfMatchesString(char *buf, char *token);
  static char *PasswordFromFile(char *hostname, char *port, char *dbname,
  				 char *username);
  static void default_threadlock(int acquire);
  
- 
  /* global variable because fe-auth.c needs to access it */
  pgthreadlock_t pg_g_threadlock = default_threadlock;
  
  
  /*
   *		Connecting to a Database
--- 241,252 ----
***************
*** 979,990 ****
--- 978,990 ----
   *	 o	If your backend wants to use Kerberos authentication then you must
   *		supply both a host name and a host address, otherwise this function
   *		may block on gethostname.
   *
   * ----------------
   */
+ 
  PostgresPollingStatusType
  PQconnectPoll(PGconn *conn)
  {
  	PGresult   *res;
  	char		sebuf[256];
  
***************
*** 998,1009 ****
--- 998,1010 ----
  			 * 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:
+ 			pqInitObjectHooks(conn);
  			return PGRES_POLLING_OK;
  
  			/* These are reading states */
  		case CONNECTION_AWAITING_RESPONSE:
  		case CONNECTION_AUTH_OK:
  			{
***************
*** 1816,1827 ****
--- 1817,1829 ----
  					conn->next_eo = EnvironmentOptions;
  					return PGRES_POLLING_WRITING;
  				}
  
  				/* Otherwise, we are open for business! */
  				conn->status = CONNECTION_OK;
+ 				pqInitObjectHooks(conn);
  				return PGRES_POLLING_OK;
  			}
  
  		case CONNECTION_SETENV:
  
  			/*
***************
*** 1848,1859 ****
--- 1850,1862 ----
  				default:
  					goto error_return;
  			}
  
  			/* We are open for business! */
  			conn->status = CONNECTION_OK;
+ 			pqInitObjectHooks(conn);
  			return PGRES_POLLING_OK;
  
  		default:
  			printfPQExpBuffer(&conn->errorMessage,
  							  libpq_gettext(
  											"invalid connection state %c, "
***************
*** 1875,1887 ****
  	 * the connection structure must be freed).
  	 */
  	conn->status = CONNECTION_BAD;
  	return PGRES_POLLING_FAILED;
  }
  
- 
  /*
   * makeEmptyPGconn
   *	 - create a PGconn data structure with (as yet) no interesting data
   */
  static PGconn *
  makeEmptyPGconn(void)
--- 1878,1889 ----
***************
*** 1970,1981 ****
--- 1972,2001 ----
   * 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;
+ 
+ 	for(i=0; i < conn->objHooksCount; i++)
+ 	{
+ 		if(conn->objHooks[i].connDestroy)
+ 		{
+ 			conn->objHooks[i].connDestroy((const PGconn *)conn);
+ 			free(conn->objHooks[i].name);
+ 		}
+ 	}
+ 
+ 	if(conn->objHooks)
+ 	{
+ 		free(conn->objHooks);
+ 		conn->objHooks = NULL;
+ 		conn->objHooksCount = conn->objHooksSize = 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:
--- 2171,2189 ----
  PQreset(PGconn *conn)
  {
  	if (conn)
  	{
  		closePGconn(conn);
  
! 		if (connectDBStart(conn) && connectDBComplete(conn))
! 		{
! 			int i;
! 			for(i=0; i < conn->objHooksCount; i++)
! 				if(conn->objHooks[i].connReset)
! 					conn->objHooks[i].connReset((const PGconn *)conn);
! 		}
  	}
  }
  
  
  /*
   * PQresetStart:
***************
*** 2176,2198 ****
  		return connectDBStart(conn);
  	}
  
  	return 0;
  }
  
- 
  /*
   * PQresetPoll:
   * resets the connection to the backend
   * 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.
--- 2201,2234 ----
  		return connectDBStart(conn);
  	}
  
  	return 0;
  }
  
  /*
   * PQresetPoll:
   * resets the connection to the backend
   * 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;
! 			for(i=0; i < conn->objHooksCount; i++)
! 				if(conn->objHooks[i].connReset)
! 					conn->objHooks[i].connReset((const PGconn *)conn);
! 		}
! 
! 		return status;
! 	}
  
  	return PGRES_POLLING_FAILED;
  }
  
  /*
   * PQcancelGet: get a PGcancel structure corresponding to a connection.
***************
*** 3855,3860 ****
--- 3891,3897 ----
  		pg_g_threadlock = newhandler;
  	else
  		pg_g_threadlock = default_threadlock;
  
  	return prev;
  }
+ 
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	30 Apr 2008 20:05:26 -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,170 ----
  #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 hook data, sets this to NULL */
+ static PGobjectHooks *
+ dupObjectHooks(PGobjectHooks *hooks, int count)
+ {
+ 	int i;
+ 	PGobjectHooks *newHooks;
+ 
+ 	if(!hooks || count <= 0)
+ 		return NULL;
+ 
+ 	newHooks = (PGobjectHooks *)malloc(count * sizeof(PGobjectHooks));
+ 	if(!newHooks)
+ 		return NULL;
+ 
+ 	memcpy(newHooks, hooks, count * sizeof(PGobjectHooks));
+ 
+ 	/* NULL out the hook data pointer */
+ 	for(i=0; i < count; i++)
+ 	{
+ 		newHooks[i].data = NULL;
+ 		newHooks[i].name = strdup(hooks[i].name);
+ 	}
+ 
+ 	return newHooks;
+ }
+ 
  /*
   * 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, ObjectHooks will be copied
+  * from the PGconn to the created PGresult.
   */
  
  PGresult *
  PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
  {
  	PGresult   *result;
***************
*** 157,175 ****
--- 186,219 ----
  	result->errMsg = NULL;
  	result->errFields = NULL;
  	result->null_field[0] = '\0';
  	result->curBlock = NULL;
  	result->curOffset = 0;
  	result->spaceLeft = 0;
+ 	result->objHooksCount = 0;
+ 	result->objHooks = NULL;
  
  	if (conn)
  	{
  		/* copy connection data we might need for operations on PGresult */
  		result->noticeHooks = conn->noticeHooks;
  		result->client_encoding = conn->client_encoding;
  
+ 		/* copy object hooks from connection */
+ 		if(conn->objHooksCount > 0)
+ 		{
+ 			result->objHooks = dupObjectHooks(conn->objHooks, conn->objHooksCount);
+ 			if(!result->objHooks)
+ 			{
+ 				PQclear(result);
+ 				return NULL;
+ 			}
+ 
+ 			result->objHooksCount = conn->objHooksCount;
+ 		}
+ 
  		/* consider copying conn's errorMessage */
  		switch (status)
  		{
  			case PGRES_EMPTY_QUERY:
  			case PGRES_COMMAND_OK:
  			case PGRES_TUPLES_OK:
***************
*** 193,204 ****
--- 237,476 ----
  	}
  
  	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_OBJECTHOOKS - Indicates that the source result's
+  *   ObjectHooks 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;
+ 
+ 	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 objHooks. */
+ 	if(!(options & PG_COPYRES_NO_OBJECTHOOKS) && src->objHooksCount > 0)
+ 	{
+ 		dest->objHooks = dupObjectHooks(src->objHooks, src->objHooksCount);
+ 		if(!dest->objHooks)
+ 		{
+ 			PQclear(dest);
+ 			return NULL;
+ 		}
+ 
+ 		dest->objHooksCount = src->objHooksCount;
+ 	}
+ 
+ 	/* Invoke resultCopy for each Object Hook */
+ 	for(i=0; i < dest->objHooksCount; i++)
+ 		if(dest->objHooks[i].resultCopy)
+ 			dest->objHooks[i].data = dest->objHooks[i].resultCopy(dest, src);
+ 
+ 	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 ****
--- 621,654 ----
   * PQclear -
   *	  free's the memory associated with a PGresult
   */
  void
  PQclear(PGresult *res)
  {
+ 	int i;
  	PGresult_data *block;
  
  	if (!res)
  		return;
  
+ 	for(i=0; i < res->objHooksCount; i++)
+ 	{
+ 		if(res->objHooks[i].resultDestroy)
+ 		{
+ 			res->objHooks[i].resultDestroy((const PGresult *)res);
+ 			free(res->objHooks[i].name);
+ 		}
+ 	}
+ 
+ 	if(res->objHooks)
+ 	{
+ 		free(res->objHooks);
+ 		res->objHooks = NULL;
+ 		res->objHooksCount = 0;
+ 	}
+ 
  	/* Free all the subsidiary blocks */
  	while ((block = res->curBlock) != NULL)
  	{
  		res->curBlock = block->next;
  		free(block);
  	}
***************
*** 1179,1202 ****
  	parseInput(conn);
  
  	/* PQgetResult will return immediately in all states except BUSY. */
  	return conn->asyncStatus == PGASYNC_BUSY;
  }
  
- 
  /*
   * PQgetResult
   *	  Get the next PGresult produced by a query.  Returns NULL if no
   *	  query work remains or an error has occurred (e.g. out of
   *	  memory).
   */
  
  PGresult *
  PQgetResult(PGconn *conn)
  {
! 	PGresult   *res;
  
  	if (!conn)
  		return NULL;
  
  	/* Parse any available data, if our state permits. */
  	parseInput(conn);
--- 1468,1490 ----
  	parseInput(conn);
  
  	/* PQgetResult will return immediately in all states except BUSY. */
  	return conn->asyncStatus == PGASYNC_BUSY;
  }
  
  /*
   * PQgetResult
   *	  Get the next PGresult produced by a query.  Returns NULL if no
   *	  query work remains or an error has occurred (e.g. out of
   *	  memory).
   */
  
  PGresult *
  PQgetResult(PGconn *conn)
  {
! 	PGresult   *res=NULL;
  
  	if (!conn)
  		return NULL;
  
  	/* Parse any available data, if our state permits. */
  	parseInput(conn);
***************
*** 1265,1276 ****
--- 1553,1574 ----
  							  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))
+ 	{
+ 		int i;
+ 		for(i=0; i < res->objHooksCount; i++)
+ 			if(res->objHooks[i].resultCreate)
+ 				res->objHooks[i].data = res->objHooks[i].resultCreate(
+ 					(const PGconn *)conn, (const PGresult *)res);
+ 	}
+ 
  	return res;
  }
  
  
  /*
   * PQexec
***************
*** 1291,1303 ****
  	if (!PQsendQuery(conn, query))
  		return NULL;
  	return PQexecFinish(conn);
  }
  
  /*
!  * PQexecParams
   *		Like PQexec, but use protocol 3.0 so we can pass parameters
   */
  PGresult *
  PQexecParams(PGconn *conn,
  			 const char *command,
  			 int nParams,
--- 1589,1601 ----
  	if (!PQsendQuery(conn, query))
  		return NULL;
  	return PQexecFinish(conn);
  }
  
  /*
!  *
   *		Like PQexec, but use protocol 3.0 so we can pass parameters
   */
  PGresult *
  PQexecParams(PGconn *conn,
  			 const char *command,
  			 int nParams,
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	30 Apr 2008 20:05:26 -0000
***************
*** 1152,1157 ****
--- 1152,1302 ----
  	}
  
  	return dgettext("libpq", msgid);
  }
  
  #endif   /* ENABLE_NLS */
+ 
+ 
+ /* ---------------------
+  * ObjectHooks
+  */
+ 
+ /* global object hooks, see PQaddGlobalObjectHooks */
+ static int g_objHooksCount = 0;
+ static int g_objHooksSize = 0;
+ static PGobjectHooks *g_objHooks = NULL;
+ 
+ static int
+ hookExists(PGobjectHooks *objHooks, int objHooksCount, const char *name)
+ {
+ 	int i;
+ 	for(i=0; i < objHooksCount; i++)
+ 		if(pg_strcasecmp(objHooks[i].name, name)==0)
+ 			return TRUE;
+ 	return FALSE;
+ }
+ 
+ static int
+ growHooks(PGobjectHooks **objHooks, int *objHooksSize,
+ 	int objHooksCount)
+ {
+ 	/* grow list */
+ 	if(objHooksCount == *objHooksSize)
+ 	{
+ 		PGobjectHooks *oh;
+ 		int n = *objHooksSize ? (*objHooksSize*3)/2 : 4;
+ 
+ 		if(*objHooks)
+ 			oh = (PGobjectHooks *)realloc(
+ 				*objHooks, n*sizeof(PGobjectHooks));
+ 		else
+ 			oh = (PGobjectHooks *)malloc(n*sizeof(PGobjectHooks));
+ 
+ 		if(!oh)
+ 			return FALSE;
+ 
+ 		*objHooks = oh;
+ 		*objHooksSize = n;
+ 	}
+ 
+ 	return TRUE;
+ }
+ 
+ static int
+ hookCopy(PGobjectHooks *dst, PGobjectHooks *src)
+ {
+ 	memcpy(dst, src, sizeof(PGobjectHooks));
+ 	dst->data = NULL;
+ 	dst->name = strdup(src->name);
+ 	return dst->name ? TRUE : FALSE;
+ }
+ 
+ int
+ PQaddGlobalObjectHooks(PGobjectHooks *hooks)
+ {
+ 	if(!hooks || !hooks->name || !*hooks->name)
+ 		return FALSE;
+ 
+ 	if(hookExists(g_objHooks, g_objHooksCount, hooks->name) ||
+ 		 !growHooks(&g_objHooks, &g_objHooksSize, g_objHooksCount) ||
+ 		 !hookCopy(g_objHooks + g_objHooksCount, hooks))
+ 		return FALSE;
+ 
+ 	g_objHooksCount++;
+ 	return TRUE;
+ }
+ 
+ /* inits the global hooks for a conn object.  If the hooks were already
+  * installed, 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
+ pqInitObjectHooks(PGconn *conn)
+ {
+ 	int i;
+ 
+ 	/* Already installed, occurs on PQreset and PQresetPoll */
+ 	if(conn->objHooksCount > 0)
+ 		return TRUE;
+ 
+ 	for(i=0; i < g_objHooksCount; i++)
+ 		if(!PQaddObjectHooks(conn, &g_objHooks[i]))
+ 			return FALSE;
+ 
+ 	return TRUE;
+ }
+ 
+ int
+ PQaddObjectHooks(PGconn *conn, PGobjectHooks *hooks)
+ {
+ 	PGobjectHooks *oh;
+ 
+ 	if(!conn || !hooks || !hooks->name || !*hooks->name)
+ 		return FALSE;
+ 
+ 	/* names must be unique */
+ 	if(hookExists(conn->objHooks, conn->objHooksCount, hooks->name) ||
+ 		 !growHooks(&conn->objHooks, &conn->objHooksSize, conn->objHooksCount) ||
+ 		 !hookCopy(conn->objHooks + conn->objHooksCount, hooks))
+ 		return FALSE;
+ 
+ 	oh = &conn->objHooks[conn->objHooksCount++];
+ 	if(oh->initHookData)
+ 		oh->data = oh->initHookData((const PGconn *)conn);
+ 
+ 	return TRUE;
+ }
+ 
+ void *
+ PQhookData(const PGconn *conn, const char *hookName)
+ {
+ 	if(conn && hookName && *hookName)
+ 	{
+ 		int i;
+ 		PGobjectHooks *hooks = conn->objHooks;
+ 
+ 		for(i=0; i < conn->objHooksCount; i++)
+ 			if(pg_strcasecmp(hooks[i].name, hookName)==0)
+ 				return hooks[i].data;
+ 	}
+ 
+ 	return NULL;
+ }
+ 
+ void *
+ PQresultHookData(const PGresult *res, const char *hookName)
+ {
+ 	if(res && hookName && *hookName)
+ 	{
+ 		int i;
+ 		PGobjectHooks *hooks = res->objHooks;
+ 
+ 		for(i=0; i < res->objHooksCount; i++)
+ 			if(pg_strcasecmp(hooks[i].name, hookName)==0)
+ 				return hooks[i].data;
+ 	}
+ 
+ 	return NULL;
+ }
+ 
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	30 Apr 2008 20:05:26 -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_OBJECTHOOKS 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,260 ----
  		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;
+ 
+ /* ----------------
+  * PGobjectHooks -- structure for adding libpq object hooks
+  * Monitors the creation and deletion of objects.
+  * ----------------
+  */
+ 
+ typedef struct
+ {
+ 	char *name;
+ 
+ 	void *data;
+ 
+ 	/* Invoked when PQsetObjectHook is called.  The pointer returned
+ 	 * by the hook implementation is stored in the private storage of
+ 	 * the PGconn, accessible via PQhookData(PGconn*).  If no
+ 	 * storage is needed, return NULL or set this hook to NULL.
+ 	 */
+ 	void *(*initHookData)(const PGconn *conn);
+ 
+ 	/* Invoked on PQreset and PQresetPoll */
+ 	void (*connReset)(const PGconn *conn);
+ 
+ 	/* Invoked on PQfinish. */
+ 	void (*connDestroy)(const PGconn *conn);
+ 
+ 	/* Invoked on PQgetResult, internally called by all exec funcs */
+ 	void *(*resultCreate)(const PGconn *conn, const PGresult *result);
+ 
+ 	/* Invoked on PQcopyResult */
+ 	void *(*resultCopy)(PGresult *dest, const PGresult *src);
+ 
+ 	/* Invoked when PQclear is called */
+ 	void (*resultDestroy)(const PGresult *result);
+ } PGobjectHooks;
+ 
+ /* ----------------
   * Exported functions of libpq
   * ----------------
   */
  
  /* ===	in fe-connect.c === */
  
***************
*** 434,445 ****
--- 493,523 ----
   * 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 ****
--- 584,617 ----
  /* 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);
  
+ /* Adds a set of global object hooks, which will be inherited by every
+  * PGconn.  Object hooks added in this fashion do not require being
+  * added on a per-connection basis.  Any number of object hooks can be
+  * be added.
+  *
+  * WARNING: This should only be called in the application's main thread
+  * before using any other libpq functions.  This is not thread-safe and
+  * should be used prior to creating any application threads.
+  */
+ extern int
+ PQaddGlobalObjectHooks(PGobjectHooks *hooks);
+ 
+ /* Adds a set of object hooks to the given connection. */
+ extern int
+ PQaddObjectHooks(PGconn *conn, PGobjectHooks *hooks);
+ 
+ extern void *
+ PQhookData(const PGconn *conn, const char *hookName);
+ 
+ extern void *
+ PQresultHookData(const PGresult *res, const char *hookName);
+ 
  /* === 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.129
diff -C6 -r1.129 libpq-int.h
*** libpq-int.h	1 Jan 2008 19:46:00 -0000	1.129
--- libpq-int.h	30 Apr 2008 20:05:26 -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 ----
***************
*** 181,192 ****
--- 168,183 ----
  	 * 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 */
  
+ 	/* Object Hooks */
+ 	int objHooksCount;
+ 	PGobjectHooks *objHooks;
+ 
  	/*
  	 * 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 */
***************
*** 202,213 ****
--- 193,205 ----
  	 */
  	PGresult_data *curBlock;	/* most recently allocated block */
  	int			curOffset;		/* start offset of free space in block */
  	int			spaceLeft;		/* number of free bytes remaining in block */
  };
  
+ 
  /* PGAsyncStatusType defines the state of the query-execution state machine */
  typedef enum
  {
  	PGASYNC_IDLE,				/* nothing's happening, dude */
  	PGASYNC_BUSY,				/* query in progress */
  	PGASYNC_READY,				/* result ready for PQgetResult */
***************
*** 300,311 ****
--- 292,308 ----
  	/* Optional file to write trace info to */
  	FILE	   *Pfdebug;
  
  	/* Callback procedures for notice message processing */
  	PGNoticeHooks noticeHooks;
  
+ 	/* Object Hooks */
+ 	int objHooksCount;
+ 	int objHooksSize;
+ 	PGobjectHooks *objHooks;
+ 
  	/* Status indicators */
  	ConnStatusType status;
  	PGAsyncStatusType asyncStatus;
  	PGTransactionStatusType xactStatus; /* never changes to ACTIVE */
  	PGQueryClass queryclass;
  	char	   *last_query;		/* last SQL command, or NULL if unknown */
***************
*** 516,531 ****
  extern int	pqPutInt(int value, size_t bytes, PGconn *conn);
  extern int	pqPutMsgStart(char msg_type, bool force_len, PGconn *conn);
  extern int	pqPutMsgEnd(PGconn *conn);
  extern int	pqReadData(PGconn *conn);
  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);
  
  /* === in fe-secure.c === */
  
  extern int	pqsecure_initialize(PGconn *);
  extern void pqsecure_destroy(void);
  extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
--- 513,529 ----
  extern int	pqPutInt(int value, size_t bytes, PGconn *conn);
  extern int	pqPutMsgStart(char msg_type, bool force_len, PGconn *conn);
  extern int	pqPutMsgEnd(PGconn *conn);
  extern int	pqReadData(PGconn *conn);
  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 pqInitObjectHooks(PGconn *conn);
  
  /* === in fe-secure.c === */
  
  extern int	pqsecure_initialize(PGconn *);
  extern void pqsecure_destroy(void);
  extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
