Thread: libpq object hooks

libpq object hooks

From
"Merlin Moncure"
Date:
Attached is an updated version of  'libpq object hooks'.  The primary
purpose for libpq object hooks is to support our libpqtypes system
(not attached), but could possibly some nice sideband features to
libpq.  We are hoping to sneak this into the May commit fest.

regards,
merlin

Attachment

Re: libpq object hooks

From
Andrew Dunstan
Date:

Merlin Moncure wrote:
> Attached is an updated version of  'libpq object hooks'.  The primary
> purpose for libpq object hooks is to support our libpqtypes system
> (not attached), but could possibly some nice sideband features to
> libpq.  We are hoping to sneak this into the May commit fest.
>
>


I've had a preliminary look at this.

The first thing it needs is lots and lots of documentation. I think it
probably needs a Section in the libpq chapter all on its own, preferably
with some examples. I think that lack alone is enough to keep it from
being committed for now.

Second, the hook names are compared case insensitively and by linear
search. I don't see any justification for using case insensitive names
for hooks in a C program, so I think that part should go. And if we
expect to keep anything other than trivial numbers of hooks we should
look at some sort of binary or hashed search.

The thing that is a bit disturbing is that the whole style of this
scheme is very different from the fairly simple APIs that the rest of
libpq presents. It's going to make libpq look rather odd, I think. I
would have felt happier if the authors had been able to come up with a
simple scheme to add API calls to export whatever information they
needed, rather than using this callback scheme.

That said, this patch doesn't look that bad to me otherwise, at least on
first inspection. One might say the the ability to add tuples to a
resultset arbitrarily, or to change an attribute arbitrarily, might be
footguns (and if you can add one, why can't you delete one?), but then
this is data in the hands of the client anyway, so they can do what they
like with it after they get it out of the resultset, so I guess there's
no real danger.

cheers

andrew

Re: libpq object hooks

From
Bruce Momjian
Date:
Andrew Dunstan wrote:
> The first thing it needs is lots and lots of documentation. I think it
> probably needs a Section in the libpq chapter all on its own, preferably
> with some examples. I think that lack alone is enough to keep it from
> being committed for now.
>
> Second, the hook names are compared case insensitively and by linear
> search. I don't see any justification for using case insensitive names
> for hooks in a C program, so I think that part should go. And if we
> expect to keep anything other than trivial numbers of hooks we should
> look at some sort of binary or hashed search.
>
> The thing that is a bit disturbing is that the whole style of this
> scheme is very different from the fairly simple APIs that the rest of
> libpq presents. It's going to make libpq look rather odd, I think. I
> would have felt happier if the authors had been able to come up with a
> simple scheme to add API calls to export whatever information they
> needed, rather than using this callback scheme.

My personal opinion is still that I would like to see a more general
usefulness for these functions before adding them to libpq.  The
complexity of the API just mirrors my gut feeling on this.

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +

Re: libpq object hooks

From
Alvaro Herrera
Date:
Andrew Dunstan escribió:

> The thing that is a bit disturbing is that the whole style of this
> scheme is very different from the fairly simple APIs that the rest of
> libpq presents. It's going to make libpq look rather odd, I think. I
> would have felt happier if the authors had been able to come up with a
> simple scheme to add API calls to export whatever information they
> needed, rather than using this callback scheme.

I'm not sure I understand this point.  Remember that this is here to
support the libpqtypes library.  There doesn't seem to be a way for an
API such as you describe to work.

> Second, the hook names are compared case insensitively and by linear
> search. I don't see any justification for using case insensitive names
> for hooks in a C program, so I think that part should go. And if we
> expect to keep anything other than trivial numbers of hooks we should
> look at some sort of binary or hashed search.

Keep in mind that the original patch supported a single hook being
registered.  Perhaps we could dream about having a couple of hooks
registered, but turning into hashed search would seem to be overkill, at
least for now.  (If hooking into libpq is truly successful we can always
improve it later -- it's not an exported detail of the API after all.)

--
Alvaro Herrera                                http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

Re: libpq object hooks

From
Andrew Dunstan
Date:

Alvaro Herrera wrote:
> Andrew Dunstan escribió:
>
>
>> The thing that is a bit disturbing is that the whole style of this
>> scheme is very different from the fairly simple APIs that the rest of
>> libpq presents. It's going to make libpq look rather odd, I think. I
>> would have felt happier if the authors had been able to come up with a
>> simple scheme to add API calls to export whatever information they
>> needed, rather than using this callback scheme.
>>
>
> I'm not sure I understand this point.  Remember that this is here to
> support the libpqtypes library.  There doesn't seem to be a way for an
> API such as you describe to work.
>

That might well be true. The issue then becomes "Do we want to add
something with this flavor to libpq?" I take it Bruce's answer is "No",
at least until he has seen more evidence of general usefulness. I think
we need to make a decision on this before anyone wastes any more time.

It should be noted that while this feels slightly foreign, it isn't
hugely invasive, unlike the previous effort - it's only a few hundred
lines of new code.

If we reject this, presumably the authors will have no alternative than
to offer libpqtypes as a patch to libpq. ISTM that we're then asking
them to climb over a fairly high hurdle. On the one hand we want them to
demonstrate that there's demand for their tool and on the other we make
it difficult to distribute and deploy.

>
>> Second, the hook names are compared case insensitively and by linear
>> search. I don't see any justification for using case insensitive names
>> for hooks in a C program, so I think that part should go. And if we
>> expect to keep anything other than trivial numbers of hooks we should
>> look at some sort of binary or hashed search.
>>
>
> Keep in mind that the original patch supported a single hook being
> registered.  Perhaps we could dream about having a couple of hooks
> registered, but turning into hashed search would seem to be overkill, at
> least for now.  (If hooking into libpq is truly successful we can always
> improve it later -- it's not an exported detail of the API after all.)
>
>

Right, it was more the case insensitive part that bothered me.

cheers

andrew

Re: libpq object hooks

From
"Merlin Moncure"
Date:
On Wed, May 14, 2008 at 8:18 AM, Andrew Dunstan <andrew@dunslane.net> wrote:
> Right, it was more the case insensitive part that bothered me.
Done. We in fact had realized this was a mistake anyways following
some profiling of the libpqtypes library.   In some scenarios, this
function gets called a lot.

Regarding the other comments:
*) API structure: Our major objective was to minimize exports to
libpq.  I think both copyresult and setvalue have some possible
sideband usage (footguns or no).  Additional functions could be
speculated but are not required by libpqtypes.  We would have no
problem adding a few things to complete the api if necessary.

The patch is basically the minimum libpqtypes needs and has to work
more or less as written.  We tried a few times to suggest implementing
the split a different way (basically, more invasion into libpq).  We
couldn't get any action there.

If the patch is rejected on general merits...that signals the death
blow for libpqtypes.  We have a chicken/egg problem...people can't use
it without patching libpq which will really hamper its adoption, which
is, uh, needed to justify the patch.  For the record, we have had a
couple of dozen downloads of the libpqtypes library on pgfoundry since
we put it up there last week.  Based on how it has simplified and
improved our own code vs. libpq, we have absolutely no doubts it is a
major improvement over PQexecParams.

merlin

Re: libpq object hooks

From
Tom Lane
Date:
Andrew Dunstan <andrew@dunslane.net> writes:
> It should be noted that while this feels slightly foreign, it isn't
> hugely invasive, unlike the previous effort - it's only a few hundred
> lines of new code.

> If we reject this, presumably the authors will have no alternative than
> to offer libpqtypes as a patch to libpq.

No, they could revise their patch to be more stylistically in keeping
with libpq.  I haven't looked at the current version of the patch yet,
but the early versions seemed quite overengineered to me, so your
criticism didn't surprise me.

>> Keep in mind that the original patch supported a single hook being
>> registered.

> Right, it was more the case insensitive part that bothered me.

I'm wondering why the hooks need names at all.  AFAICS all that
libpq needs to know about a hook is a callback function address
and a void * passthrough pointer.

            regards, tom lane

Re: libpq object hooks

From
"Merlin Moncure"
Date:
On Tue, May 13, 2008 at 11:52 PM, Bruce Momjian <bruce@momjian.us> wrote:
> My personal opinion is still that I would like to see a more general
> usefulness for these functions before adding them to libpq.  The
> complexity of the API just mirrors my gut feeling on this.

There has been a lot of demand for the features that libpqtypes
provides...a quick search of the archives demonstrates this.  Here are
a few examples of threads from users asking or even trying to
implement some of the things that libpqtypes helps with or indirectly
solves...I am speaking to your point of usefulness here.

[BUGS] BUG #4053: libpq documentation should express clearly, that
integers are passed in network octet order
[PATCHES] [PATCH] automatic integer conversion
[HACKERS] convert int to bytea
[HACKERS] comunication protocol
[HACKERS] PQescapeBytea* version for parameters
[HACKERS] libpq and Binary Data Formats
[GENERAL] binary representation of date and numeric
[GENERAL] binding 64-bit integer
[HACKERS] Last minute mini-proposal (I know, I know) for PQexecf()
[PERFORM] Low throughput of binary inserts from windows to linux
[GENERAL] Storing images as BYTEA or large objects

merlin

Re: libpq object hooks

From
Bruce Momjian
Date:
Merlin Moncure wrote:
> Regarding the other comments:
> *) API structure: Our major objective was to minimize exports to
> libpq.  I think both copyresult and setvalue have some possible
> sideband usage (footguns or no).  Additional functions could be
> speculated but are not required by libpqtypes.  We would have no
> problem adding a few things to complete the api if necessary.
>
> The patch is basically the minimum libpqtypes needs and has to work
> more or less as written.  We tried a few times to suggest implementing
> the split a different way (basically, more invasion into libpq).  We
> couldn't get any action there.
>
> If the patch is rejected on general merits...that signals the death
> blow for libpqtypes.  We have a chicken/egg problem...people can't use
> it without patching libpq which will really hamper its adoption, which
> is, uh, needed to justify the patch.  For the record, we have had a
> couple of dozen downloads of the libpqtypes library on pgfoundry since
> we put it up there last week.  Based on how it has simplified and
> improved our own code vs. libpq, we have absolutely no doubts it is a
> major improvement over PQexecParams.

One idea would be to add the libpq hooks but not document them.  This
way, we can modify or remove the API as needed in the future.  As
libpqtypes matures and we are sure what the API should be, we can
document it as stable and permanent.

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +

Re: libpq object hooks

From
"Merlin Moncure"
Date:
On Wed, May 14, 2008 at 10:44 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> No, they could revise their patch to be more stylistically in keeping
> with libpq.  I haven't looked at the current version of the patch yet,
> but the early versions seemed quite overengineered to me, so your
> criticism didn't surprise me.

I think you'll find the latest version more reasonable.  We tried to
keep the over-engineering, so to speak, on our side and make the libpq
changes surgical.

> I'm wondering why the hooks need names at all.  AFAICS all that
> libpq needs to know about a hook is a callback function address
> and a void * passthrough pointer.

Here are the proposed API changes [aside: this is one of two breaks
from your initial suggestions..the other being PQcopyResult]:

+ PQcopyResult              142
+ PQsetvalue                143
+ PQresultAlloc             144
+ PQaddObjectHooks          145
+ PQaddGlobalObjectHooks    146
+ PQhookData                147
+ PQresultHookData          148

In question is:
+ void *
+ PQhookData(const PGconn *conn, const char *hookName)

Basically, libpqtypes has various functions that take a PGconn that
need the private data that is stored in libpq with the connection.
PQhookData just does simple linear search and returns the data.

[thinks]
are you suggesting something like
+ void *
+ PQhookData(const PGconn *conn, const void *hookHandle)
?

I would have to take a quick look at the code with Andrew C (he'll be
in in a bit)...but this might be doable.

merlin

Re: libpq object hooks

From
"Merlin Moncure"
Date:
On Wed, May 14, 2008 at 10:52 AM, Bruce Momjian <bruce@momjian.us> wrote:
> Merlin Moncure wrote:
>> Regarding the other comments:
>> *) API structure: Our major objective was to minimize exports to
>> libpq.  I think both copyresult and setvalue have some possible
>> sideband usage (footguns or no).  Additional functions could be
>> speculated but are not required by libpqtypes.  We would have no
>> problem adding a few things to complete the api if necessary.
>>
>> The patch is basically the minimum libpqtypes needs and has to work
>> more or less as written.  We tried a few times to suggest implementing
>> the split a different way (basically, more invasion into libpq).  We
>> couldn't get any action there.
>>
>> If the patch is rejected on general merits...that signals the death
>> blow for libpqtypes.  We have a chicken/egg problem...people can't use
>> it without patching libpq which will really hamper its adoption, which
>> is, uh, needed to justify the patch.  For the record, we have had a
>> couple of dozen downloads of the libpqtypes library on pgfoundry since
>> we put it up there last week.  Based on how it has simplified and
>> improved our own code vs. libpq, we have absolutely no doubts it is a
>> major improvement over PQexecParams.
>
> One idea would be to add the libpq hooks but not document them.  This
> way, we can modify or remove the API as needed in the future.  As
> libpqtypes matures and we are sure what the API should be, we can
> document it as stable and permanent.

The API functions relating to hooks are unlikely to change once
settled on...but PQsetvalue/PQcopyResult are a good fit with your idea
(they introduce new behaviors that could possibly be used outside of
libpqtypes context, and could require changes down the line).

merlin

Re: libpq object hooks

From
Andrew Chernow
Date:
>> I'm wondering why the hooks need names at all.  AFAICS all that
>> libpq needs to know about a hook is a callback function address
>> and a void * passthrough pointer.
>
>
> In question is:
> + void *
> + PQhookData(const PGconn *conn, const char *hookName)
>
> Basically, libpqtypes has various functions that take a PGconn that
> need the private data that is stored in libpq with the connection.
> PQhookData just does simple linear search and returns the data.
>
> [thinks]
> are you suggesting something like
> + void *
> + PQhookData(const PGconn *conn, const void *hookHandle)
> ?
>
> I would have to take a quick look at the code with Andrew C (he'll be
> in in a bit)...but this might be doable.
>

The hook callback functions allow the hook implementor to receive
created/destroyed events about a PGconn and PGresult (PQreset as well).  The
hook implementor has the option of associating some memory with either.  But,
that memory pointer is worthless unless there is a way of referencing it at a
later time.

HookName would not be needed if the libpq hook API only supported a single
Object Hook to be registered per conn (or library-wide).  It was requested of us
to allow a list of hooks per conn.  This requries a way of referencing the item.

Functions outside the hook callback functions:
- PQparamCreate needs libpq-hook-func void *PQhookData(conn, hookName)
- PQgetf needs libpq-hook-func void *PQresultHookData(res, hookName)
- Also, the "void *resultCreate(...)" hook callback implementation inside
libpqtypes must use PQhookData on the provided conn so it can copy some
conn.hookData properties to the result.hookData.  The created result.hookData is
returned (one can return NULL if no hookData is needed).

I have no issue with merlin's idea of a void *handle, but that doesn't really
change the concept at all ... just allows someone registering hooks with libpq
to use something other than a string.  The hookName string idea feels a little
more natural and simple.

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

Re: libpq object hooks

From
"Merlin Moncure"
Date:
On Wed, May 14, 2008 at 10:44 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> I'm wondering why the hooks need names at all.  AFAICS all that
> libpq needs to know about a hook is a callback function address
> and a void * passthrough pointer.

For reference...here is what libpqtypes has to do to bind via the
hooking interface:
http://libpqtypes.esilo.com/browse_source.html?file=hooks.c

Are you proposing something substantially different (not my handle
suggestion)? How would it work exactly?

merlin

Re: libpq object hooks

From
Andrew Chernow
Date:
Merlin Moncure wrote:
> On Wed, May 14, 2008 at 10:44 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I'm wondering why the hooks need names at all.  AFAICS all that
>> libpq needs to know about a hook is a callback function address
>> and a void * passthrough pointer.
>
> For reference...here is what libpqtypes has to do to bind via the
> hooking interface:
> http://libpqtypes.esilo.com/browse_source.html?file=hooks.c
>
> Are you proposing something substantially different (not my handle
> suggestion)? How would it work exactly?
>
> merlin
>
>

It is important to see how "NON-hook-callback" functions in libpqtypes
make use of the hook data.

PQparamCreate must get a pointer to the conn hook data
http://libpqtypes.esilo.com/browse_source.html?file=param.c#line24

PQgetf must get a pointer to the result hook data
http://libpqtypes.esilo.com/browse_source.html?file=exec.c#line65

These are NOT hook callbacks.  The hook data is NOT isolated to callback
functions.  It is memory that is publically accessible, outside hook
implementations.

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

Re: libpq object hooks

From
Andrew Chernow
Date:
Attached is an updated patch.  The only change to this patch is that
hookNames are now compared with strcmp rather than strcasecmp.  The
comparisons occur in fe-mics.c (bottom of file) during PQhookData and
PQresultHookData.

Not sure this needs clarification, but PQcopyResult, PQresultAlloc and
PQsetvalue are not part of the object hooks API.  They are part of
libpq's result management functions (at least that is what I call them).

Hook API
- PQaddObjectHooks
- PQaddGlobalObjectHooks ** CAN BE REMOVED, SEE BELOW
- PQhookData
- PQresultHookData

Result Management (I would put PQmakeEmptyResult here)
- PQcopyResult
- PQsetvalue
- PQresultAlloc (light wrapper to internal pqResultAlloc)

PROPOSAL:
PQaddGlobalObjectHooks can be removed by handling per-conn and global
hook registeration through PQaddObjectHooks.  If the provided PGconn is
NULL, add hooks to global libpq list:

int
PQaddObjectHooks(PGconn *conn, PGobjectHooks *hooks)
{
   if(conn == NULL)
     ;// global hook register
   else
     ;// per-conn register
}

This would make the Object Hooks API 3 functions instead of 4.  The same
rules apply to global hook registeration, do this from main() before
using libpq as the ObjectHooks list is not thread-safe (no locking).

NOTE: The patch is called objhooks.patch which is a misleading.  The
patch is really comprised of the API calls needed to make libpqtypes
work.  If anyone feels this should be broken into two patches (like
objhooks.patch and resmgnt.patch), let us know.

--
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    14 May 2008 17:47:59 -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    14 May 2008 17:47:59 -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    14 May 2008 17:48:00 -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    14 May 2008 17:48:00 -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(strcmp(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(strcmp(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(strcmp(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    14 May 2008 17:48:00 -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    14 May 2008 17:48:00 -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 *);

Re: libpq object hooks

From
daveg
Date:
On Wed, May 14, 2008 at 10:52:43AM -0400, Bruce Momjian wrote:
>
> One idea would be to add the libpq hooks but not document them.  This
> way, we can modify or remove the API as needed in the future.  As
> libpqtypes matures and we are sure what the API should be, we can
> document it as stable and permanent.

-1

Perhaps it is just me, but undocumented interface are evil. Simply document
it with the changable bits labled as such.

-dg

--
David Gould       daveg@sonic.net      510 536 1443    510 282 0869
If simplicity worked, the world would be overrun with insects.

Re: libpq object hooks

From
"Merlin Moncure"
Date:
On Wed, May 14, 2008 at 3:47 PM, daveg <daveg@sonic.net> wrote:
> On Wed, May 14, 2008 at 10:52:43AM -0400, Bruce Momjian wrote:
>>
>> One idea would be to add the libpq hooks but not document them.  This
>> way, we can modify or remove the API as needed in the future.  As
>> libpqtypes matures and we are sure what the API should be, we can
>> document it as stable and permanent.
>
> Perhaps it is just me, but undocumented interface are evil. Simply document
> it with the changable bits labled as such.

Well, in defense of Bruce, there is some precedent for that.  Anything
that queries binary currently falls under that umbrella (mostly
undocumented and changeable).  For functions exported by libpq
though...it's probably better to nail things down as much as possible
up front.

This is a big advantage of the hooks strategy overall, it allows us to
mature the library except for the interaction with libpq (ISTM Bruce
was trying to give us a little leeway here as well, and we appreciate
that).

merlin

Re: libpq object hooks

From
"Merlin Moncure"
Date:
On Wed, May 14, 2008 at 10:44 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> I'm wondering why the hooks need names at all.  AFAICS all that
> libpq needs to know about a hook is a callback function address
> and a void * passthrough pointer.

I'm trying to make this work as you suggest.

It's pretty clear that all the callbacks should be modified to send a
void* along with the other arguments.  This would eliminate the need
for hookname there (getting the state inside callback functions).

The problem is the functions PQhookData(conn, hookname) and
PQresultHookData(result, hookName).  We need these to work in
functions that are not callbacks.  If we eliminate hookname
completely, there is no way for libpq to know which private state we
are asking for.  I'm a little bit stuck here.  Is it possible to
preserve the hookname outside of the context of the callback functions
or is there something else I'm missing? (I'm sorry if I'm being
obtuse)

Eliminating the need for libpqtypes to need PQhookx functions, while
possible, would make libpqtypes a lot more difficult to use and
generally more awkward.

merlin

Re: libpq object hooks

From
Tom Lane
Date:
"Merlin Moncure" <mmoncure@gmail.com> writes:
> The problem is the functions PQhookData(conn, hookname) and
> PQresultHookData(result, hookName).  We need these to work in
> functions that are not callbacks.  If we eliminate hookname
> completely, there is no way for libpq to know which private state we
> are asking for.

Well, depending on a hook name for this is broken-by-design anyway,
because there is no way for two independently written libraries to
be sure they don't choose conflicting hook names.  So the need for
a hook name has to go away.

It might work to use the address of the hook callback function as
a key for retrieving the associated void * pointer.  You'd need to
not register the same callback function more than once per object,
but from what I gather here you don't need to.

            regards, tom lane

Re: libpq object hooks

From
Andrew Dunstan
Date:

Tom Lane wrote:
> "Merlin Moncure" <mmoncure@gmail.com> writes:
>
>> The problem is the functions PQhookData(conn, hookname) and
>> PQresultHookData(result, hookName).  We need these to work in
>> functions that are not callbacks.  If we eliminate hookname
>> completely, there is no way for libpq to know which private state we
>> are asking for.
>>
>
> Well, depending on a hook name for this is broken-by-design anyway,
> because there is no way for two independently written libraries to
> be sure they don't choose conflicting hook names.  So the need for
> a hook name has to go away.
>
> It might work to use the address of the hook callback function as
> a key for retrieving the associated void * pointer.  You'd need to
> not register the same callback function more than once per object,
> but from what I gather here you don't need to.
>
>
>

Or else have the library return a unique handle when registering hooks,
rather than supplying a hook name.

cheers

andrew

Re: libpq object hooks

From
Andrew Chernow
Date:
Tom Lane wrote:
> "Merlin Moncure" <mmoncure@gmail.com> writes:
>> The problem is the functions PQhookData(conn, hookname) and
>> PQresultHookData(result, hookName).  We need these to work in
>> functions that are not callbacks.  If we eliminate hookname
>> completely, there is no way for libpq to know which private state we
>> are asking for.
>
> Well, depending on a hook name for this is broken-by-design anyway,
> because there is no way for two independently written libraries to
> be sure they don't choose conflicting hook names.  So the need for
> a hook name has to go away.
>
> It might work to use the address of the hook callback function as
> a key for retrieving the associated void * pointer.  You'd need to
> not register the same callback function more than once per object,
> but from what I gather here you don't need to.
>
>             regards, tom lane
>
>

There can be cases to use the same callbacks, although unlikely.  To
completely avoid collisions, the below would work:

Use the address of a static, maybe an 'int', as a hook hanlde.  Provide
the user with a macro that can make a hook handle.

typedef void *PGhookHandle;

// Declare an int and point "tokname" at it.  The value doesn't
// matter, its the pointer address we are interested in.
#define PQ_MAKE_HOOK_HANDLE(tokname) \
   static int hh__ ## tokname = 0; \
   static const PGhookHandle tokname = &hh__ ## tokname

As an example, here is what libpqtypes would do:

// libpqtypes hooks.c
PQ_MAKE_HOOK_HANDLE(pqthookhandle);

Now the handle replaces the hookName.  The "const char *hookName" member
of the PQobjectHooks structure is changed to "const PGhookHanlde
hookHandle".  This allows for all the flexibility of a const char * w/o
the collision issues.

// these function prototypes change as well
void *PQhookData(PGconn *, const PGhookHandle);
void *PQresultHookData(PGresult *, const PGhookHandle);

We will send in an updated patch.

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

Re: libpq object hooks

From
Andrew Chernow
Date:
Andrew Dunstan wrote:
>
>
> Tom Lane wrote:
>>
>> It might work to use the address of the hook callback function as
>> a key for retrieving the associated void * pointer.  You'd need to
>> not register the same callback function more than once per object,
>> but from what I gather here you don't need to.
>>
>>
>>
>
> Or else have the library return a unique handle when registering hooks,
> rather than supplying a hook name.
>
> cheers
>
> andrew
>
>

The problem with this is that hooks can be registered on a per-conn
basis.  Is there a way to ensure the libpq returned handle would be the
unique across every registration of a given PGobjectHooks?  ISTM that
the hook handle needs to be constant and unique somehow.  Tom's idea
would work with the "very" small limitation of not being able to reuse
hook callbacks.  I throw out an idea of using the address of a static,
which is constant and unique.

app_func(PGresult *res)
{
   PQresultHookData(res, ?handle?);
}

app_func is not aware of what PGconn generated the result so it has no
idea what libpq returned handle to use.

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

Re: libpq object hooks

From
Tom Lane
Date:
Andrew Dunstan <andrew@dunslane.net> writes:
> Tom Lane wrote:
>> It might work to use the address of the hook callback function as
>> a key for retrieving the associated void * pointer.  You'd need to
>> not register the same callback function more than once per object,
>> but from what I gather here you don't need to.

> Or else have the library return a unique handle when registering hooks,
> rather than supplying a hook name.

Uh, how would that solve your problem?  Seems like the difficulty
shifts from "how do I get the hook data" to "how do I get the key
with which to get the hook data".

            regards, tom lane

Re: libpq object hooks

From
Tom Lane
Date:
Andrew Chernow <ac@esilo.com> writes:
> There can be cases to use the same callbacks, although unlikely.  To
> completely avoid collisions, the below would work:

Still looks like overdesign to me.  If we use the hook function address
we solve the problem with no extra notation and no extra storage.

Note that if you want N fixed keys, you can just have N hook functions
(all calling a shared workhorse routine, no doubt).  So your proposal
adds no functionality whatever if the usage involves a fixed number of
static handles.  Now it could possibly allow a variable-at-runtime
number of handles, but I'd want to see a worked-out use case before
designing for that much complexity.  In particular, it seems to me that
the problem would then shift to how do you know which handle to use for
the lookup, thus you've just introduced another layer of complexity
without buying anything.

I think the typical use case is just that you need to distinguish "your"
hook from anyone else's hooks, so the function address is plenty
sufficient.

It should also be noted that this whole problem *can* be solved without
any PQhookData at all: as long as you have hooks to get control at
creation and destruction of PGconns and PGresults, you can maintain your
own index data structure.  I'm willing to grant some amount of extra
API-stuff to save users having to do that in simple cases, but I don't
think we need to try to support infinitely complex cases.

            regards, tom lane

Re: libpq object hooks

From
Andrew Chernow
Date:
Tom Lane wrote:
> Andrew Chernow <ac@esilo.com> writes:
>> There can be cases to use the same callbacks, although unlikely.  To
>> completely avoid collisions, the below would work:
>
> Still looks like overdesign to me.  If we use the hook function address
> we solve the problem with no extra notation and no extra storage.
>
> Note that if you want N fixed keys, you can just have N hook functions
> (all calling a shared workhorse routine, no doubt).  So your proposal
> adds no functionality whatever if the usage involves a fixed number of
> static handles.  Now it could possibly allow a variable-at-runtime
> number of handles, but I'd want to see a worked-out use case before
> designing for that much complexity.  In particular, it seems to me that
> the problem would then shift to how do you know which handle to use for
> the lookup, thus you've just introduced another layer of complexity
> without buying anything.
>
> I think the typical use case is just that you need to distinguish "your"
> hook from anyone else's hooks, so the function address is plenty
> sufficient.
>
> It should also be noted that this whole problem *can* be solved without
> any PQhookData at all: as long as you have hooks to get control at
> creation and destruction of PGconns and PGresults, you can maintain your
> own index data structure.  I'm willing to grant some amount of extra
> API-stuff to save users having to do that in simple cases, but I don't
> think we need to try to support infinitely complex cases.
>
>             regards, tom lane
>
>

Okay.  No problem over here.

Which callback do we use as the key?  Currently, none are required (only
the name was required).  We have to choose one callback that must be
provided.  Maybe initHookData() must be provided?  If the end-user
doesn't need it they can just return NULL.

This is what is passed to PQaddObjectHooks, along with a conn:

typedef struct
{
   //char *name; REMOVED

   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;

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

Re: libpq object hooks

From
Tom Lane
Date:
Andrew Chernow <ac@esilo.com> writes:
> Which callback do we use as the key?  Currently, none are required (only
> the name was required).  We have to choose one callback that must be
> provided.

What?  I thought what you wanted back was the void * pointer that had
been registered with a particular callback function.  So you use that
callback function.  If it's not actually registered, you get a NULL.

> This is what is passed to PQaddObjectHooks, along with a conn:

This is all wrong IMHO, not least because it creates ABI problems if you
want to add another hook type later.  Register each hook separately, eg

typedef void (*PGCRHook) (PGconn *conn, void *passthrough);

void PQregisterConnResetHook(PGconn *conn, PQCRHook func, void *passthrough);

... repeat for each possible hook ...

            regards, tom lane

Re: libpq object hooks

From
Andrew Chernow
Date:
Tom Lane wrote:
> Andrew Chernow <ac@esilo.com> writes:
>> Which callback do we use as the key?  Currently, none are required (only
>> the name was required).  We have to choose one callback that must be
>> provided.
>
> What?  I thought what you wanted back was the void * pointer that had
> been registered with a particular callback function.  So you use that
> callback function.  If it's not actually registered, you get a NULL.
>
>> This is what is passed to PQaddObjectHooks, along with a conn:
>
> This is all wrong IMHO, not least because it creates ABI problems if you
> want to add another hook type later.  Register each hook separately, eg
>
> typedef void (*PGCRHook) (PGconn *conn, void *passthrough);
>
> void PQregisterConnResetHook(PGconn *conn, PQCRHook func, void *passthrough);
>
> ... repeat for each possible hook ...
>
>             regards, tom lane
>
>

One can make a case to break apart the obj hooks structure into
individual register functions, but I think you have a different idea in
your head than what is being proposed.  For starters, there is no
passthru pointer to register with a callback (there could be but that is
different than hook data...your register looks more like a user_ptr).
The passthru pointer, what we call hookData, is allocated with a PGconn
(not provided by the user).  This is the point of the initHookData callback.

typedef void *(*PGinitHookData)(const PGconn *conn);

PQregisterInitHookData((PGconn *)NULL, (PGinitHookData)func);
PQregisterConnResetHook((PGconn *)NULL, (PGCRHook)func);
//etc...
conn = PQconnectdb();

When connectdb returns, initHookData has already been called.  So, a
call to PQhookData(conn, ????) will work.  BUT, what is still missing
from the equation is how to uniquely reference hookData on a conn.

What I was previously suggesting was to use the address of initHookData,
since w/o this address there wouldn't be any hook data to get.  Seemed
like a logical choice.

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

Re: libpq object hooks

From
Andrew Chernow
Date:
Tom Lane wrote:
> Andrew Chernow <ac@esilo.com> writes:
>> Which callback do we use as the key?  Currently, none are required (only
>> the name was required).  We have to choose one callback that must be
>> provided.
>
> What?  I thought what you wanted back was the void * pointer that had
> been registered with a particular callback function.  So you use that
> callback function.  If it's not actually registered, you get a NULL.
>
>> This is what is passed to PQaddObjectHooks, along with a conn:
>
> This is all wrong IMHO, not least because it creates ABI problems if you
> want to add another hook type later.  Register each hook separately, eg
>
> typedef void (*PGCRHook) (PGconn *conn, void *passthrough);
>
> void PQregisterConnResetHook(PGconn *conn, PQCRHook func, void *passthrough);
>
> ... repeat for each possible hook ...
>
>             regards, tom lane
>
>

I am starting to think we have not clarified what it is we are trying to
do; maybe hook is the wrong terminology.

We need to add members to a conn and result, that's pretty much it.  To
do this, an api user can register callbacks to receive notifications
about created/destroyed states of objects.  PQhookData is just like
PQerrorMessage in that both are public accessor functions to private
object data.  The difference is that there can be more than one hookData
"dynamic struct member" on a conn/result at a time, unlike errorMessage;
  thus the need for an additional "lookup" value when getting hook data
(what was hookName).

A solution is to only have one function with an eventId, instead of a
register function per event.  I am playing with using the name 'event'
rather than hook.

typedef enum
{
   PQEVTID_INITDATA,
   PQEVTID_CONNRESET,
   // resultcreate, resultcopy, etc...
} PGobjectEventId;

typedef void *(*PGobjectEventProc)(PGobjectEventId evtId, ...);

int PQregisterObjectEventProc(PGconn*, PGobjectEventProc);

// no more key (hookName), use PGobjectEventProc address
void *PQeventData(PGconn *, PGobjectEventProc);
void *PQresultEventData(PGresult *, PGobjectEventProc);

Now there is just one libpq register function and an enum; very
resilient to future additions and ABI friendly.  The API user will
follow a convention of: if you don't care about an event or don't know
what it is, just return NULL from the eventProc function (ignore it).

The implementation of a PGobjectEventProc would most likely be a switch
on the PGobjectEventId with a couple va_arg() calls (which would be very
well documented).

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

Re: libpq object hooks

From
"Merlin Moncure"
Date:
On Thu, May 15, 2008 at 8:38 PM, Andrew Chernow <ac@esilo.com> wrote:
> We need to add members to a conn and result, that's pretty much it.  To do
> this, an api user can register callbacks to receive notifications about
> created/destroyed states of objects.  PQhookData is just like PQerrorMessage
> in that both are public accessor functions to private object data.  The
> difference is that there can be more than one hookData "dynamic struct
> member" on a conn/result at a time, unlike errorMessage;  thus the need for
> an additional "lookup" value when getting hook data (what was hookName).

> typedef void *(*PGobjectEventProc)(PGobjectEventId evtId, ...);
> int PQregisterObjectEventProc(PGconn*, PGobjectEventProc);
> void *PQeventData(PGconn *, PGobjectEventProc);
> void *PQresultEventData(PGresult *, PGobjectEventProc);

This provides what we need...a key to lookup the hook data without
using a string. Also, it reduces the number of exports (it's a little
easier for us, while not essential, to not have to register each
callback individually).  Also, this AFAICT does not have any ABI
issues (no struct), and adds less exports which is nice.  We don't
have to 'look up' the data inside the callbacks..it's properly passed
through as an argument.  While vararg callbacks may be considered
unsafe in some scenarios, I think it's a good fit here.

The most important part though is that it fits what we think is needed
to maintain the data we associate with the libpq with the proper
lifetime.  I'm not sure that everyone was on the same page in terms of
how this works...we may not have explained ourselves properly.  In our
defense, the interaction between libpq and a wrapping library like
libpqtypes is a bit involved and the 'best possible' way to link
things up did not necessarily suggest itself the first time out.

We would like to wrap this up into some form the community would
accept.  The event proc style of doing this is better than our initial
approach...faster and cleaner.  In fact, we are pleased with all the
changes that have come about due to community suggestions...there are
many positive results.

merlin

Re: libpq object hooks

From
Tom Lane
Date:
"Merlin Moncure" <mmoncure@gmail.com> writes:
>> typedef void *(*PGobjectEventProc)(PGobjectEventId evtId, ...);
>> int PQregisterObjectEventProc(PGconn*, PGobjectEventProc);
>> void *PQeventData(PGconn *, PGobjectEventProc);
>> void *PQresultEventData(PGresult *, PGobjectEventProc);

> This provides what we need...a key to lookup the hook data without
> using a string. Also, it reduces the number of exports (it's a little
> easier for us, while not essential, to not have to register each
> callback individually).  Also, this AFAICT does not have any ABI
> issues (no struct), and adds less exports which is nice.  We don't
> have to 'look up' the data inside the callbacks..it's properly passed
> through as an argument.  While vararg callbacks may be considered
> unsafe in some scenarios, I think it's a good fit here.

I don't think varargs callbacks are a good idea at all.  Please adjust
this to something that doesn't do that.  Also, haven't you forgotten
the passthrough void *?

If you really want only one callback function, perhaps what would work
is

typedef void (*PGeventProc) (PGeventId eventId, const void *eventInfo,
                             void *passthrough);

int PQregisterEventProc(PGconn *conn, PGeventProc proc, void *passthrough);

where eventInfo will point to one of several exported struct types
depending on the value of eventId.  With this approach, you can add
more fields to the end of any one such struct type without creating
ABI issues.  I have little confidence in being able to make similar
changes in a varargs environment.

Also, even if varargs are safe they'd be notationally unpleasant
in the extreme.  varargs are just a PITA to work with --- you'd have
to do all the decoding in the first-level hook routine, even for
items you weren't going to use.  With something like the above
all you need is a switch() and some pointer casts.

            regards, tom lane

Re: libpq object hooks

From
"Merlin Moncure"
Date:
On Fri, May 16, 2008 at 10:59 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> "Merlin Moncure" <mmoncure@gmail.com> writes:
>>> typedef void *(*PGobjectEventProc)(PGobjectEventId evtId, ...);
>>> int PQregisterObjectEventProc(PGconn*, PGobjectEventProc);
>>> void *PQeventData(PGconn *, PGobjectEventProc);
>>> void *PQresultEventData(PGresult *, PGobjectEventProc);
>
>> This provides what we need...a key to lookup the hook data without
>> using a string. Also, it reduces the number of exports (it's a little
>> easier for us, while not essential, to not have to register each
>> callback individually).  Also, this AFAICT does not have any ABI
>> issues (no struct), and adds less exports which is nice.  We don't
>> have to 'look up' the data inside the callbacks..it's properly passed
>> through as an argument.  While vararg callbacks may be considered
>> unsafe in some scenarios, I think it's a good fit here.
>
> I don't think varargs callbacks are a good idea at all.  Please adjust
> this to something that doesn't do that.  Also, haven't you forgotten
> the passthrough void *?

We didn't...one of the functions (the init) doesn't need it so we
didn't expose it to the fixed arguments...we would just va_arg it off
in the other cases.  Regardless, your comments below make that moot:

> If you really want only one callback function, perhaps what would work
> is
>
> typedef void (*PGeventProc) (PGeventId eventId, const void *eventInfo,
>                             void *passthrough);
>
> int PQregisterEventProc(PGconn *conn, PGeventProc proc, void *passthrough);
>
> where eventInfo will point to one of several exported struct types
> depending on the value of eventId.  With this approach, you can add
> more fields to the end of any one such struct type without creating
> ABI issues.  I have little confidence in being able to make similar
> changes in a varargs environment.

this is fine.

> Also, even if varargs are safe they'd be notationally unpleasant
> in the extreme.  varargs are just a PITA to work with --- you'd have
> to do all the decoding in the first-level hook routine, even for
> items you weren't going to use.  With something like the above
> all you need is a switch() and some pointer casts.

Switch, plus struct (basically a union) will do the trick nicely.  Can
it be a formal union, or is it better as a void*?

The main issue was how what we called the 'hook data' was passed back
and forth.  We'll get a patch up.

merlin

Re: libpq object hooks

From
Andrew Dunstan
Date:

Merlin Moncure wrote:
>
>> Also, even if varargs are safe they'd be notationally unpleasant
>> in the extreme.  varargs are just a PITA to work with --- you'd have
>> to do all the decoding in the first-level hook routine, even for
>> items you weren't going to use.  With something like the above
>> all you need is a switch() and some pointer casts.
>>
>
> Switch, plus struct (basically a union) will do the trick nicely.  Can
> it be a formal union, or is it better as a void*?
>
> The main issue was how what we called the 'hook data' was passed back
> and forth.  We'll get a patch up.
>
>
>

All of this is getting quite a long way from what was in the commitfest
queue. Do we still want to try to get this in this cycle, or should it
be marked returned to author for more work?

cheers

andrew

Re: libpq object hooks

From
Tom Lane
Date:
"Merlin Moncure" <mmoncure@gmail.com> writes:
> Switch, plus struct (basically a union) will do the trick nicely.  Can
> it be a formal union, or is it better as a void*?

I don't think a union buys much notational convenience or safety here,
although admittedly it's a close question.  In one case you're trusting
to cast the pointer to the appropriate type, in the other you're
trusting to use the right union member.  One advantage of separate
structs is that there's no reason not to make the struct type names
long enough to be clear, whereas there's a very strong notational
temptation to make union member names short, because you'll be typing
them a lot.

            regards, tom lane

Re: libpq object hooks

From
Tom Lane
Date:
Andrew Dunstan <andrew@dunslane.net> writes:
> All of this is getting quite a long way from what was in the commitfest
> queue. Do we still want to try to get this in this cycle, or should it
> be marked returned to author for more work?

So far I think it still falls within the category of allowing the author
to revise his work.  I don't want to hold commitfest open waiting on
revisions of this patch, but as long as there's still other stuff being
worked through I don't see why they can't keep trying.

Just for the record, I would really like to close this fest before
PGCon starts.  We still have a couple more days to get it done.

            regards, tom lane

Re: libpq object hooks

From
Alvaro Herrera
Date:
Andrew Dunstan escribió:

> All of this is getting quite a long way from what was in the commitfest
> queue. Do we still want to try to get this in this cycle, or should it
> be marked returned to author for more work?

There are still patches open for which no discussion has even started,
so I think this is OK for this commitfest.

(Besides, it seems we're almost there on this patch.)

--
Alvaro Herrera                                http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

Re: libpq object hooks

From
"Merlin Moncure"
Date:
On Fri, May 16, 2008 at 11:21 AM, Andrew Dunstan <andrew@dunslane.net> wrote:
> All of this is getting quite a long way from what was in the commitfest
> queue. Do we still want to try to get this in this cycle, or should it be
> marked returned to author for more work?

That's your call...we can have a patch up within 24 hours or so.  My
suggestion would be to let us put up an updated version in the May
queue, and if there are any further serious objections we can push it
to the next queue giving some more time for things to percolate.

merlin

Re: libpq object hooks

From
Andrew Dunstan
Date:

Tom Lane wrote:
> Andrew Dunstan <andrew@dunslane.net> writes:
>
>> All of this is getting quite a long way from what was in the commitfest
>> queue. Do we still want to try to get this in this cycle, or should it
>> be marked returned to author for more work?
>>
>
> So far I think it still falls within the category of allowing the author
> to revise his work.  I don't want to hold commitfest open waiting on
> revisions of this patch, but as long as there's still other stuff being
> worked through I don't see why they can't keep trying.
>
> Just for the record, I would really like to close this fest before
> PGCon starts.  We still have a couple more days to get it done.
>
>
>

Apart from this one only two have not been claimed:

. Map Forks
. TRUNCATE TABLE with IDENTITY

cheers

andrew



Re: libpq object hooks

From
Andrew Chernow
Date:
Tom Lane wrote:
>
> typedef void (*PGeventProc) (PGeventId eventId, const void *eventInfo,
>                              void *passthrough);
>
> int PQregisterEventProc(PGconn *conn, PGeventProc proc, void *passthrough);
>
>
>

The above prototypes will work and we will add our 'event instance
pointer' to the event info structures.  Should have a patch shortly.

libpqtypes doesn't need a passthrough/user-pointer.  The object
events/hooks allocate memory when the object is created "part of a
conn/result object instance", it is not supplied by the API user
registering the event/hook callback.

I think this is where some confusion has been occurring, there are two
different pointers: user pointer and event instance pointer.

BTW, PQeventData and PQresultEventData return the event instance
pointer, not the passthrough.  At least that is how we were using these
functions, being how our previous patches do not include a
passthrough/user-pointer feature because libpqtypes didn't need it.

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

Re: libpq object hooks

From
"Merlin Moncure"
Date:
On Fri, May 16, 2008 at 2:34 PM, Andrew Chernow <ac@esilo.com> wrote:
> Tom Lane wrote:
>>
>> typedef void (*PGeventProc) (PGeventId eventId, const void *eventInfo,
>>                             void *passthrough);
>>
>> int PQregisterEventProc(PGconn *conn, PGeventProc proc, void
>> *passthrough);

> The above prototypes will work and we will add our 'event instance pointer'
> to the event info structures.  Should have a patch shortly.


Right.  I actually overlooked the 'passthrough' in
PQregisterEventProc.  It turns out that we are still not quite on the
same page and this needs to be clarified before we move on.  The
passthrough cannot exist...the correct prototypes (reasoning will
follow) are:

typedef void (*PGeventProc) (PGeventId eventId, const void *eventInfo);
int PQregisterEventProc(PGconn *conn, PGeventProc proc);
PQhookData(const PGconn* conn, PGeventProc proc);
...etc.

The purpose of the callbacks is to allow a hooking library to
establish private data that is associated with a PGconn or a PGresult.
 Invoking PQregisterEventProc tells libpq 'I am interested in doing
this', for the supplied PGconn, future results created with that
connection, and (if PGconn is passed as null), all future connections.

Once that is established, libpq begins telling the hooking library
when and what needs to be allocated or deleted.  This is done for both
connections and results at the appropriate times and the management
always occurs inside a callback function.  Thus, while the hooking
library controls what is in the state data, _it does not control when
it is created or destroyed_.  Passing a void* into PQregisterEventProc
suggests that is the wrapper's purvue to establish the private
information.  It isn't, and it can't be..it's as simple as that.

For example, consider that 'hooked' PGconn then produces a result.  If
a passthrough as Tom suggested were passed around, what would you do
to it when the connection was freed (which throws a connection closing
event)?  What exactly is passthrough pointing to in a result copy
event?  If passed around as a single poitner, they are not
defined.These are pointers to different structures who are created at
particular times that only libpq knows.

All of the callbacks (except for possibly conn reset) are basically
allocation and deletion events.  For example, result create is libpq
telling the hooking library: 'a result is coming, please initialize
your result extensions now'.  At that point, a very particular
structure is passed back to the hooking library:

{
  const PGconn* conn;   // the source conn the result
  const void *conn_state; // the conn's private state (it might be
interesting, and we don't want to look it up later)
  const PGresult* res; // the new result's poiter
  void *out_res_state; // we are going to allocate something and set
this in the callback
}

As you can see, there is more going on here than a single passthrough
pointer can handle.  We are in fact doing what a passthrough provides
in principle.  I can certainly understand why our original 'lookup by
name' concept threw up a smokescreen (it did obfuscate the passing
requirements), but hooking can't be serviced by a single pointer set
at callback registration.

With the above prototypes and carefully designed structures,
everything feels natural.  libpq is defining exactly what is supposed
to happen, and the structure parameters define what is coming in and
out.  Things are being 'passed through'...but in a more discrete way.

merlin

Re: libpq object hooks

From
"Merlin Moncure"
Date:
On Fri, May 16, 2008 at 3:49 PM, Merlin Moncure <mmoncure@gmail.com> wrote:
> On Fri, May 16, 2008 at 2:34 PM, Andrew Chernow <ac@esilo.com> wrote:
>> Tom Lane wrote:
>>>
>>> typedef void (*PGeventProc) (PGeventId eventId, const void *eventInfo,
>>>                             void *passthrough);
>>>
>>> int PQregisterEventProc(PGconn *conn, PGeventProc proc, void
>>> *passthrough);
>
>> The above prototypes will work and we will add our 'event instance pointer'
>> to the event info structures.  Should have a patch shortly.
>
>
> Right.  I actually overlooked the 'passthrough' in
> PQregisterEventProc.  It turns out that we are still not quite on the
> same page and this needs to be clarified before we move on.  The
> passthrough cannot exist...the correct prototypes (reasoning will
> follow) are:
>
> typedef void (*PGeventProc) (PGeventId eventId, const void *eventInfo);

small typo: eventInfo obviously can't be const

> int PQregisterEventProc(PGconn *conn, PGeventProc proc);
> PQhookData(const PGconn* conn, PGeventProc proc);

PQhookData is the old name...we are going with 'events' now....the
proper names will come with the patch.

merlin

Re: libpq object hooks

From
Tom Lane
Date:
"Merlin Moncure" <mmoncure@gmail.com> writes:
> Right.  I actually overlooked the 'passthrough' in
> PQregisterEventProc.  It turns out that we are still not quite on the
> same page and this needs to be clarified before we move on.  The
> passthrough cannot exist...

Yes, it *will* exist.  You are assuming that the goals you have for
these hooks are the only ones anyone will ever have.  There are other
possible usage patterns and many of them will need some passthrough
state data.  I'd venture to say that anytime I've ever used a callback
design that did not include a passthrough, I've had reason to curse
its designer sooner or later (qsort being a pretty typical example).

However, it's clear that we are not on the same page, because what
I thought you wanted the PQeventData function to return was the
passthrough pointer.  Evidently that's not what you want it to do,
so you'd better back up a few steps and say what you do want it to do.
(I think that a function to get the passthrough value associated with
a given hook function might be useful too, but it's clear that what
you are after must be something else.)

> The purpose of the callbacks is to allow a hooking library to
> establish private data that is associated with a PGconn or a PGresult.

So you're imagining that on creation of a PGconn or PGresult, the
hook function would be allowed to create some storage and libpq would
then be responsible for tracking that storage?  Okay, but that's a
separate feature from the sort of passthrough I had in mind.  Where
exactly does the hook hand off the storage pointer to libpq?  What
are you going to do if the hook fails to create the storage
(ie, out of memory during PGresult creation)?

> Invoking PQregisterEventProc tells libpq 'I am interested in doing
> this', for the supplied PGconn, future results created with that
> connection, and (if PGconn is passed as null), all future connections.

I am entirely serious about saying that I will not accept that last bit.
To do that we have to buy into having permanent modifiable storage
within libpq, protecting it with thread mutexes, etc etc.  And I don't
like any of the possible usage scenarios for it.  I think hooking into a
PGconn immediately after its creation is just as useful and a lot easier
to manage.

> Once that is established, libpq begins telling the hooking library
> when and what needs to be allocated or deleted.

Wait a minute --- you're trying to get between libpq and malloc?
Why?  That's getting a *whole* lot more invasive than I thought
this patch intended to be, and I don't see a good reason for it.

            regards, tom lane

Re: libpq object hooks

From
"Merlin Moncure"
Date:
On Fri, May 16, 2008 at 4:23 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> "Merlin Moncure" <mmoncure@gmail.com> writes:
>> Right.  I actually overlooked the 'passthrough' in
>> PQregisterEventProc.  It turns out that we are still not quite on the
>> same page and this needs to be clarified before we move on.  The
>> passthrough cannot exist...
>
> Yes, it *will* exist.  You are assuming that the goals you have for
> these hooks are the only ones anyone will ever have.  There are other
> possible usage patterns and many of them will need some passthrough
> state data.  I'd venture to say that anytime I've ever used a callback
> design that did not include a passthrough, I've had reason to curse
> its designer sooner or later (qsort being a pretty typical example).

right.  It can exist, just not hold the event data (the extended
properties of PGresult/conn).  It has a meaning and scope that are not
defined for libpq purposes.  For the 'result' callbacks (see below),
we will just use the passthrough passed in for the connection.
libpqtypes will not use this, and we were getting nervous about people
understanding what the different pointers were used for (we would call
it a user pointer, not a pass through).

> However, it's clear that we are not on the same page, because what
> I thought you wanted the PQeventData function to return was the
> passthrough pointer.  Evidently that's not what you want it to do,
> so you'd better back up a few steps and say what you do want it to do.
> (I think that a function to get the passthrough value associated with
> a given hook function might be useful too, but it's clear that what

We will do this:
void *PQeventData(PGconn*, PGeventProc, void **passthrough);
void *PQresultEventData(PGresult*, PGeventProc, void **passthrough);

> you are after must be something else.)

The major challenge is that libpqtypes (and, presumably, other
libraries) must essentially store its own data in both conn and
result.  We allocate our data when these structures are created and
free it when they are destroyed.  The idea is that libpq fires
callbacks at appropriate times when conn/results are
constructed/destructed.  From these events we set up our private
'event' data and delete it.  Each connection and result maintains a
'list' of private event states, one for each registering callback
(libpqtypes only requires one).  So, registering an event proc is
analogous to adding one or fields to a conn or a result with a private
meaning.

So, libpq is defining 6 events:
*) when a connection is created (put into OK status)
*) when a connection is reset
*) when a connection is destroyed
*) when a result is created in non-error state
*) when a result is copied (we are adding this to libpq w/PQcopyResult)
*) when a result is destroyed

In these events we set up or tear down the private state for the libpq
objects.  We need to sometimes look up the data from outside the
library in public (non callback) code.  This is what PQeventData, etc.
accomplish...provide the private state for the object.   The
difference is that there can be more than one registered event proc on
a conn/result at a time;  thus the need for an additional "lookup"
value when getting event data (what was hookName and is now the event
proc address).

>> The purpose of the callbacks is to allow a hooking library to
>> establish private data that is associated with a PGconn or a PGresult.
>
> So you're imagining that on creation of a PGconn or PGresult, the
> hook function would be allowed to create some storage and libpq would
> then be responsible for tracking that storage?  Okay, but that's a
> separate feature from the sort of passthrough I had in mind.  Where
> exactly does the hook hand off the storage pointer to libpq?  What
> are you going to do if the hook fails to create the storage
> (ie, out of memory during PGresult creation)?

Right, this could happen for one of two likely reasons: OOM as you
suggest or the callback fails for some reason only known to the
hooking library.  In either case, the callback would set the return
pointer as defined by the structure to null, return FALSE, and libpq
would not add the state to the array of 'event state datas' it
maintains.

Here is the event proc with passthrough added:
int PGEventProc(PGEventId event, void *eventInfo, void *passThrough);
// FALSE = failure

>> Invoking PQregisterEventProc tells libpq 'I am interested in doing
>> this', for the supplied PGconn, future results created with that
>> connection, and (if PGconn is passed as null), all future connections.
>
> I am entirely serious about saying that I will not accept that last bit.
> To do that we have to buy into having permanent modifiable storage
> within libpq, protecting it with thread mutexes, etc etc.  And I don't
> like any of the possible usage scenarios for it.  I think hooking into a
> PGconn immediately after its creation is just as useful and a lot easier
> to manage.

Locking is not required so long as you follow certain conventions.
Here is our warning that describes this in the header:
+  * 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.

Obviously, the 'global' behavior is optional, but nice (it's kind of a
'gotcha' if you forget to re-register in a reconnect routine for
example).  It is like an _init() routine, and considered doing it that
way.  It does mean adding an array of 'global' event procs to libpq.
So I would ask you to hold your judgment on this, and, once we get the
new patch in, consider it and we will pull it if you still think it's
too dangerous (meaning, the convention is not followed, by a api
user).

>> Once that is established, libpq begins telling the hooking library
>> when and what needs to be allocated or deleted.
>
> Wait a minute --- you're trying to get between libpq and malloc?
> Why?  That's getting a *whole* lot more invasive than I thought
> this patch intended to be, and I don't see a good reason for it.

No, we don't get in between.  Maybe my wording was a little
misleading...libpq notifies the registered event proc implementations
of when a conn or result is created/destroyed.  libpq, strictly
speaking, is not allocating the private states...just notifying other
code it's time to do it.  There is no change to the way libpq
allocates things generally.

When we send up the revised patch, you will see the invasion is
minimal...the trickiest part was actually injecting the events in the
proper places.

merlin

Re: libpq object hooks

From
Andrew Chernow
Date:
Tom Lane wrote:
> Where
> exactly does the hook hand off the storage pointer to libpq?  What
> are you going to do if the hook fails to create the storage
> (ie, out of memory during PGresult creation)?

The current submitted patch has 3 of its function callbacks returning a
void*, initHookData after the creation of a conn, resultCreate, and
resultCopy.  We have recently changed this design so all hooks, now
called events, go through a single callback ... PGEventProc.  The old
function callbacks are just eventIds now.

/////////////////////////////////
// The libpq side (looping registered event procs)
PGEventResultCreate info;
info.stateData = NULL; /* our event data ptr */
info.conn = conn;
info.result = result;

if(!result->evtState[i].proc(PGEVT_RESULTCREATE,
   (void *)&info, result->evtState[i].passThrough)
{
   PQclear(result); // destroy result? ... not sure
   return error;    // previously, we ignored it
}

// assign event data created by event proc.
result->evtState[i].data = info.stateData;


///////////////////////////////////////
// example of what the event proc does
int my_evtproc(PGEventId evtId, void *evtInfo, void *passThrough)
{
   switch(eventId)
   {
     case PGEVT_RESULTCREATE:
     {
       void *data = makeresultdata(....)
       if(!data)
         return FALSE;
       ((PGEventResultCreate *)evtInfo)->stateData = data;
       break;
     }
   }

   return TRUE;
}


--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

Re: libpq object hooks

From
Tom Lane
Date:
"Merlin Moncure" <mmoncure@gmail.com> writes:
> On Fri, May 16, 2008 at 4:23 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> So you're imagining that on creation of a PGconn or PGresult, the
>> hook function would be allowed to create some storage and libpq would
>> then be responsible for tracking that storage?  Okay, but that's a
>> separate feature from the sort of passthrough I had in mind.  Where
>> exactly does the hook hand off the storage pointer to libpq?  What
>> are you going to do if the hook fails to create the storage
>> (ie, out of memory during PGresult creation)?

> Right, this could happen for one of two likely reasons: OOM as you
> suggest or the callback fails for some reason only known to the
> hooking library.  In either case, the callback would set the return
> pointer as defined by the structure to null, return FALSE, and libpq
> would not add the state to the array of 'event state datas' it
> maintains.

So at that point we have an apparently-successful creation of a
PGresult, but some of the hook library's functionality is going to fail
to work with that PGresult.  That doesn't seem very nice.  ISTM the hook
ought to be able to request that libpq return an out-of-memory failure
for the query, just as would happen if the malloc failure had happened
directly to libpq.

            regards, tom lane

Re: libpq object hooks

From
Andrew Chernow
Date:
Tom Lane wrote:
>  ISTM the hook
> ought to be able to request that libpq return an out-of-memory failure
> for the query, just as would happen if the malloc failure had happened
> directly to libpq.
>
>

I am working on this new patch and that is how I have been implementing it.  If
the eventProc function returns FALSE for creation events (not destruction
events), the libpq call that triggered it will fail.  For instance: for the
creation of result objects "PGEVT_RESULTCREATE", I am clearing the result and
returning an error value.

I think that is the expected behavior when one calls PQexec and an out-of-memory
failure occurs.

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

Re: libpq object hooks (libpq events)

From
Andrew Chernow
Date:
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 *);

Re: libpq object hooks (libpq events)

From
"Merlin Moncure"
Date:
On Sat, May 17, 2008 at 8:28 AM, Andrew Chernow <ac@esilo.com> wrote:
> 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.


Let's decide where to go with this.  We have no objections to pushing
this back to the next fest (tom's comments on the wiki were noted).
If that's the case though, we would like to wrap up a consenus on the
general implementation in the meantime.  Just give us a heads up and
I'll update the wiki.

merlin

Re: libpq object hooks (libpq events)

From
Andrew Chernow
Date:
Merlin Moncure wrote:
> On Sat, May 17, 2008 at 8:28 AM, Andrew Chernow <ac@esilo.com> wrote:
>> 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.
>
>
> Let's decide where to go with this.  We have no objections to pushing
> this back to the next fest (tom's comments on the wiki were noted).
> If that's the case though, we would like to wrap up a consenus on the
> general implementation in the meantime.  Just give us a heads up and
> I'll update the wiki.
>
> merlin
>
>

Yeah, it would be nice to avoid another push back into July by getting some
feedback now for June; it would be great to squeeze it into May.  I'm not
talking about a review, but maybe a couple of "I hate this" or "This works well"
or "Give up coding" :)  The implementation of the events (as well as when they
were object hooks) is actually very simple, so a quick glance can probably raise
most concerns.  There is very little going on.

In the end, something like this can be done several different ways (choice here
is a matter of taste & style).  It would be nice to iron out the API and focus
on other aspects; namely is this general concept appealing enough.

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

Re: libpq object hooks (libpq events)

From
Tom Lane
Date:
Andrew Chernow <ac@esilo.com> writes:
> 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.

This is starting to get there, though I am still desperately unhappy
with the notion of "global" registrations.  As I've said repeatedly,
I don't want that concept in any shape or form: there are no uses for
it that I regard as good ideas.  (And taking out the mutex protection
for the global state isn't making it better, since that makes the
plausible use cases even narrower.)

> 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.

Hmm.  "Event" for a callback seems a bit weird.  The reasons for calling
the callback are events, and by extension the information structs you
pass to it could legitimately be called events, but the callback isn't
an event.  "Event hook" or "event proc" seem possibly reasonable
nomenclature, though I'd still rather just go with "PGCallback".
Likewise I don't care for "event data", as the data inside that struct
is even less of an event than the proc is.  "Per-callback instance data"
is the best description I can think of but it seems a bit awkward.
I've gone with "instance data" and "passthrough data" in the comments
below.

Some other comments in no particular order:

* I'm inclined to split out the hook-related stuff into a separate
header instead of cluttering libpq-fe.h with it, on the theory that
it has a separate audience; average applications will not be interested
in it.

* The proposed API passes the instance data pointer via the event struct
and the passthrough data pointer as a separate parameter.  This seems
just weird.  Shouldn't we do both the same way?  This ties into my
original thought that the event data struct should be const, on the
grounds that you shouldn't have to initialize it more than once for all
the callbacks.   But I'm not quite sure how you allocate a new instance
data value if that's the case.  Maybe instead of having the ResultCreate
callback scribble on the event data structure, provide an additional
API routine to store the pointer:
    PQresultSetInstanceData(PGresult *res, PGeventProc proc,
                void *instanceData);
This would cost a couple extra cycles compared to what you have,
but surely not much in the normal case where there are very few
hooks registered.  Also, meseems you need such a callback anyway:
what if the hook library desires to realloc its instance data
larger?

* Likewise it's weird to provide PQeventData and PQresultEventData
returning one pointer as function result and one as an output
argument.  I'd suggest four functions returning results only,
perhaps
    void *PQinstanceData(const PGconn *conn, PGEventProc proc)
    void *PQpassthroughData(const PGconn *conn, PGEventProc proc)
    void *PQresultInstanceData(const PGresult *res, PGEventProc proc)
    void *PQresultPassthroughData(const PGresult *res, PGEventProc proc)
The way you have it might save a few cycles in the case where a client
needs both pointers, but I have a feeling that most callers will only
care about one or the other.

            regards, tom lane

Re: libpq object hooks (libpq events)

From
Andrew Chernow
Date:
Will make all of those changes.  We appreciate the help.

1. remove global registration :(

2. New Name: PGCallback

3. use instanceData and passThrough names (passThrough with upper 'T')

3. separate getters for conn/result instanceData and passthrough

4. add a setter for result instance data
   - There should also be a PQsetInstanceData(PGconn*, ...)
   - I see no need for a passThrough setter

5. move callback stuff to its own header, maybe pgcallback.h?

No issue with any of them.  Although, small issue below:

> Maybe instead of having the ResultCreate
> callback scribble on the event data structure, provide an additional
> API routine to store the pointer:
>     PQresultSetInstanceData(PGresult *res, PGeventProc proc,
>                 void *instanceData);

Adding PQresultSetInstanceData doesn't removes the need for a resultcreate
callback event.  This is an event the callbacks are informed about (instanceData
or not).  It does remove the need for an instance data member in all event info
structures, just use the getter/setter when desired.  If the passThrough is
needed, one can use the public getters.

 > hooks registered.  Also, meseems you need such a callback anyway:
 > what if the hook library desires to realloc its instance data
 > larger?
 >

With your suggestions, this would work:

res = PQexec(conn, "blah");
data = PQresultInstanceData(res, cbfunc);
data = realloc(data, 1024);
PQresultSetInstanceData(res, cbfunc, data);

The API user should have a valid instanceData whenever libpq returns a result,
assuming they registered a callback that allocates instance data during a
resultcreate event.

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

Re: libpq object hooks (libpq events)

From
Tom Lane
Date:
Andrew Chernow <ac@esilo.com> writes:
> 4. add a setter for result instance data
>    - There should also be a PQsetInstanceData(PGconn*, ...)
>    - I see no need for a passThrough setter

Check, though I assume we're not expecting PQsetInstanceData to
propagate to previously created PGresults?

> 5. move callback stuff to its own header, maybe pgcallback.h?

Should be libpq-something.  I was considering libpq-hooks.h or
libpq-events.h, but libpq-callback.h would be OK too.

> Adding PQresultSetInstanceData doesn't removes the need for a resultcreate
> callback event.

No, of course not.  What I was imagining was that the resultcreate
callback would call PQresultSetInstanceData.

            regards, tom lane

Re: libpq object hooks (libpq events)

From
"Merlin Moncure"
Date:
On Mon, May 19, 2008 at 10:37 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Andrew Chernow <ac@esilo.com> writes:
>> 4. add a setter for result instance data
>>    - There should also be a PQsetInstanceData(PGconn*, ...)
>>    - I see no need for a passThrough setter
>
> Check, though I assume we're not expecting PQsetInstanceData to
> propagate to previously created PGresults?
>
>> 5. move callback stuff to its own header, maybe pgcallback.h?
>
> Should be libpq-something.  I was considering libpq-hooks.h or
> libpq-events.h, but libpq-callback.h would be OK too.

I think 'events' best describes what is going on.  Your other
suggestions are improvements (we maybe tried a little too hard to
minimize exports).  Losing the global hooks is no problem (it was
basically syntax sugar).

merlin

Re: libpq object hooks (libpq events)

From
Andrew Chernow
Date:
Tom Lane wrote:
> Andrew Chernow <ac@esilo.com> writes:
>> 4. add a setter for result instance data
>>    - There should also be a PQsetInstanceData(PGconn*, ...)
>>    - I see no need for a passThrough setter
>
> Check, though I assume we're not expecting PQsetInstanceData to
> propagate to previously created PGresults?
>

No, not at all.  Already created results are on their own.  If you want to
modify the instanceData, you can use PQresultSetInstanceData.

>> 5. move callback stuff to its own header, maybe pgcallback.h?
>
> Should be libpq-something.  I was considering libpq-hooks.h or
> libpq-events.h, but libpq-callback.h would be OK too.
>

I like events.  It sounds like you wanted PGCallback, although I am starting to
think PGEventProc is better than PGcallback, only because it is more consistent
with the term events.

BTW, my suggestion to call this libpq events was not directly referring to the
callback/proc.  It was a term for describing the whole #!  I think the idea is
to notify interested parties about libpq events, the callback is just an
implementaion for doing that.

>> Adding PQresultSetInstanceData doesn't removes the need for a resultcreate
>> callback event.
>
> No, of course not.  What I was imagining was that the resultcreate
> callback would call PQresultSetInstanceData.
>

Sorry, my mistake.  You were actually very clear, my reading skills are in question.


--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/

Re: libpq object hooks (libpq events)

From
Andrew Chernow
Date:
Attached is the latest patch.  It has addressed the requested changes found
here: http://archives.postgresql.org/pgsql-patches/2008-05/msg00389.php

Its a tarball because there are two new files, libpq-events.c and
libpq-events.h.  The patch is in the tarball as well as attached to the email.

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/
Index: src/interfaces/libpq/Makefile
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/Makefile,v
retrieving revision 1.166
diff -C6 -r1.166 Makefile
*** src/interfaces/libpq/Makefile    16 Apr 2008 14:19:56 -0000    1.166
--- src/interfaces/libpq/Makefile    20 May 2008 04:18:07 -0000
***************
*** 29,41 ****
  # the object files from libpgport, this would not be true on all
  # platforms.
  LIBS := $(LIBS:-lpgport=)

  OBJS=    fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
      fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
!     md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o \
      $(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o win32error.o, $(LIBOBJS))

  ifeq ($(PORTNAME), cygwin)
  override shlib = cyg$(NAME)$(DLSUFFIX)
  endif

--- 29,41 ----
  # the object files from libpgport, this would not be true on all
  # platforms.
  LIBS := $(LIBS:-lpgport=)

  OBJS=    fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
      fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
!     md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o libpq-events.o \
      $(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o win32error.o, $(LIBOBJS))

  ifeq ($(PORTNAME), cygwin)
  override shlib = cyg$(NAME)$(DLSUFFIX)
  endif

***************
*** 103,114 ****
--- 103,115 ----

  $(top_builddir)/src/port/pg_config_paths.h:
      $(MAKE) -C $(top_builddir)/src/port pg_config_paths.h

  install: all installdirs install-lib
      $(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)'
+     $(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)'
      $(INSTALL_DATA) $(srcdir)/libpq-int.h '$(DESTDIR)$(includedir_internal)'
      $(INSTALL_DATA) $(srcdir)/pqexpbuffer.h '$(DESTDIR)$(includedir_internal)'
      $(INSTALL_DATA) $(srcdir)/pg_service.conf.sample '$(DESTDIR)$(datadir)/pg_service.conf.sample'

  installdirs: installdirs-lib
      $(mkinstalldirs) '$(DESTDIR)$(includedir)' '$(DESTDIR)$(includedir_internal)'
Index: src/interfaces/libpq/exports.txt
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/exports.txt,v
retrieving revision 1.19
diff -C6 -r1.19 exports.txt
*** src/interfaces/libpq/exports.txt    19 Mar 2008 00:39:33 -0000    1.19
--- src/interfaces/libpq/exports.txt    20 May 2008 04:18:07 -0000
***************
*** 138,143 ****
--- 138,153 ----
  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
+ PQinstanceData            146
+ PQsetInstanceData         147
+ PQresultInstanceData      148
+ PQresultSetInstanceData   149
+ PQpassThroughData         150
+ PQresultPassThroughData   151
\ 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.358
diff -C6 -r1.358 fe-connect.c
*** src/interfaces/libpq/fe-connect.c    16 May 2008 18:30:53 -0000    1.358
--- src/interfaces/libpq/fe-connect.c    20 May 2008 04:18:08 -0000
***************
*** 1970,1981 ****
--- 1970,1999 ----
   * 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 evt;
+
+     /* Let the event procs cleanup their state data */
+     for(i=0; i < conn->nEvents; i++)
+     {
+         evt.conn = conn;
+         (void)conn->events[i].proc(PGEVT_CONNDESTROY, &evt);
+     }
+
+     /* free the PGEvent array */
+     if(conn->events)
+     {
+         free(conn->events);
+         conn->events = NULL;
+         conn->nEvents = conn->eventArrSize = 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:
--- 2169,2199 ----
  PQreset(PGconn *conn)
  {
      if (conn)
      {
          closePGconn(conn);

!         if (connectDBStart(conn) && connectDBComplete(conn))
!         {
!             int i;
!             PGEventConnReset evt;
!
!             for(i=0; i < conn->nEvents; i++)
!             {
!                 evt.conn = conn;
!
!                 if(!conn->events[i].proc(PGEVT_CONNRESET, &evt))
!                 {
!                     conn->status = CONNECTION_BAD;
!                     printfPQExpBuffer(&conn->errorMessage,
!                         libpq_gettext("PGEventProc %p failed during PGEVT_CONNRESET event\n"),
!                         conn->events[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.
--- 2221,2257 ----
   * 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 evt;
!
!             for(i=0; i < conn->nEvents; i++)
!             {
!                 evt.conn = conn;
!
!                 if(!conn->events[i].proc(PGEVT_CONNRESET, &evt))
!                 {
!                     conn->status = CONNECTION_BAD;
!                     printfPQExpBuffer(&conn->errorMessage,
!                         libpq_gettext("PGEventProc %p failed during PGEVT_CONNRESET event\n"),
!                         conn->events[i].proc);
!                     return PGRES_POLLING_FAILED;
!                 }
!             }
!         }
!
!         return status;
!     }

      return PGRES_POLLING_FAILED;
  }

  /*
   * PQcancelGet: get a PGcancel structure corresponding to a connection.
Index: src/interfaces/libpq/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
*** src/interfaces/libpq/fe-exec.c    1 Jan 2008 19:46:00 -0000    1.194
--- src/interfaces/libpq/fe-exec.c    20 May 2008 04:18:08 -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 event instance data, sets this to NULL */
+ static PGEvent *
+ dupEvents(PGEvent *events, int count)
+ {
+     int i;
+     PGEvent *newEvents;
+
+     if(!events || count <= 0)
+         return NULL;
+
+     newEvents = (PGEvent *)malloc(count * sizeof(PGEvent));
+     if(!newEvents)
+         return NULL;
+
+     memcpy(newEvents, events, count * sizeof(PGEvent));
+
+     /* NULL out the data pointer */
+     for(i=0; i < count; i++)
+         newEvents[i].data = NULL;
+
+     return newEvents;
+ }
+
  /*
   * 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,216 ----
      result->errMsg = NULL;
      result->errFields = NULL;
      result->null_field[0] = '\0';
      result->curBlock = NULL;
      result->curOffset = 0;
      result->spaceLeft = 0;
+     result->nEvents = 0;
+     result->events = NULL;

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

+         /* copy events from connection */
+         if(conn->nEvents > 0)
+         {
+             result->events = dupEvents(conn->events, conn->nEvents);
+             if(!result->events)
+             {
+                 PQclear(result);
+                 return NULL;
+             }
+
+             result->nEvents = conn->nEvents;
+         }
+
          /* consider copying conn's errorMessage */
          switch (status)
          {
              case PGRES_EMPTY_QUERY:
              case PGRES_COMMAND_OK:
              case PGRES_TUPLES_OK:
***************
*** 193,204 ****
--- 234,481 ----
      }

      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_EVENTS - Indicates that the source result's
+  *   events 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 evt;
+
+     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 PGEvents. */
+     if(!(options & PG_COPYRES_NO_EVENTS) && src->nEvents > 0)
+     {
+         dest->events = dupEvents(src->events, src->nEvents);
+         if(!dest->events)
+         {
+             PQclear(dest);
+             return NULL;
+         }
+
+         dest->nEvents = src->nEvents;
+     }
+
+     /* Trigger PGEVT_RESULTCOPY event */
+     for(i=0; i < dest->nEvents; i++)
+     {
+         evt.src = src;
+         evt.dest = dest;
+         if(!dest->events[i].proc(PGEVT_RESULTCOPY, &evt))
+         {
+             PQclear(dest);
+             return NULL;
+         }
+     }
+
+     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 ****
--- 626,657 ----
   * PQclear -
   *      free's the memory associated with a PGresult
   */
  void
  PQclear(PGresult *res)
  {
+     int i;
      PGresult_data *block;
+     PGEventResultDestroy evt;

      if (!res)
          return;

+     for(i=0; i < res->nEvents; i++)
+     {
+         evt.result = res;
+         (void)res->events[i].proc(PGEVT_RESULTDESTROY, &evt);
+     }
+
+     if(res->events)
+     {
+         free(res->events);
+         res->events = NULL;
+         res->nEvents = 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);
--- 1482,1494 ----
   *      memory).
   */

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

      if (!conn)
          return NULL;

      /* Parse any available data, if our state permits. */
      parseInput(conn);
***************
*** 1265,1276 ****
--- 1557,1593 ----
                                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 evt;
+
+         for(i=0; i < res->nEvents; i++)
+         {
+             evt.conn = conn;
+             evt.result = res;
+
+             if(!res->events[i].proc(PGEVT_RESULTCREATE, &evt))
+             {
+                 char msg[256];
+                 sprintf(msg,
+                     "PGEventProc %p failed during PGEVT_RESULTCREATE event",
+                     res->events[i].proc);
+                 pqSetResultError(res, msg);
+                 res->resultStatus = PGRES_FATAL_ERROR;
+                 break;
+             }
+         }
+     }
+
      return res;
  }


  /*
   * PQexec
Index: src/interfaces/libpq/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
*** src/interfaces/libpq/fe-misc.c    1 Jan 2008 19:46:00 -0000    1.133
--- src/interfaces/libpq/fe-misc.c    20 May 2008 04:18:09 -0000
***************
*** 1152,1157 ****
--- 1152,1158 ----
      }

      return dgettext("libpq", msgid);
  }

  #endif   /* ENABLE_NLS */
+
Index: src/interfaces/libpq/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
*** src/interfaces/libpq/libpq-fe.h    19 Mar 2008 00:39:33 -0000    1.142
--- src/interfaces/libpq/libpq-fe.h    20 May 2008 04:18:09 -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_EVENTS      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,225 ----
          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;
+
+ /* ----------------
   * Exported functions of libpq
   * ----------------
   */

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

***************
*** 434,445 ****
--- 458,488 ----
   * 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,
Index: src/interfaces/libpq/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
*** src/interfaces/libpq/libpq-int.h    16 May 2008 18:30:53 -0000    1.130
--- src/interfaces/libpq/libpq-int.h    20 May 2008 04:18:09 -0000
***************
*** 19,30 ****
--- 19,31 ----

  #ifndef LIBPQ_INT_H
  #define LIBPQ_INT_H

  /* We assume libpq-fe.h has already been included. */
  #include "postgres_fe.h"
+ #include "libpq-events.h"

  #include <time.h>
  #include <sys/types.h>
  #ifndef WIN32
  #include <sys/time.h>
  #endif
***************
*** 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;

--- 98,109 ----
***************
*** 159,170 ****
--- 147,167 ----
      PQnoticeReceiver noticeRec; /* notice message receiver */
      void       *noticeRecArg;
      PQnoticeProcessor noticeProc;        /* notice message processor */
      void       *noticeProcArg;
  } PGNoticeHooks;

+ typedef struct
+ {
+     /* pointer supplied by user */
+     void *passThrough;
+     /* state data, optionally generated by event proc */
+     void *data;
+     PGEventProc proc;
+ } PGEvent;
+
  struct pg_result
  {
      int            ntups;
      int            numAttributes;
      PGresAttDesc *attDescs;
      PGresAttValue **tuples;        /* each PGresTuple is an array of
***************
*** 181,192 ****
--- 178,193 ----
       * 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 */

+     /* registered events, copied from conn */
+     int nEvents;
+     PGEvent *events;
+
      /*
       * 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 ****
--- 301,317 ----
      /* Optional file to write trace info to */
      FILE       *Pfdebug;

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

+     /* registered events via PQregisterEventProc */
+     int nEvents;
+     int eventArrSize;
+     PGEvent *events;
+
      /* Status indicators */
      ConnStatusType status;
      PGAsyncStatusType asyncStatus;
      PGTransactionStatusType xactStatus; /* never changes to ACTIVE */
      PGQueryClass queryclass;
      char       *last_query;        /* last SQL command, or NULL if unknown */

Attachment

Re: libpq object hooks (libpq events)

From
"Merlin Moncure"
Date:
On Mon, May 19, 2008 at 8:22 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Andrew Chernow <ac@esilo.com> writes:
>> 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.
>
> This is starting to get there,

Interesting you haven't commented on two areas, the actual placement
of the event callers in the libpq code (i'm assuming these are ok),
and the PQcopyResult/PQsetvalue functions.  The latter area introduces
some new behavior into standard libpq (imo) so it deserves some
scrutiny.

merlin

libpq events patch

From
Andrew Chernow
Date:
This is an updated version pf the libpqevents patch.  See

http://archives.postgresql.org/pgsql-hackers/2008-09/msg00153.php

for details.  The only change I didn't make yet is the event 'name'.  I
have put it in and taken it out twice now, so a firm 'put it in there'
would be appreciated.

Go here for libpqtypes using events.  pgfoundry is still using the older
object hooks version.

http://libpqtypes.esilo.com/libpqtypes-events.tar.gz

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/
Index: src/interfaces/libpq/Makefile
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/Makefile,v
retrieving revision 1.166
diff -C6 -r1.166 Makefile
*** src/interfaces/libpq/Makefile    16 Apr 2008 14:19:56 -0000    1.166
--- src/interfaces/libpq/Makefile    3 Sep 2008 16:06:49 -0000
***************
*** 29,41 ****
  # the object files from libpgport, this would not be true on all
  # platforms.
  LIBS := $(LIBS:-lpgport=)

  OBJS=    fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
      fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
!     md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o \
      $(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o win32error.o, $(LIBOBJS))

  ifeq ($(PORTNAME), cygwin)
  override shlib = cyg$(NAME)$(DLSUFFIX)
  endif

--- 29,41 ----
  # the object files from libpgport, this would not be true on all
  # platforms.
  LIBS := $(LIBS:-lpgport=)

  OBJS=    fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
      fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
!     md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o libpq-events.o \
      $(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o win32error.o, $(LIBOBJS))

  ifeq ($(PORTNAME), cygwin)
  override shlib = cyg$(NAME)$(DLSUFFIX)
  endif

***************
*** 103,114 ****
--- 103,115 ----

  $(top_builddir)/src/port/pg_config_paths.h:
      $(MAKE) -C $(top_builddir)/src/port pg_config_paths.h

  install: all installdirs install-lib
      $(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)'
+     $(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)'
      $(INSTALL_DATA) $(srcdir)/libpq-int.h '$(DESTDIR)$(includedir_internal)'
      $(INSTALL_DATA) $(srcdir)/pqexpbuffer.h '$(DESTDIR)$(includedir_internal)'
      $(INSTALL_DATA) $(srcdir)/pg_service.conf.sample '$(DESTDIR)$(datadir)/pg_service.conf.sample'

  installdirs: installdirs-lib
      $(mkinstalldirs) '$(DESTDIR)$(includedir)' '$(DESTDIR)$(includedir_internal)'
Index: src/interfaces/libpq/exports.txt
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/exports.txt,v
retrieving revision 1.19
diff -C6 -r1.19 exports.txt
*** src/interfaces/libpq/exports.txt    19 Mar 2008 00:39:33 -0000    1.19
--- src/interfaces/libpq/exports.txt    3 Sep 2008 16:06:49 -0000
***************
*** 138,143 ****
--- 138,154 ----
  PQsendDescribePortal      136
  lo_truncate               137
  PQconnectionUsedPassword  138
  pg_valid_server_encoding_id 139
  PQconnectionNeedsPassword 140
  lo_import_with_oid          141
+ PQcopyResult              142
+ PQsetResultAttrs          143
+ PQsetvalue                144
+ PQresultAlloc             145
+ PQregisterEventProc       146
+ PQinstanceData            147
+ PQsetInstanceData         148
+ PQresultInstanceData      149
+ PQresultSetInstanceData   150
+ PQpassThroughData         151
+ PQresultPassThroughData   152
Index: src/interfaces/libpq/fe-connect.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v
retrieving revision 1.359
diff -C6 -r1.359 fe-connect.c
*** src/interfaces/libpq/fe-connect.c    29 May 2008 22:02:44 -0000    1.359
--- src/interfaces/libpq/fe-connect.c    3 Sep 2008 16:06:49 -0000
***************
*** 1971,1982 ****
--- 1971,2000 ----
   * 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 evt;
+
+     /* Let the event procs cleanup their state data */
+     for(i=0; i < conn->nEvents; i++)
+     {
+         evt.conn = conn;
+         (void)conn->events[i].proc(PGEVT_CONNDESTROY, &evt);
+     }
+
+     /* free the PGEvent array */
+     if(conn->events)
+     {
+         free(conn->events);
+         conn->events = NULL;
+         conn->nEvents = conn->eventArrSize = 0;
+     }
+
      if (conn->pghost)
          free(conn->pghost);
      if (conn->pghostaddr)
          free(conn->pghostaddr);
      if (conn->pgport)
          free(conn->pgport);
***************
*** 2152,2165 ****
  PQreset(PGconn *conn)
  {
      if (conn)
      {
          closePGconn(conn);

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


  /*
   * PQresetStart:
--- 2170,2200 ----
  PQreset(PGconn *conn)
  {
      if (conn)
      {
          closePGconn(conn);

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


  /*
   * PQresetStart:
***************
*** 2187,2199 ****
   * 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.
--- 2222,2258 ----
   * 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 evt;
!
!             for(i=0; i < conn->nEvents; i++)
!             {
!                 evt.conn = conn;
!
!                 if(!conn->events[i].proc(PGEVT_CONNRESET, &evt))
!                 {
!                     conn->status = CONNECTION_BAD;
!                     printfPQExpBuffer(&conn->errorMessage,
!                         libpq_gettext("PGEventProc %p failed during PGEVT_CONNRESET event\n"),
!                         conn->events[i].proc);
!                     return PGRES_POLLING_FAILED;
!                 }
!             }
!         }
!
!         return status;
!     }

      return PGRES_POLLING_FAILED;
  }

  /*
   * PQcancelGet: get a PGcancel structure corresponding to a connection.
Index: src/interfaces/libpq/fe-exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v
retrieving revision 1.196
diff -C6 -r1.196 fe-exec.c
*** src/interfaces/libpq/fe-exec.c    23 Jun 2008 21:10:49 -0000    1.196
--- src/interfaces/libpq/fe-exec.c    3 Sep 2008 16:06:50 -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 event instance data, sets this to NULL */
+ static PGEvent *
+ dupEvents(PGEvent *events, int count)
+ {
+     int i;
+     PGEvent *newEvents;
+
+     if(!events || count <= 0)
+         return NULL;
+
+     newEvents = (PGEvent *)malloc(count * sizeof(PGEvent));
+     if(!newEvents)
+         return NULL;
+
+     memcpy(newEvents, events, count * sizeof(PGEvent));
+
+     /* NULL out the data pointer */
+     for(i=0; i < count; i++)
+         newEvents[i].data = NULL;
+
+     return newEvents;
+ }
+
  /*
   * 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,216 ----
      result->errMsg = NULL;
      result->errFields = NULL;
      result->null_field[0] = '\0';
      result->curBlock = NULL;
      result->curOffset = 0;
      result->spaceLeft = 0;
+     result->nEvents = 0;
+     result->events = NULL;

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

+         /* copy events from connection */
+         if(conn->nEvents > 0)
+         {
+             result->events = dupEvents(conn->events, conn->nEvents);
+             if(!result->events)
+             {
+                 PQclear(result);
+                 return NULL;
+             }
+
+             result->nEvents = conn->nEvents;
+         }
+
          /* consider copying conn's errorMessage */
          switch (status)
          {
              case PGRES_EMPTY_QUERY:
              case PGRES_COMMAND_OK:
              case PGRES_TUPLES_OK:
***************
*** 192,203 ****
--- 233,487 ----
          result->client_encoding = PG_SQL_ASCII;
      }

      return result;
  }

+ /* PQsetResultAttrs
+  * Set the attributes for a given result.  This function fails if there are
+  * already attributes contained in the provided result.  The call is
+  * ignored if numAttributes is is zero or attDescs is NULL.  If the
+  * function fails, it returns zero.  If the function succeeds, it
+  * returns a non-zero value.
+  */
+ int
+ PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs)
+ {
+     int i;
+
+     /* If attrs already exist, they cannot be overwritten. */
+     if(!res || res->numAttributes > 0)
+         return FALSE;
+
+     /* ignore request */
+     if(numAttributes <= 0 || !attDescs)
+         return TRUE;
+
+     res->attDescs = (PGresAttDesc *)PQresultAlloc(res,
+         numAttributes * sizeof(PGresAttDesc));
+
+     if(!res->attDescs)
+         return FALSE;
+
+     res->numAttributes = numAttributes;
+     memcpy(res->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).
+      */
+     res->binary = 1;
+     for(i=0; i < res->numAttributes; i++)
+     {
+         if(res->attDescs[i].name)
+             res->attDescs[i].name = pqResultStrdup(res, res->attDescs[i].name);
+         else
+             res->attDescs[i].name = res->null_field;
+
+         if(!res->attDescs[i].name)
+             return FALSE;
+
+         /* 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 TRUE;
+ }
+
+ /*
+  * 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.  The created result is always put into the
+  * PGRES_TUPLES_OK status.  The source result error message is not copied,
+  * although cmdStatus is.
+  *
+  * To set custom attributes, see PQsetResultAttrs.  That function requires
+  * that there are no attrs contained in the result, so to use that
+  * function you cannot use the PG_COPYRES_ATTRS or PG_COPYRES_TUPLES
+  * options with this function.
+  *
+  * Options:
+  *   PG_COPYRES_ATTRS - Copy the source result's attributes
+  *
+  *   PG_COPYRES_TUPLES - Copy the source result's tuples.  This implies
+  *   copying the attrs, being how the attrs are needed by the tuples.
+  *
+  *   PG_COPYRES_EVENTS - Copy the source result's events.
+  *
+  *   PG_COPYRES_NOTICEHOOKS - Copy the source result's notice hooks.
+  */
+
+ PGresult *
+ PQcopyResult(const PGresult *src, int options)
+ {
+     int i;
+     PGresult *dest;
+     PGEventResultCopy evt;
+
+     if(!src)
+         return NULL;
+
+     /* Automatically turn on attrs options because you can't copy tuples
+      * without copying the attrs.  _TUPLES implies _ATTRS.
+      */
+     if(options & PG_COPYRES_TUPLES)
+         options |= PG_COPYRES_ATTRS;
+
+     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_NOTICEHOOKS)
+         dest->noticeHooks = src->noticeHooks;
+
+     /* Wants attrs */
+     if((options & PG_COPYRES_ATTRS) &&
+          !PQsetResultAttrs(dest, src->numAttributes, src->attDescs))
+     {
+         PQclear(dest);
+         return NULL;
+     }
+
+     /* Wants to copy result tuples: use PQsetvalue(). */
+     if((options & PG_COPYRES_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 PGEvents. */
+     if((options & PG_COPYRES_EVENTS) && src->nEvents > 0)
+     {
+         dest->events = dupEvents(src->events, src->nEvents);
+         if(!dest->events)
+         {
+             PQclear(dest);
+             return NULL;
+         }
+
+         dest->nEvents = src->nEvents;
+     }
+
+     /* Trigger PGEVT_RESULTCOPY event */
+     for(i=0; i < dest->nEvents; i++)
+     {
+         evt.src = src;
+         evt.dest = dest;
+         if(!dest->events[i].proc(PGEVT_RESULTCOPY, &evt))
+         {
+             PQclear(dest);
+             return NULL;
+         }
+     }
+
+     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
***************
*** 349,365 ****
--- 633,664 ----
   * PQclear -
   *      free's the memory associated with a PGresult
   */
  void
  PQclear(PGresult *res)
  {
+     int i;
      PGresult_data *block;
+     PGEventResultDestroy evt;

      if (!res)
          return;

+     for(i=0; i < res->nEvents; i++)
+     {
+         evt.result = res;
+         (void)res->events[i].proc(PGEVT_RESULTDESTROY, &evt);
+     }
+
+     if(res->events)
+     {
+         free(res->events);
+         res->events = NULL;
+         res->nEvents = 0;
+     }
+
      /* Free all the subsidiary blocks */
      while ((block = res->curBlock) != NULL)
      {
          res->curBlock = block->next;
          free(block);
      }
***************
*** 1192,1204 ****
   *      memory).
   */

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

      if (!conn)
          return NULL;

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

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

      if (!conn)
          return NULL;

      /* Parse any available data, if our state permits. */
      parseInput(conn);
***************
*** 1267,1278 ****
--- 1566,1602 ----
                                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 evt;
+
+         for(i=0; i < res->nEvents; i++)
+         {
+             evt.conn = conn;
+             evt.result = res;
+
+             if(!res->events[i].proc(PGEVT_RESULTCREATE, &evt))
+             {
+                 char msg[256];
+                 sprintf(msg,
+                     "PGEventProc %p failed during PGEVT_RESULTCREATE event",
+                     res->events[i].proc);
+                 pqSetResultError(res, msg);
+                 res->resultStatus = PGRES_FATAL_ERROR;
+                 break;
+             }
+         }
+     }
+
      return res;
  }


  /*
   * PQexec
Index: src/interfaces/libpq/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
*** src/interfaces/libpq/libpq-fe.h    19 Mar 2008 00:39:33 -0000    1.142
--- src/interfaces/libpq/libpq-fe.h    3 Sep 2008 16:06:50 -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_ATTRS          0x01
+ #define PG_COPYRES_TUPLES         0x02 /* Implies PG_COPYRES_ATTRS */
+ #define PG_COPYRES_EVENTS         0x04
+ #define PG_COPYRES_NOTICEHOOKS    0x08
+
  /* Application-visible enum types */

  typedef enum
  {
      /*
       * Although it is okay to add to this list, values which become unused
***************
*** 190,201 ****
--- 199,225 ----
          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;
+
+ /* ----------------
   * Exported functions of libpq
   * ----------------
   */

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

***************
*** 434,445 ****
--- 458,490 ----
   * 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 options);
+
+ extern int
+ PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs);
+
+ 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,
Index: src/interfaces/libpq/libpq-int.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v
retrieving revision 1.131
diff -C6 -r1.131 libpq-int.h
*** src/interfaces/libpq/libpq-int.h    29 May 2008 22:02:44 -0000    1.131
--- src/interfaces/libpq/libpq-int.h    3 Sep 2008 16:06:50 -0000
***************
*** 19,30 ****
--- 19,31 ----

  #ifndef LIBPQ_INT_H
  #define LIBPQ_INT_H

  /* We assume libpq-fe.h has already been included. */
  #include "postgres_fe.h"
+ #include "libpq-events.h"

  #include <time.h>
  #include <sys/types.h>
  #ifndef WIN32
  #include <sys/time.h>
  #endif
***************
*** 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;

--- 98,109 ----
***************
*** 159,170 ****
--- 147,167 ----
      PQnoticeReceiver noticeRec; /* notice message receiver */
      void       *noticeRecArg;
      PQnoticeProcessor noticeProc;        /* notice message processor */
      void       *noticeProcArg;
  } PGNoticeHooks;

+ typedef struct
+ {
+     /* pointer supplied by user */
+     void *passThrough;
+     /* state (instance) data, optionally generated by event proc */
+     void *data;
+     PGEventProc proc;
+ } PGEvent;
+
  struct pg_result
  {
      int            ntups;
      int            numAttributes;
      PGresAttDesc *attDescs;
      PGresAttValue **tuples;        /* each PGresTuple is an array of
***************
*** 181,192 ****
--- 178,193 ----
       * 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 */

+     /* registered events, copied from conn */
+     int nEvents;
+     PGEvent *events;
+
      /*
       * 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 ****
--- 301,317 ----
      /* Optional file to write trace info to */
      FILE       *Pfdebug;

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

+     /* registered events via PQregisterEventProc */
+     int nEvents;
+     int eventArrSize;
+     PGEvent *events;
+
      /* Status indicators */
      ConnStatusType status;
      PGAsyncStatusType asyncStatus;
      PGTransactionStatusType xactStatus; /* never changes to ACTIVE */
      PGQueryClass queryclass;
      char       *last_query;        /* last SQL command, or NULL if unknown */
Index: src/interfaces/libpq/pthread-win32.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/pthread-win32.c,v
retrieving revision 1.17
diff -C6 -r1.17 pthread-win32.c
*** src/interfaces/libpq/pthread-win32.c    21 May 2008 14:20:48 -0000    1.17
--- src/interfaces/libpq/pthread-win32.c    3 Sep 2008 16:06:50 -0000
***************
*** 2,14 ****
  *
  * pthread-win32.c
  *     partial pthread implementation for win32
  *
  * Copyright (c) 2004-2008, PostgreSQL Global Development Group
  * IDENTIFICATION
! *    $PostgreSQL: pgsql/src/interfaces/libpq/pthread-win32.c,v 1.17 2008/05/21 14:20:48 mha Exp $
  *
  *-------------------------------------------------------------------------
  */

  #include "postgres_fe.h"

--- 2,14 ----
  *
  * pthread-win32.c
  *     partial pthread implementation for win32
  *
  * Copyright (c) 2004-2008, PostgreSQL Global Development Group
  * IDENTIFICATION
! *    $PostgreSQL: pgsql/src/interfaces/libpq/pthread-win32.c,v 1.16 2008/05/16 18:30:53 mha Exp $
  *
  *-------------------------------------------------------------------------
  */

  #include "postgres_fe.h"


Attachment

Re: libpq events patch

From
Alvaro Herrera
Date:
Andrew Chernow wrote:
> This is an updated version pf the libpqevents patch.  See
>
> http://archives.postgresql.org/pgsql-hackers/2008-09/msg00153.php
>
> for details.  The only change I didn't make yet is the event 'name'.  I
> have put it in and taken it out twice now, so a firm 'put it in there'
> would be appreciated.

You didn't merge the other changes I did to your patch.  Please use that
one as starting point, or merge them into your version.  They were
minor, sure, but I went some lengths to make them, which means the code
kinda has my signoff that way.  Please don't waste that work.

--
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Re: libpq events patch

From
Andrew Chernow
Date:
Andrew Chernow wrote:
> This is an updated version pf the libpqevents patch.  See
>
> http://archives.postgresql.org/pgsql-hackers/2008-09/msg00153.php
>
> for details.  The only change I didn't make yet is the event 'name'.  I
> have put it in and taken it out twice now, so a firm 'put it in there'
> would be appreciated.
>
> Go here for libpqtypes using events.  pgfoundry is still using the older
> object hooks version.
>
> http://libpqtypes.esilo.com/libpqtypes-events.tar.gz
>
>

Patch update again.  This one includes an optional event name for
debugging purposes.  It also includes the changes made by Alvaro that I
missed.

PQregisterEventProc now takes a name argumet.  If the name is NULL, the
error message will identify the event procedure by its address ...
"addr:%p".  If its not NULL, error messages will indicate the provided name.

I updated the styling in libpq-events.c and a couple places in fe-exec.c
that Alvaro missed.

--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/
Index: src/interfaces/libpq/Makefile
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/Makefile,v
retrieving revision 1.166
diff -C6 -r1.166 Makefile
*** src/interfaces/libpq/Makefile    16 Apr 2008 14:19:56 -0000    1.166
--- src/interfaces/libpq/Makefile    3 Sep 2008 21:55:06 -0000
***************
*** 29,41 ****
  # the object files from libpgport, this would not be true on all
  # platforms.
  LIBS := $(LIBS:-lpgport=)

  OBJS=    fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
      fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
!     md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o \
      $(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o win32error.o, $(LIBOBJS))

  ifeq ($(PORTNAME), cygwin)
  override shlib = cyg$(NAME)$(DLSUFFIX)
  endif

--- 29,41 ----
  # the object files from libpgport, this would not be true on all
  # platforms.
  LIBS := $(LIBS:-lpgport=)

  OBJS=    fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
      fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
!     md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o libpq-events.o \
      $(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o win32error.o, $(LIBOBJS))

  ifeq ($(PORTNAME), cygwin)
  override shlib = cyg$(NAME)$(DLSUFFIX)
  endif

***************
*** 103,123 ****

  $(top_builddir)/src/port/pg_config_paths.h:
      $(MAKE) -C $(top_builddir)/src/port pg_config_paths.h

  install: all installdirs install-lib
      $(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)'
      $(INSTALL_DATA) $(srcdir)/libpq-int.h '$(DESTDIR)$(includedir_internal)'
      $(INSTALL_DATA) $(srcdir)/pqexpbuffer.h '$(DESTDIR)$(includedir_internal)'
      $(INSTALL_DATA) $(srcdir)/pg_service.conf.sample '$(DESTDIR)$(datadir)/pg_service.conf.sample'

  installdirs: installdirs-lib
      $(mkinstalldirs) '$(DESTDIR)$(includedir)' '$(DESTDIR)$(includedir_internal)'

  uninstall: uninstall-lib
!     rm -f '$(DESTDIR)$(includedir)/libpq-fe.h' '$(DESTDIR)$(includedir_internal)/libpq-int.h'
'$(DESTDIR)$(includedir_internal)/pqexpbuffer.h''$(DESTDIR)$(datadir)/pg_service.conf.sample' 

  clean distclean: clean-lib
      rm -f $(OBJS) pg_config_paths.h crypt.c getaddrinfo.c inet_aton.c noblock.c open.c pgstrcasecmp.c snprintf.c
strerror.cstrlcpy.c thread.c md5.c ip.c encnames.c wchar.c win32error.c pgsleep.c pthread.h libpq.rc 
  # Might be left over from a Win32 client-only build
      rm -f pg_config_paths.h

--- 103,124 ----

  $(top_builddir)/src/port/pg_config_paths.h:
      $(MAKE) -C $(top_builddir)/src/port pg_config_paths.h

  install: all installdirs install-lib
      $(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)'
+     $(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)'
      $(INSTALL_DATA) $(srcdir)/libpq-int.h '$(DESTDIR)$(includedir_internal)'
      $(INSTALL_DATA) $(srcdir)/pqexpbuffer.h '$(DESTDIR)$(includedir_internal)'
      $(INSTALL_DATA) $(srcdir)/pg_service.conf.sample '$(DESTDIR)$(datadir)/pg_service.conf.sample'

  installdirs: installdirs-lib
      $(mkinstalldirs) '$(DESTDIR)$(includedir)' '$(DESTDIR)$(includedir_internal)'

  uninstall: uninstall-lib
!     rm -f '$(DESTDIR)$(includedir)/libpq-fe.h' '$(DESTDIR)$(includedir)/libpq-events.h'
'$(DESTDIR)$(includedir_internal)/libpq-int.h''$(DESTDIR)$(includedir_internal)/pqexpbuffer.h'
'$(DESTDIR)$(datadir)/pg_service.conf.sample'

  clean distclean: clean-lib
      rm -f $(OBJS) pg_config_paths.h crypt.c getaddrinfo.c inet_aton.c noblock.c open.c pgstrcasecmp.c snprintf.c
strerror.cstrlcpy.c thread.c md5.c ip.c encnames.c wchar.c win32error.c pgsleep.c pthread.h libpq.rc 
  # Might be left over from a Win32 client-only build
      rm -f pg_config_paths.h

Index: src/interfaces/libpq/exports.txt
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/exports.txt,v
retrieving revision 1.19
diff -C6 -r1.19 exports.txt
*** src/interfaces/libpq/exports.txt    19 Mar 2008 00:39:33 -0000    1.19
--- src/interfaces/libpq/exports.txt    3 Sep 2008 21:55:06 -0000
***************
*** 138,143 ****
--- 138,154 ----
  PQsendDescribePortal      136
  lo_truncate               137
  PQconnectionUsedPassword  138
  pg_valid_server_encoding_id 139
  PQconnectionNeedsPassword 140
  lo_import_with_oid          141
+ PQcopyResult              142
+ PQsetResultAttrs          143
+ PQsetvalue                144
+ PQresultAlloc             145
+ PQregisterEventProc       146
+ PQinstanceData            147
+ PQsetInstanceData         148
+ PQresultInstanceData      149
+ PQresultSetInstanceData   150
+ PQpassThroughData         151
+ PQresultPassThroughData   152
Index: src/interfaces/libpq/fe-connect.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v
retrieving revision 1.359
diff -C6 -r1.359 fe-connect.c
*** src/interfaces/libpq/fe-connect.c    29 May 2008 22:02:44 -0000    1.359
--- src/interfaces/libpq/fe-connect.c    3 Sep 2008 21:55:06 -0000
***************
*** 1971,1982 ****
--- 1971,2002 ----
   * 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 evt;
+
+     /* Let the event procs cleanup their state data */
+     for (i = 0; i < conn->nEvents; i++)
+     {
+         evt.conn = conn;
+         (void)conn->events[i].proc(PGEVT_CONNDESTROY, &evt);
+         if (conn->events[i].name)
+             free(conn->events[i].name);
+     }
+
+     /* free the PGEvent array */
+     if (conn->events)
+     {
+         free(conn->events);
+         conn->events = NULL;
+         conn->nEvents = conn->eventArrSize = 0;
+     }
+
      if (conn->pghost)
          free(conn->pghost);
      if (conn->pghostaddr)
          free(conn->pghostaddr);
      if (conn->pgport)
          free(conn->pgport);
***************
*** 2152,2165 ****
  PQreset(PGconn *conn)
  {
      if (conn)
      {
          closePGconn(conn);

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


  /*
   * PQresetStart:
--- 2172,2207 ----
  PQreset(PGconn *conn)
  {
      if (conn)
      {
          closePGconn(conn);

!         if (connectDBStart(conn) && connectDBComplete(conn))
!         {
!             int i;
!             PGEventConnReset evt;
!
!             for (i = 0; i < conn->nEvents; i++)
!             {
!                 evt.conn = conn;
!
!                 if (!conn->events[i].proc(PGEVT_CONNRESET, &evt))
!                 {
!                     conn->status = CONNECTION_BAD;
!                     if (conn->events[i].name)
!                         printfPQExpBuffer(&conn->errorMessage,
!                             libpq_gettext("PGEventProc \"%s\" failed during PGEVT_CONNRESET event\n"),
!                             conn->events[i].name);
!                     else
!                         printfPQExpBuffer(&conn->errorMessage,
!                             libpq_gettext("PGEventProc \"addr:%p\" failed during PGEVT_CONNRESET event\n"),
!                             conn->events[i].proc);
!                     break;
!                 }
!             }
!         }
      }
  }


  /*
   * PQresetStart:
***************
*** 2187,2199 ****
   * 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.
--- 2229,2270 ----
   * 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 evt;
!
!             for (i = 0; i < conn->nEvents; i++)
!             {
!                 evt.conn = conn;
!
!                 if (!conn->events[i].proc(PGEVT_CONNRESET, &evt))
!                 {
!                     conn->status = CONNECTION_BAD;
!                     if (conn->events[i].name)
!                         printfPQExpBuffer(&conn->errorMessage,
!                             libpq_gettext("PGEventProc \"%s\" failed during PGEVT_CONNRESET event\n"),
!                             conn->events[i].name);
!                     else
!                         printfPQExpBuffer(&conn->errorMessage,
!                             libpq_gettext("PGEventProc \"addr:%p\" failed during PGEVT_CONNRESET event\n"),
!                             conn->events[i].proc);
!                     return PGRES_POLLING_FAILED;
!                 }
!             }
!         }
!
!         return status;
!     }

      return PGRES_POLLING_FAILED;
  }

  /*
   * PQcancelGet: get a PGcancel structure corresponding to a connection.
Index: src/interfaces/libpq/fe-exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v
retrieving revision 1.196
diff -C6 -r1.196 fe-exec.c
*** src/interfaces/libpq/fe-exec.c    23 Jun 2008 21:10:49 -0000    1.196
--- src/interfaces/libpq/fe-exec.c    3 Sep 2008 21:55:07 -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,171 ----
  #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 event instance data, sets this to NULL */
+ static PGEvent *
+ dupEvents(PGEvent *events, int count)
+ {
+     int i;
+     PGEvent *newEvents;
+
+     if (!events || count <= 0)
+         return NULL;
+
+     newEvents = (PGEvent *) malloc(count * sizeof(PGEvent));
+     if (!newEvents)
+         return NULL;
+
+     memcpy(newEvents, events, count * sizeof(PGEvent));
+
+     /* NULL out the data pointer and deep copy name */
+     for (i = 0; i < count; i++)
+     {
+         if (newEvents[i].name)
+             newEvents[i].name = strdup(newEvents[i].name);
+         newEvents[i].data = NULL;
+     }
+
+     return newEvents;
+ }
+
  /*
   * 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 ****
--- 187,220 ----
      result->errMsg = NULL;
      result->errFields = NULL;
      result->null_field[0] = '\0';
      result->curBlock = NULL;
      result->curOffset = 0;
      result->spaceLeft = 0;
+     result->nEvents = 0;
+     result->events = NULL;

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

+         /* copy events from connection */
+         if (conn->nEvents > 0)
+         {
+             result->events = dupEvents(conn->events, conn->nEvents);
+             if (!result->events)
+             {
+                 PQclear(result);
+                 return NULL;
+             }
+
+             result->nEvents = conn->nEvents;
+         }
+
          /* consider copying conn's errorMessage */
          switch (status)
          {
              case PGRES_EMPTY_QUERY:
              case PGRES_COMMAND_OK:
              case PGRES_TUPLES_OK:
***************
*** 192,203 ****
--- 237,496 ----
          result->client_encoding = PG_SQL_ASCII;
      }

      return result;
  }

+ /* PQsetResultAttrs
+  * Set the attributes for a given result.  This function fails if there are
+  * already attributes contained in the provided result.  The call is
+  * ignored if numAttributes is is zero or attDescs is NULL.  If the
+  * function fails, it returns zero.  If the function succeeds, it
+  * returns a non-zero value.
+  */
+ int
+ PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs)
+ {
+     int i;
+
+     /* If attrs already exist, they cannot be overwritten. */
+     if (!res || res->numAttributes > 0)
+         return FALSE;
+
+     /* ignore request */
+     if (numAttributes <= 0 || !attDescs)
+         return TRUE;
+
+     res->attDescs = (PGresAttDesc *) PQresultAlloc(res,
+         numAttributes * sizeof(PGresAttDesc));
+
+     if (!res->attDescs)
+         return FALSE;
+
+     res->numAttributes = numAttributes;
+     memcpy(res->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).
+      */
+     res->binary = 1;
+     for (i = 0; i < res->numAttributes; i++)
+     {
+         if (res->attDescs[i].name)
+             res->attDescs[i].name = pqResultStrdup(res, res->attDescs[i].name);
+         else
+             res->attDescs[i].name = res->null_field;
+
+         if (!res->attDescs[i].name)
+             return FALSE;
+
+         /* 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 TRUE;
+ }
+
+ /*
+  * 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.  The created result is always put into the
+  * PGRES_TUPLES_OK status.  The source result error message is not copied,
+  * although cmdStatus is.
+  *
+  * To set custom attributes, see PQsetResultAttrs.  That function requires
+  * that there are no attrs contained in the result, so to use that
+  * function you cannot use the PG_COPYRES_ATTRS or PG_COPYRES_TUPLES
+  * options with this function.
+  *
+  * Options:
+  *   PG_COPYRES_ATTRS - Copy the source result's attributes
+  *
+  *   PG_COPYRES_TUPLES - Copy the source result's tuples.  This implies
+  *   copying the attrs, being how the attrs are needed by the tuples.
+  *
+  *   PG_COPYRES_EVENTS - Copy the source result's events.
+  *
+  *   PG_COPYRES_NOTICEHOOKS - Copy the source result's notice hooks.
+  */
+
+ PGresult *
+ PQcopyResult(const PGresult *src, int options)
+ {
+     int i;
+     PGresult *dest;
+     PGEventResultCopy evt;
+
+     if (!src)
+         return NULL;
+
+     /* Automatically turn on attrs options because you can't copy tuples
+      * without copying the attrs.  _TUPLES implies _ATTRS.
+      */
+     if (options & PG_COPYRES_TUPLES)
+         options |= PG_COPYRES_ATTRS;
+
+     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_NOTICEHOOKS)
+         dest->noticeHooks = src->noticeHooks;
+
+     /* Wants attrs */
+     if ((options & PG_COPYRES_ATTRS) &&
+          !PQsetResultAttrs(dest, src->numAttributes, src->attDescs))
+     {
+         PQclear(dest);
+         return NULL;
+     }
+
+     /* Wants to copy result tuples: use PQsetvalue(). */
+     if ((options & PG_COPYRES_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 PGEvents. */
+     if ((options & PG_COPYRES_EVENTS) && src->nEvents > 0)
+     {
+         dest->events = dupEvents(src->events, src->nEvents);
+         if (!dest->events)
+         {
+             PQclear(dest);
+             return NULL;
+         }
+
+         dest->nEvents = src->nEvents;
+     }
+
+     /* Trigger PGEVT_RESULTCOPY event */
+     for (i = 0; i < dest->nEvents; i++)
+     {
+         evt.src = src;
+         evt.dest = dest;
+         if (!dest->events[i].proc(PGEVT_RESULTCOPY, &evt))
+         {
+             PQclear(dest);
+             return NULL;
+         }
+     }
+
+     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 * 2 : 128;
+         PGresAttValue **tups;
+
+         if (res->tuples)
+             tups = (PGresAttValue **) realloc(res->tuples, n * sizeof(PGresAttValue *));
+         else
+             tups = (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;
+
+         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;
+
+         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
***************
*** 349,365 ****
--- 642,675 ----
   * PQclear -
   *      free's the memory associated with a PGresult
   */
  void
  PQclear(PGresult *res)
  {
+     int i;
      PGresult_data *block;
+     PGEventResultDestroy evt;

      if (!res)
          return;

+     for (i = 0; i < res->nEvents; i++)
+     {
+         evt.result = res;
+         (void)res->events[i].proc(PGEVT_RESULTDESTROY, &evt);
+         if (res->events[i].name)
+             free(res->events[i].name);
+     }
+
+     if (res->events)
+     {
+         free(res->events);
+         res->events = NULL;
+         res->nEvents = 0;
+     }
+
      /* Free all the subsidiary blocks */
      while ((block = res->curBlock) != NULL)
      {
          res->curBlock = block->next;
          free(block);
      }
***************
*** 1192,1204 ****
   *      memory).
   */

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

      if (!conn)
          return NULL;

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

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

      if (!conn)
          return NULL;

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

+     if (res && res->nEvents > 0 &&
+         (res->resultStatus == PGRES_COMMAND_OK ||
+          res->resultStatus == PGRES_TUPLES_OK ||
+          res->resultStatus == PGRES_EMPTY_QUERY))
+     {
+         int i;
+         PGEventResultCreate evt;
+
+         for (i = 0; i < res->nEvents; i++)
+         {
+             evt.conn = conn;
+             evt.result = res;
+
+             if (!res->events[i].proc(PGEVT_RESULTCREATE, &evt))
+             {
+                 char msg[256];
+
+                 if (res->events[i].name)
+                     sprintf(msg,
+                         "PGEventProc \"%s\" failed during PGEVT_RESULTCREATE event",
+                         res->events[i].name);
+                 else
+                     sprintf(msg,
+                         "PGEventProc \"addr:%p\" failed during PGEVT_RESULTCREATE event",
+                         res->events[i].proc);
+
+                 pqSetResultError(res, msg);
+                 res->resultStatus = PGRES_FATAL_ERROR;
+                 break;
+             }
+         }
+     }
+
      return res;
  }


  /*
   * PQexec
Index: src/interfaces/libpq/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
*** src/interfaces/libpq/libpq-fe.h    19 Mar 2008 00:39:33 -0000    1.142
--- src/interfaces/libpq/libpq-fe.h    3 Sep 2008 21:55:07 -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_ATTRS          0x01
+ #define PG_COPYRES_TUPLES         0x02 /* Implies PG_COPYRES_ATTRS */
+ #define PG_COPYRES_EVENTS         0x04
+ #define PG_COPYRES_NOTICEHOOKS    0x08
+
  /* Application-visible enum types */

  typedef enum
  {
      /*
       * Although it is okay to add to this list, values which become unused
***************
*** 190,201 ****
--- 199,225 ----
          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;
+
+ /* ----------------
   * Exported functions of libpq
   * ----------------
   */

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

***************
*** 434,445 ****
--- 458,489 ----
   * 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 options);
+
+ extern int
+ PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs);
+
+ 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,
Index: src/interfaces/libpq/libpq-int.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v
retrieving revision 1.131
diff -C6 -r1.131 libpq-int.h
*** src/interfaces/libpq/libpq-int.h    29 May 2008 22:02:44 -0000    1.131
--- src/interfaces/libpq/libpq-int.h    3 Sep 2008 21:55:07 -0000
***************
*** 19,30 ****
--- 19,31 ----

  #ifndef LIBPQ_INT_H
  #define LIBPQ_INT_H

  /* We assume libpq-fe.h has already been included. */
  #include "postgres_fe.h"
+ #include "libpq-events.h"

  #include <time.h>
  #include <sys/types.h>
  #ifndef WIN32
  #include <sys/time.h>
  #endif
***************
*** 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;

--- 98,109 ----
***************
*** 159,170 ****
--- 147,166 ----
      PQnoticeReceiver noticeRec; /* notice message receiver */
      void       *noticeRecArg;
      PQnoticeProcessor noticeProc;        /* notice message processor */
      void       *noticeProcArg;
  } PGNoticeHooks;

+ typedef struct
+ {
+     char       *name;    /* for error messages */
+     void       *passThrough; /* pointer supplied by user */
+     void       *data; /* state (instance) data, optionally generated by event proc */
+     PGEventProc proc; /* the function to call on events */
+ } PGEvent;
+
  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 */

+     /* registered events, copied from conn */
+     int nEvents;
+     PGEvent *events;
+
      /*
       * 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;

+     /* registered events via PQregisterEventProc */
+     int nEvents;
+     int eventArrSize;
+     PGEvent *events;
+
      /* Status indicators */
      ConnStatusType status;
      PGAsyncStatusType asyncStatus;
      PGTransactionStatusType xactStatus; /* never changes to ACTIVE */
      PGQueryClass queryclass;
      char       *last_query;        /* last SQL command, or NULL if unknown */
Index: src/interfaces/libpq/pthread-win32.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/pthread-win32.c,v
retrieving revision 1.17
diff -C6 -r1.17 pthread-win32.c
*** src/interfaces/libpq/pthread-win32.c    21 May 2008 14:20:48 -0000    1.17
--- src/interfaces/libpq/pthread-win32.c    3 Sep 2008 21:55:07 -0000
***************
*** 2,14 ****
  *
  * pthread-win32.c
  *     partial pthread implementation for win32
  *
  * Copyright (c) 2004-2008, PostgreSQL Global Development Group
  * IDENTIFICATION
! *    $PostgreSQL: pgsql/src/interfaces/libpq/pthread-win32.c,v 1.17 2008/05/21 14:20:48 mha Exp $
  *
  *-------------------------------------------------------------------------
  */

  #include "postgres_fe.h"

--- 2,14 ----
  *
  * pthread-win32.c
  *     partial pthread implementation for win32
  *
  * Copyright (c) 2004-2008, PostgreSQL Global Development Group
  * IDENTIFICATION
! *    $PostgreSQL: pgsql/src/interfaces/libpq/pthread-win32.c,v 1.16 2008/05/16 18:30:53 mha Exp $
  *
  *-------------------------------------------------------------------------
  */

  #include "postgres_fe.h"


Attachment