]> granicus.if.org Git - postgresql/commitdiff
Add an "events" system to libpq, whereby applications can get callbacks that
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 17 Sep 2008 04:31:08 +0000 (04:31 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 17 Sep 2008 04:31:08 +0000 (04:31 +0000)
enable them to manage private data associated with PGconns and PGresults.

Andrew Chernow and Merlin Moncure

doc/src/sgml/libpq.sgml
src/interfaces/libpq/Makefile
src/interfaces/libpq/exports.txt
src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/fe-exec.c
src/interfaces/libpq/libpq-events.c [new file with mode: 0644]
src/interfaces/libpq/libpq-events.h [new file with mode: 0644]
src/interfaces/libpq/libpq-fe.h
src/interfaces/libpq/libpq-int.h
src/tools/msvc/Install.pm

index deb052e5c49421d262aa81be20fae0728beee3e9..2db369e906dfbb005e17201ad42c8ea7e728dcce 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.260 2008/06/27 02:44:31 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.261 2008/09/17 04:31:08 tgl Exp $ -->
 
 <chapter id="libpq">
  <title><application>libpq</application> - C Library</title>
@@ -2063,38 +2063,6 @@ PGresult *PQdescribePortal(PGconn *conn, const char *portalName);
        </para>
       </listitem>
      </varlistentry>
-
-     <varlistentry>
-      <term>
-       <function>PQmakeEmptyPGresult</function>
-       <indexterm>
-        <primary>PQmakeEmptyPGresult</primary>
-       </indexterm>
-      </term>
-
-      <listitem>
-       <para>
-        Constructs an empty <structname>PGresult</structname> object with the given status.
-        <synopsis>
-         PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status);
-        </synopsis>
-       </para>
-
-       <para>
-        This is <application>libpq</>'s internal function to allocate and
-        initialize an empty <structname>PGresult</structname> object.  This
-        function returns NULL if memory could not be allocated. It is
-        exported because some applications find it useful to generate result
-        objects (particularly objects with error status) themselves.  If
-        <parameter>conn</parameter> is not null and <parameter>status</>
-        indicates an error, the current error message of the specified
-        connection is copied into the <structname>PGresult</structname>.
-        Note that <function>PQclear</function> should eventually be called
-        on the object, just as with a <structname>PGresult</structname>
-        returned by <application>libpq</application> itself.
-       </para>
-      </listitem>
-     </varlistentry>
     </variablelist>
    </para>
   </sect2>
@@ -4598,6 +4566,170 @@ char *pg_encoding_to_char(int <replaceable>encoding_id</replaceable>);
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>PQmakeEmptyPGresult</function>
+     <indexterm>
+      <primary>PQmakeEmptyPGresult</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      Constructs an empty <structname>PGresult</structname> object with the given status.
+      <synopsis>
+       PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status);
+      </synopsis>
+     </para>
+
+     <para>
+      This is <application>libpq</>'s internal function to allocate and
+      initialize an empty <structname>PGresult</structname> object.  This
+      function returns NULL if memory could not be allocated. It is
+      exported because some applications find it useful to generate result
+      objects (particularly objects with error status) themselves.  If
+      <parameter>conn</parameter> is not null and <parameter>status</>
+      indicates an error, the current error message of the specified
+      connection is copied into the <structname>PGresult</structname>.
+      Also, if <parameter>conn</parameter> is not null, any event handlers
+      registered in the connection are copied into the
+      <structname>PGresult</structname> (but they don't get
+      <literal>PGEVT_RESULTCREATE</> calls).
+      Note that <function>PQclear</function> should eventually be called
+      on the object, just as with a <structname>PGresult</structname>
+      returned by <application>libpq</application> itself.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>PQcopyResult</function>
+     <indexterm>
+      <primary>PQcopyResult</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      Makes a copy of a <structname>PGresult</structname> object.  The copy is
+      not linked to the source result in any way and
+      <function>PQclear</function> must be called when the copy is no longer
+      needed.  If the function fails, NULL is returned.
+
+      <synopsis>
+       PGresult *PQcopyResult(const PGresult *src, int flags);
+      </synopsis>
+     </para>
+
+     <para>
+      This is not intended to make an exact copy.  The returned result is
+      always put into <literal>PGRES_TUPLES_OK</literal> status, and does not
+      copy any error message in the source.  (It does copy the command status
+      string, however.)  The <parameter>flags</parameter> argument determines
+      what else is copied.  It is a bitwise OR of several flags.
+      <literal>PG_COPYRES_ATTRS</literal> specifies copying the source
+      result's attributes (column definitions).
+      <literal>PG_COPYRES_TUPLES</literal> specifies copying the source
+      result's tuples.  (This implies copying the attributes, too.)
+      <literal>PG_COPYRES_NOTICEHOOKS</literal> specifies
+      copying the source result's notify hooks.
+      <literal>PG_COPYRES_EVENTS</literal> specifies copying the source
+      result's events.  (But any instance data associated with the source
+      is not copied.)
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>PQsetResultAttrs</function>
+     <indexterm>
+      <primary>PQsetResultAttrs</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      Sets the attributes of a <structname>PGresult</structname> object.
+      <synopsis>
+       int PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs);
+      </synopsis>
+     </para>
+
+     <para>
+      The provided <parameter>attDescs</parameter> are copied into the result.
+      If the <parameter>attDescs</parameter> pointer is NULL or
+      <parameter>numAttributes</parameter> is less than one, the request is
+      ignored and the function succeeds.  If <parameter>res</parameter>
+      already contains attributes, the function will fail.  If the function
+      fails, the return value is zero.  If the function succeeds, the return
+      value is non-zero.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>PQsetvalue</function>
+     <indexterm>
+      <primary>PQsetvalue</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      Sets a tuple field value of a <structname>PGresult</structname> object.
+      <synopsis>
+       int PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len);
+      </synopsis>
+     </para>
+
+     <para>
+      The function will automatically grow the result's internal tuples array
+      as needed.  However, the <parameter>tup_num</parameter> argument must be
+      less than or equal to <function>PQntuples</function>, meaning this
+      function can only grow the tuples array one tuple at a time.  But any
+      field of any existing tuple can be modified in any order.  If a value at
+      <parameter>field_num</parameter> already exists, it will be overwritten.
+      If <parameter>len</parameter> is <literal>-1</literal> or
+      <parameter>value</parameter> is <literal>NULL</literal>, the field value
+      will be set to an SQL <literal>NULL</literal>.  The
+      <parameter>value</parameter> is copied into the result's private storage,
+      thus is no longer needed after the function
+      returns.  If the function fails, the return value is zero.  If the
+      function succeeds, the return value is non-zero.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>PQresultAlloc</function>
+     <indexterm>
+      <primary>PQresultAlloc</primary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      Allocate subsidiary storage for a <structname>PGresult</structname> object.
+      <synopsis>
+       void *PQresultAlloc(PGresult *res, size_t nBytes);
+      </synopsis>
+     </para>
+
+     <para>
+      Any memory allocated with this function will be freed when
+      <parameter>res</parameter> is cleared.  If the function fails,
+      the return value is <literal>NULL</literal>.  The result is
+      guaranteed to be adequately aligned for any type of data,
+      just as for <function>malloc</>.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
 
  </sect1>
@@ -4711,6 +4843,551 @@ defaultNoticeProcessor(void *arg, const char *message)
 
  </sect1>
 
+ <sect1 id="libpq-events">
+  <title>Event System</title>
+
+  <para>
+   <application>libpq</application>'s event system is designed to notify
+   registered event handlers about interesting
+   <application>libpq</application> events, such as the creation or
+   destruction of <structname>PGconn</structname> and
+   <structname>PGresult</structname> objects.  A principal use case is that
+   this allows applications to associate their own data with a
+   <structname>PGconn</structname> or <structname>PGresult</structname>
+   and ensure that that data is freed at an appropriate time.
+  </para>
+
+  <para>
+   Each registered event handler is associated with two pieces of data,
+   known to <application>libpq</application> only as opaque <literal>void *</>
+   pointers.  There is a <firstterm>passthrough</> pointer that is provided
+   by the application when the event handler is registered with a
+   <structname>PGconn</>.  The passthrough pointer never changes for the
+   life of the <structname>PGconn</> and all <structname>PGresult</>s
+   generated from it; so if used, it must point to long-lived data.
+   In addition there is an <firstterm>instance data</> pointer, which starts
+   out NULL in every <structname>PGconn</> and <structname>PGresult</>.
+   This pointer can be manipulated using the
+   <function>PQinstanceData</function>,
+   <function>PQsetInstanceData</function>,
+   <function>PQresultInstanceData</function> and
+   <function>PQsetResultInstanceData</function> functions.  Note that
+   unlike the passthrough pointer, instance data of a <structname>PGconn</>
+   is not automatically inherited by <structname>PGresult</>s created from
+   it.  <application>libpq</application> does not know what passthrough
+   and instance data pointers point to (if anything) and will never attempt
+   to free them &mdash; that is the responsibility of the event handler.
+  </para>
+
+  <sect2 id="libpq-events-types">
+   <title>Event Types</title>
+
+   <para>
+    The enum <literal>PGEventId</> names the types of events handled by
+    the event system.  All its values have names beginning with
+    <literal>PGEVT</literal>.  For each event type, there is a corresponding
+    event info structure that carries the parameters passed to the event
+    handlers.  The event types are:
+   </para>
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>PGEVT_REGISTER</literal></term>
+     <listitem>
+      <para>
+       The register event occurs when <function>PQregisterEventProc</function>
+       is called.  It is the ideal time to initialize any
+       <literal>instanceData</literal> an event procedure may need.  Only one
+       register event will be fired per event handler per connection.  If the
+       event procedure fails, the registration is aborted.
+
+      <synopsis>
+typedef struct
+{
+    const PGconn *conn;
+} PGEventRegister;
+      </synopsis>
+
+       When a <literal>PGEVT_REGISTER</literal> event is received, the
+       <parameter>evtInfo</parameter> pointer should be cast to a
+       <structname>PGEventRegister *</structname>.  This structure contains a
+       <structname>PGconn</structname> that should be in the
+       <literal>CONNECTION_OK</literal> status; guaranteed if one calls
+       <function>PQregisterEventProc</function> right after obtaining a good
+       <structname>PGconn</structname>.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>PGEVT_CONNRESET</literal></term>
+     <listitem>
+      <para>
+       The connection reset event is fired on completion of
+       <function>PQreset</function> or <function>PQresetPoll</function>.  In
+       both cases, the event is only fired if the reset was successful.  If
+       the event procedure fails, the entire connection reset will fail; the
+       <structname>PGconn</structname> is put into
+       <literal>CONNECTION_BAD</literal> status and
+       <function>PQresetPoll</function> will return
+       <literal>PGRES_POLLING_FAILED</literal>.
+
+      <synopsis>
+typedef struct
+{
+    const PGconn *conn;
+} PGEventConnReset;
+      </synopsis>
+
+       When a <literal>PGEVT_CONNRESET</literal> event is received, the
+       <parameter>evtInfo</parameter> pointer should be cast to a
+       <structname>PGEventConnReset *</structname>.  Although the contained
+       <structname>PGconn</structname> was just reset, all event data remains
+       unchanged.  This event should be used to reset/reload/requery any
+       associated <literal>instanceData</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>PGEVT_CONNDESTROY</literal></term>
+     <listitem>
+      <para>
+       The connection destroy event is fired in response to
+       <function>PQfinish</function>.  It is the event procedure's
+       responsibility to properly clean up its event data as libpq has no
+       ability to manage this memory.  Failure to clean up will lead
+       to memory leaks.
+
+      <synopsis>
+typedef struct
+{
+    const PGconn *conn;
+} PGEventConnDestroy;
+      </synopsis>
+
+       When a <literal>PGEVT_CONNDESTROY</literal> event is received, the
+       <parameter>evtInfo</parameter> pointer should be cast to a
+       <structname>PGEventConnDestroy *</structname>.  This event is fired
+       prior to <function>PQfinish</function> performing any other cleanup.
+       The return value of the event procedure is ignored since there is no
+       way of indicating a failure from <function>PQfinish</function>.  Also,
+       an event procedure failure should not abort the process of cleaning up
+       unwanted memory.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>PGEVT_RESULTCREATE</literal></term>
+     <listitem>
+      <para>
+       The result creation event is fired in response to any query execution
+       function that generates a result, including
+       <function>PQgetResult</function>.  This event will only be fired after
+       the result has been created successfully.
+
+      <synopsis>
+typedef struct
+{
+    const PGconn *conn;
+    PGresult *result;
+} PGEventResultCreate;
+      </synopsis>
+
+       When a <literal>PGEVT_RESULTCREATE</literal> event is received, the
+       <parameter>evtInfo</parameter> pointer should be cast to a
+       <structname>PGEventResultCreate *</structname>.  The
+       <parameter>conn</parameter> is the connection used to generate the
+       result.  This is the ideal place to initialize any
+       <literal>instanceData</literal> that needs to be associated with the
+       result.  If the event procedure fails, the result will be cleared and
+       the failure will be propagated.  The event procedure must not try to
+       <function>PQclear</> the result object for itself.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>PGEVT_RESULTCOPY</literal></term>
+     <listitem>
+      <para>
+       The result copy event is fired in response to
+       <function>PQcopyResult</function>.  This event will only be fired after
+       the copy is complete.
+
+      <synopsis>
+typedef struct
+{
+    const PGresult *src;
+    PGresult *dest;
+} PGEventResultCopy;
+      </synopsis>
+
+       When a <literal>PGEVT_RESULTCOPY</literal> event is received, the
+       <parameter>evtInfo</parameter> pointer should be cast to a
+       <structname>PGEventResultCopy *</structname>.  The
+       <parameter>src</parameter> result is what was copied while the
+       <parameter>dest</parameter> result is the copy destination.  This event
+       can be used to provide a deep copy of <literal>instanceData</literal>,
+       since <literal>PQcopyResult</literal> cannot do that.  If the event
+       procedure fails, the entire copy operation will fail and the
+       <parameter>dest</parameter> result will be cleared.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>PGEVT_RESULTDESTROY</literal></term>
+     <listitem>
+      <para>
+       The result destroy event is fired in response to a
+       <function>PQclear</function>.  It is the event procedure's
+       responsibility to properly clean up its event data as libpq has no
+       ability to manage this memory.  Failure to clean up will lead
+       to memory leaks.
+
+      <synopsis>
+typedef struct
+{
+    const PGresult *result;
+} PGEventResultDestroy;
+      </synopsis>
+
+       When a <literal>PGEVT_RESULTDESTROY</literal> event is received, the
+       <parameter>evtInfo</parameter> pointer should be cast to a
+       <structname>PGEventResultDestroy *</structname>.  This event is fired
+       prior to <function>PQclear</function> performing any other cleanup.
+       The return value of the event procedure is ignored since there is no
+       way of indicating a failure from <function>PQclear</function>.  Also,
+       an event procedure failure should not abort the process of cleaning up
+       unwanted memory.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </sect2>
+
+  <sect2 id="libpq-events-proc">
+   <title>Event Callback Procedure</title>
+
+   <variablelist>
+    <varlistentry>
+     <term>
+      <literal>PGEventProc</literal>
+      <indexterm>
+       <primary>PGEventProc</primary>
+      </indexterm>
+     </term>
+
+     <listitem>
+      <para>
+       <literal>PGEventProc</literal> is a typedef for a pointer to an
+       event procedure, that is, the user callback function that receives
+       events from libpq.  The signature of an event procedure must be
+
+      <synopsis>
+int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)
+      </synopsis>
+
+       The <parameter>evtId</parameter> parameter indicates which
+       <literal>PGEVT</literal> event occurred.  The
+       <parameter>evtInfo</parameter> pointer must be cast to the appropriate
+       structure type to obtain further information about the event.
+       The <parameter>passThrough</parameter> parameter is the pointer
+       provided to <function>PQregisterEventProc</function> when the event
+       procedure was registered.  The function should return a non-zero value
+       if it succeeds and zero if it fails.
+      </para>
+
+      <para>
+       A particular event procedure can be registered only once in any
+       <structname>PGconn</>.  This is because the address of the procedure
+       is used as a lookup key to identify the associated instance data.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </sect2>
+
+  <sect2 id="libpq-events-funcs">
+   <title>Event Support Functions</title>
+
+    <variablelist>
+    <varlistentry>
+     <term>
+      <function>PQregisterEventProc</function>
+      <indexterm>
+       <primary>PQregisterEventProc</primary>
+      </indexterm>
+     </term>
+
+     <listitem>
+      <para>
+       Registers an event callback procedure with libpq.
+
+       <synopsis>
+        int PQregisterEventProc(PGconn *conn, PGEventProc proc,
+                                const char *name, void *passThrough);
+       </synopsis>
+      </para>
+
+      <para>
+       An event procedure must be registered once on each
+       <structname>PGconn</> you want to receive events about.  There is no
+       limit, other than memory, on the number of event procedures that
+       can be registered with a connection.  The function returns a non-zero
+       value if it succeeds and zero if it fails.
+      </para>
+
+      <para>
+       The <parameter>proc</parameter> argument will be called when a libpq
+       event is fired.  Its memory address is also used to lookup
+       <literal>instanceData</literal>.  The <parameter>name</parameter>
+       argument is used to refer to the event procedure in error messages.
+       This value cannot be NULL or a zero-length string.  The name string is
+       copied into the <structname>PGconn</>, so what is passed need not be
+       long-lived.  The <parameter>passThrough</parameter> pointer is passed
+       to the <parameter>proc</parameter> whenever an event occurs. This
+       argument can be NULL.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term>
+      <function>PQsetInstanceData</function>
+      <indexterm>
+       <primary>PQsetInstanceData</primary>
+      </indexterm>
+     </term>
+     <listitem>
+      <para>
+       Sets the conn's instanceData for proc to data.  This returns non-zero
+       for success and zero for failure.  (Failure is only possible if
+       the proc has not been properly registered in the conn.)
+
+       <synopsis>
+        int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data);
+       </synopsis>
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term>
+      <function>PQinstanceData</function>
+      <indexterm>
+       <primary>PQinstanceData</primary>
+      </indexterm>
+     </term>
+     <listitem>
+      <para>
+       Returns the conn's instanceData associated with proc, or NULL
+       if there is none.
+
+       <synopsis>
+        void *PQinstanceData(const PGconn *conn, PGEventProc proc);
+       </synopsis>
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term>
+      <function>PQresultSetInstanceData</function>
+      <indexterm>
+       <primary>PQresultSetInstanceData</primary>
+      </indexterm>
+     </term>
+     <listitem>
+      <para>
+       Sets the result's instanceData for proc to data.  This returns non-zero
+       for success and zero for failure.  (Failure is only possible if the
+       proc has not been properly registered in the result.)
+
+       <synopsis>
+        int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);
+       </synopsis>
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term>
+      <function>PQresultInstanceData</function>
+      <indexterm>
+       <primary>PQresultInstanceData</primary>
+      </indexterm>
+     </term>
+     <listitem>
+      <para>
+       Returns the result's instanceData associated with proc, or NULL
+       if there is none.
+
+       <synopsis>
+        void *PQresultInstanceData(const PGresult *res, PGEventProc proc);
+       </synopsis>
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </sect2>
+
+  <sect2 id="libpq-events-example">
+   <title>Event Example</title>
+
+   <para>
+    Here is a skeleton example of managing private data associated with
+    libpq connections and results.
+   </para>
+
+   <programlisting>
+/* required header for libpq events (note: includes libpq-fe.h) */
+#include &lt;libpq-events.h&gt;
+
+/* The instanceData */
+typedef struct
+{
+    int n;
+    char *str;
+} mydata;
+
+/* PGEventProc */
+static int myEventProc(PGEventId evtId, void *evtInfo, void *passThrough);
+
+int
+main(void)
+{
+    mydata *data;
+    PGresult *res;
+    PGconn *conn = PQconnectdb("dbname = postgres");
+
+    if (PQstatus(conn) != CONNECTION_OK)
+    {
+        fprintf(stderr, "Connection to database failed: %s",
+                PQerrorMessage(conn));
+        PQfinish(conn);
+        return 1;
+    }
+
+    /* called once on any connection that should receive events.
+     * Sends a PGEVT_REGISTER to myEventProc.
+     */
+    if (!PQregisterEventProc(conn, myEventProc, "mydata_proc", NULL))
+    {
+        fprintf(stderr, "Cannot register PGEventProc\n");
+        PQfinish(conn);
+        return 1;
+    }
+
+    /* conn instanceData is available */
+    data = PQinstanceData(conn, myEventProc);
+
+    /* Sends a PGEVT_RESULTCREATE to myEventProc */
+    res = PQexec(conn, "SELECT 1 + 1");
+
+    /* result instanceData is available */
+    data = PQresultInstanceData(res, myEventProc);
+
+    /* If PG_COPYRES_EVENTS is used, sends a PGEVT_RESULTCOPY to myEventProc */
+    res_copy = PQcopyResult(res, PG_COPYRES_TUPLES | PG_COPYRES_EVENTS);
+
+    /* result instanceData is available if PG_COPYRES_EVENTS was
+     * used during the PQcopyResult call.
+     */
+    data = PQresultInstanceData(res_copy, myEventProc);
+
+    /* Both clears send a PGEVT_RESULTDESTROY to myEventProc */
+    PQclear(res);
+    PQclear(res_copy);
+
+    /* Sends a PGEVT_CONNDESTROY to myEventProc */
+    PQfinish(conn);
+
+    return 0;
+}
+
+static int
+myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
+{
+    switch (evtId)
+    {
+        case PGEVT_REGISTER:
+        {
+            PGEventRegister *e = (PGEventRegister *)evtInfo;
+            mydata *data = get_mydata(e-&gt;conn);
+
+            /* associate app specific data with connection */
+            PQsetInstanceData(e-&gt;conn, myEventProc, data);
+            break;
+        }
+
+        case PGEVT_CONNRESET:
+        {
+            PGEventConnReset *e = (PGEventConnReset *)evtInfo;
+            mydata *data = PQinstanceData(e-&gt;conn, myEventProc);
+
+            if (data)
+              memset(data, 0, sizeof(mydata));
+            break;
+        }
+
+        case PGEVT_CONNDESTROY:
+        {
+            PGEventConnDestroy *e = (PGEventConnDestroy *)evtInfo;
+            mydata *data = PQinstanceData(e-&gt;conn, myEventProc);
+
+            /* free instance data because the conn is being destroyed */
+            if (data)
+              free_mydata(data);
+            break;
+        }
+
+        case PGEVT_RESULTCREATE:
+        {
+            PGEventResultCreate *e = (PGEventResultCreate *)evtInfo;
+            mydata *conn_data = PQinstanceData(e-&gt;conn, myEventProc);
+            mydata *res_data = dup_mydata(conn_data);
+
+            /* associate app specific data with result (copy it from conn) */
+            PQsetResultInstanceData(e-&gt;result, myEventProc, res_data);
+            break;
+        }
+
+        case PGEVT_RESULTCOPY:
+        {
+            PGEventResultCopy *e = (PGEventResultCopy *)evtInfo;
+            mydata *src_data = PQresultInstanceData(e-&gt;src, myEventProc);
+            mydata *dest_data = dup_mydata(src_data);
+
+            /* associate app specific data with result (copy it from a result) */
+            PQsetResultInstanceData(e-&gt;dest, myEventProc, dest_data);
+            break;
+        }
+
+        case PGEVT_RESULTDESTROY:
+        {
+            PGEventResultDestroy *e = (PGEventResultDestroy *)evtInfo;
+            mydata *data = PQresultInstanceData(e-&gt;result, myEventProc);
+
+            /* free instance data because the result is being destroyed */
+            if (data)
+              free_mydata(data);
+            break;
+        }
+
+        /* unknown event id, just return TRUE. */
+        default:
+            break;
+    }
+
+    return TRUE; /* event processing succeeded */
+}
+</programlisting>
+  </sect2>
+ </sect1>
+
  <sect1 id="libpq-envars">
   <title>Environment Variables</title>
 
@@ -5263,7 +5940,7 @@ defaultNoticeProcessor(void *arg, const char *message)
    to inside <application>libpq</application>), you can use
    <function>PQinitSSL(int)</> to tell <application>libpq</application>
    that the <acronym>SSL</> library has already been initialized by your
-   application.  
+   application.
    <!-- If this URL changes replace it with a URL to www.archive.org. -->
    See <ulink
    url="http://h71000.www7.hp.com/doc/83final/BA554_90007/ch04.html"></ulink>
index 62885fbb5e01ed6ede699355720c32b001208949..7bace86345d2bf07f451c937caf3f2a37a545190 100644 (file)
@@ -5,7 +5,7 @@
 # Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
 # Portions Copyright (c) 1994, Regents of the University of California
 #
-# $PostgreSQL: pgsql/src/interfaces/libpq/Makefile,v 1.166 2008/04/16 14:19:56 adunstan Exp $
+# $PostgreSQL: pgsql/src/interfaces/libpq/Makefile,v 1.167 2008/09/17 04:31:08 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -32,6 +32,7 @@ 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 \
+       libpq-events.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))
 
@@ -106,6 +107,7 @@ $(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'
@@ -114,7 +116,11 @@ 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'
+       rm -f '$(DESTDIR)$(includedir)/libpq-fe.h'
+       rm -f '$(DESTDIR)$(includedir)/libpq-events.h'
+       rm -f '$(DESTDIR)$(includedir_internal)/libpq-int.h'
+       rm -f '$(DESTDIR)$(includedir_internal)/pqexpbuffer.h'
+       rm -f '$(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.c strlcpy.c thread.c md5.c ip.c encnames.c wchar.c win32error.c pgsleep.c pthread.h libpq.rc
index f8809f841e4ad1a866380d692b11fbe5347f5712..c720efce4b97994cd28679531b46538228baf6f0 100644 (file)
@@ -1,4 +1,4 @@
-# $PostgreSQL: pgsql/src/interfaces/libpq/exports.txt,v 1.19 2008/03/19 00:39:33 ishii Exp $
+# $PostgreSQL: pgsql/src/interfaces/libpq/exports.txt,v 1.20 2008/09/17 04:31:08 tgl Exp $
 # Functions to be exported by libpq DLLs
 PQconnectdb               1
 PQsetdbLogin              2
@@ -140,4 +140,13 @@ lo_truncate               137
 PQconnectionUsedPassword  138
 pg_valid_server_encoding_id 139
 PQconnectionNeedsPassword 140
-lo_import_with_oid               141
+lo_import_with_oid        141
+PQcopyResult              142
+PQsetResultAttrs          143
+PQsetvalue                144
+PQresultAlloc             145
+PQregisterEventProc       146
+PQinstanceData            147
+PQsetInstanceData         148
+PQresultInstanceData      149
+PQresultSetInstanceData   150
index 5e687c15585fca08c01ab7452503aab500f44ad6..7e77c9a5c7ac62184900a37f179f7ce84a52fd0d 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.359 2008/05/29 22:02:44 tgl Exp $
+ *       $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.360 2008/09/17 04:31:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1974,6 +1974,21 @@ makeEmptyPGconn(void)
 static void
 freePGconn(PGconn *conn)
 {
+       int                     i;
+
+       /* let any event procs clean up their state data */
+       for (i = 0; i < conn->nEvents; i++)
+       {
+               PGEventConnDestroy evt;
+
+               evt.conn = conn;
+               (void) conn->events[i].proc(PGEVT_CONNDESTROY, &evt,
+                                                                       conn->events[i].passThrough);
+               free(conn->events[i].name);
+       }
+
+       if (conn->events)
+               free(conn->events);
        if (conn->pghost)
                free(conn->pghost);
        if (conn->pghostaddr)
@@ -2155,8 +2170,30 @@ PQreset(PGconn *conn)
        {
                closePGconn(conn);
 
-               if (connectDBStart(conn))
-                       (void) connectDBComplete(conn);
+               if (connectDBStart(conn) && connectDBComplete(conn))
+               {
+                       /*
+                        * Notify event procs of successful reset.  We treat an event
+                        * proc failure as disabling the connection ... good idea?
+                        */
+                       int i;
+
+                       for (i = 0; i < conn->nEvents; i++)
+                       {
+                               PGEventConnReset evt;
+
+                               evt.conn = conn;
+                               if (!conn->events[i].proc(PGEVT_CONNRESET, &evt,
+                                                                                 conn->events[i].passThrough))
+                               {
+                                       conn->status = CONNECTION_BAD;
+                                       printfPQExpBuffer(&conn->errorMessage,
+                                                                         libpq_gettext("PGEventProc \"%s\" failed during PGEVT_CONNRESET event\n"),
+                                                                         conn->events[i].name);
+                                       break;
+                               }
+                       }
+               }
        }
 }
 
@@ -2190,7 +2227,36 @@ PostgresPollingStatusType
 PQresetPoll(PGconn *conn)
 {
        if (conn)
-               return PQconnectPoll(conn);
+       {
+               PostgresPollingStatusType status = PQconnectPoll(conn);
+
+               if (status == PGRES_POLLING_OK)
+               {
+                       /*
+                        * Notify event procs of successful reset.  We treat an event
+                        * proc failure as disabling the connection ... good idea?
+                        */
+                       int i;
+
+                       for (i = 0; i < conn->nEvents; i++)
+                       {
+                               PGEventConnReset evt;
+
+                               evt.conn = conn;
+                               if (!conn->events[i].proc(PGEVT_CONNRESET, &evt,
+                                                                                 conn->events[i].passThrough))
+                               {
+                                       conn->status = CONNECTION_BAD;
+                                       printfPQExpBuffer(&conn->errorMessage,
+                                                                         libpq_gettext("PGEventProc \"%s\" failed during PGEVT_CONNRESET event\n"),
+                                                                         conn->events[i].name);
+                                       return PGRES_POLLING_FAILED;
+                               }
+                       }
+               }
+
+               return status;
+       }
 
        return PGRES_POLLING_FAILED;
 }
index 149a0b73f6bb4efa4b8a0a696b0cab2b0ece94bd..7db303ce0085d00dfa0d8fb869403059e9307b08 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/interfaces/libpq/fe-exec.c,v 1.197 2008/09/10 17:01:07 tgl Exp $
+ *       $PostgreSQL: pgsql/src/interfaces/libpq/fe-exec.c,v 1.198 2008/09/17 04:31:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -48,6 +48,7 @@ static int    static_client_encoding = PG_SQL_ASCII;
 static bool static_std_strings = false;
 
 
+static PGEvent *dupEvents(PGEvent *events, int count);
 static bool PQsendQueryStart(PGconn *conn);
 static int PQsendQueryGuts(PGconn *conn,
                                const char *command,
@@ -63,6 +64,7 @@ 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);
 
 
 /* ----------------
@@ -128,13 +130,8 @@ static int PQsendDescribe(PGconn *conn, char desc_type,
  * 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.
+ *      errorMessage is copied.  Also, any PGEvents are copied from the conn.
  */
-
 PGresult *
 PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
 {
@@ -154,6 +151,8 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
        result->resultStatus = status;
        result->cmdStatus[0] = '\0';
        result->binary = 0;
+       result->events = NULL;
+       result->nEvents = 0;
        result->errMsg = NULL;
        result->errFields = NULL;
        result->null_field[0] = '\0';
@@ -181,6 +180,18 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
                                pqSetResultError(result, conn->errorMessage.data);
                                break;
                }
+
+               /* copy events last; result must be valid if we need to PQclear */
+               if (conn->nEvents > 0)
+               {
+                       result->events = dupEvents(conn->events, conn->nEvents);
+                       if (!result->events)
+                       {
+                               PQclear(result);
+                               return NULL;
+                       }
+                       result->nEvents = conn->nEvents;
+               }
        }
        else
        {
@@ -195,6 +206,301 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
        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 no-op 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));
+
+       /* deep-copy the attribute names, and determine format */
+       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;
+
+               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 'flags' 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, use 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, seeeing 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 flags)
+{
+       PGresult *dest;
+       int i;
+
+       if (!src)
+               return NULL;
+
+       dest = PQmakeEmptyPGresult(NULL, PGRES_TUPLES_OK);
+       if (!dest)
+               return NULL;
+
+       /* Always copy these over.  Is cmdStatus really useful here? */
+       dest->client_encoding = src->client_encoding;
+       strcpy(dest->cmdStatus, src->cmdStatus);
+
+       /* Wants attrs? */
+       if (flags & (PG_COPYRES_ATTRS | PG_COPYRES_TUPLES))
+       {
+               if (!PQsetResultAttrs(dest, src->numAttributes, src->attDescs))
+               {
+                       PQclear(dest);
+                       return NULL;
+               }
+       }
+
+       /* Wants to copy tuples? */
+       if (flags & PG_COPYRES_TUPLES)
+       {
+               int tup, field;
+
+               for (tup = 0; tup < src->ntups; tup++)
+               {
+                       for (field = 0; field < src->numAttributes; field++)
+                       {
+                               if (!PQsetvalue(dest, tup, field,
+                                                               src->tuples[tup][field].value,
+                                                               src->tuples[tup][field].len))
+                               {
+                                       PQclear(dest);
+                                       return NULL;
+                               }
+                       }
+               }
+       }
+
+       /* Wants to copy notice hooks? */
+       if (flags & PG_COPYRES_NOTICEHOOKS)
+               dest->noticeHooks = src->noticeHooks;
+
+       /*
+        * Wants to copy PGEvents?  NB: this should be last, as we don't want
+        * to trigger RESULTDESTROY events on a useless PGresult.
+        */
+       if ((flags & PG_COPYRES_EVENTS) && src->nEvents > 0)
+       {
+               dest->events = dupEvents(src->events, src->nEvents);
+               if (!dest->events)
+               {
+                       PQclear(dest);
+                       return NULL;
+               }
+               dest->nEvents = src->nEvents;
+       }
+
+       /* Okay, trigger PGEVT_RESULTCOPY event */
+       for (i = 0; i < dest->nEvents; i++)
+       {
+               PGEventResultCopy evt;
+
+               evt.src = src;
+               evt.dest = dest;
+               if (!dest->events[i].proc(PGEVT_RESULTCOPY, &evt,
+                                                                 dest->events[i].passThrough))
+               {
+                       PQclear(dest);
+                       return NULL;
+               }
+       }
+
+       return dest;
+}
+
+/*
+ * Copy an array of PGEvents (with no extra space for more)
+ * Does not duplicate the event instance data, sets this to NULL
+ */
+static PGEvent *
+dupEvents(PGEvent *events, int count)
+{
+       PGEvent *newEvents;
+       int i;
+
+       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 pointers and deep copy names */
+       for (i = 0; i < count; i++)
+       {
+               newEvents[i].data = NULL;
+               newEvents[i].name = strdup(newEvents[i].name);
+               if (!newEvents[i].name)
+               {
+                       while (--i >= 0)
+                               free(newEvents[i].name);
+                       free(newEvents);
+                       return NULL;
+               }
+       }
+
+       return newEvents;
+}
+
+
+/*
+ * Sets the value for a tuple field.  The tup_num must be less than or
+ * equal to PQntuples(res).  If it is equal, a new tuple is created and
+ * added to the result.
+ * Returns a non-zero value for success and zero for failure.
+ */
+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 < 0 || 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])
+       {
+               PGresAttValue *tup;
+               int i;
+
+               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];
+
+       /* treat either NULL_LEN or NULL value pointer as a NULL field */
+       if (len == NULL_LEN || value == NULL)
+       {
+               attval->len = NULL_LEN;
+               attval->value = res->null_field;
+       }
+       else if (len <= 0)
+       {
+               attval->len = 0;
+               attval->value = res->null_field;
+       }
+       else
+       {
+               attval->value = (char *) pqResultAlloc(res, len + 1, TRUE);
+               if (!attval->value)
+                       return FALSE;
+               attval->len = len;
+               memcpy(attval->value, value, len);
+               attval->value[len] = '\0';
+       }
+
+       return TRUE;
+}
+
+/*
+ * pqResultAlloc - exported routine to allocate local storage in a PGresult.
+ *
+ * We force all such allocations to be maxaligned, since we don't know
+ * whether the value might be binary.
+ */
+void *
+PQresultAlloc(PGresult *res, size_t nBytes)
+{
+       return pqResultAlloc(res, nBytes, TRUE);
+}
+
 /*
  * pqResultAlloc -
  *             Allocate subsidiary storage for a PGresult.
@@ -353,10 +659,24 @@ void
 PQclear(PGresult *res)
 {
        PGresult_data *block;
+       int i;
 
        if (!res)
                return;
 
+       for (i = 0; i < res->nEvents; i++)
+       {
+               PGEventResultDestroy evt;
+
+               evt.result = res;
+               (void) res->events[i].proc(PGEVT_RESULTDESTROY, &evt,
+                                                                  res->events[i].passThrough);
+               free(res->events[i].name);
+       }
+
+       if (res->events)
+               free(res->events);
+
        /* Free all the subsidiary blocks */
        while ((block = res->curBlock) != NULL)
        {
@@ -373,6 +693,8 @@ PQclear(PGresult *res)
        res->tuples = NULL;
        res->paramDescs = NULL;
        res->errFields = NULL;
+       res->events = NULL;
+       res->nEvents = 0;
        /* res->curBlock was zeroed out earlier */
 
        /* Free the PGresult structure itself */
@@ -1270,6 +1592,29 @@ PQgetResult(PGconn *conn)
                        break;
        }
 
+       if (res)
+       {
+               int i;
+
+               for (i = 0; i < res->nEvents; i++)
+               {
+                       PGEventResultCreate evt;
+
+                       evt.conn = conn;
+                       evt.result = res;
+                       if (!res->events[i].proc(PGEVT_RESULTCREATE, &evt,
+                                                                        res->events[i].passThrough))
+                       {
+                               printfPQExpBuffer(&conn->errorMessage,
+                                                                 libpq_gettext("PGEventProc \"%s\" failed during PGEVT_RESULTCREATE event\n"),
+                                                                 res->events[i].name);
+                               pqSetResultError(res, conn->errorMessage.data);
+                               res->resultStatus = PGRES_FATAL_ERROR;
+                               break;
+                       }
+               }
+       }
+
        return res;
 }
 
diff --git a/src/interfaces/libpq/libpq-events.c b/src/interfaces/libpq/libpq-events.c
new file mode 100644 (file)
index 0000000..7d3d1cb
--- /dev/null
@@ -0,0 +1,176 @@
+/*-------------------------------------------------------------------------
+ *
+ * libpq-events.c
+ *       functions for supporting the libpq "events" API
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       $PostgreSQL: pgsql/src/interfaces/libpq/libpq-events.c,v 1.1 2008/09/17 04:31:08 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+
+
+/*
+ * Registers an event proc with the given PGconn.
+ *
+ * The same proc can't be registered more than once in a PGconn.  This
+ * restriction is required because we use the proc address to identify
+ * the event for purposes such as PQinstanceData().
+ *
+ * The name argument is used within error messages to aid in debugging.
+ * A name must be supplied, but it needn't be unique.  The string is
+ * copied, so the passed value needn't be long-lived.
+ *
+ * The passThrough argument is an application specific pointer and can be set
+ * to NULL if not required.  It is passed through to the event proc whenever
+ * the event proc is called, and is not otherwise touched by libpq.
+ *
+ * The function returns a non-zero if successful.  If the function fails,
+ * zero is returned.
+ */
+int
+PQregisterEventProc(PGconn *conn, PGEventProc proc,
+                                       const char *name, void *passThrough)
+{
+       int i;
+       PGEventRegister regevt;
+
+       if (!proc || !conn || !name || !*name)
+               return FALSE;                   /* bad arguments */
+
+       for (i = 0; i < conn->nEvents; i++)
+       {
+               if (conn->events[i].proc == proc)
+                       return FALSE;           /* already registered */
+       }
+
+       if (conn->nEvents >= conn->eventArraySize)
+       {
+               PGEvent *e;
+               int newSize;
+
+               newSize = conn->eventArraySize ? conn->eventArraySize * 2 : 8;
+               if (conn->events)
+                       e = (PGEvent *) realloc(conn->events, newSize * sizeof(PGEvent));
+               else
+                       e = (PGEvent *) malloc(newSize * sizeof(PGEvent));
+
+               if (!e)
+                       return FALSE;
+
+               conn->eventArraySize = newSize;
+               conn->events = e;
+       }
+
+       conn->events[conn->nEvents].proc = proc;
+       conn->events[conn->nEvents].name = strdup(name);
+       if (!conn->events[conn->nEvents].name)
+               return FALSE;
+       conn->events[conn->nEvents].passThrough = passThrough;
+       conn->events[conn->nEvents].data = NULL;
+       conn->nEvents++;
+
+       regevt.conn = conn;
+       if (!proc(PGEVT_REGISTER, &regevt, passThrough))
+       {
+               conn->nEvents--;
+               free(conn->events[conn->nEvents].name);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+/*
+ * Set some "instance data" for an event within a PGconn.
+ * Returns nonzero on success, zero on failure.
+ */
+int
+PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data)
+{
+       int i;
+
+       if (!conn || !proc)
+               return FALSE;
+
+       for (i = 0; i < conn->nEvents; i++)
+       {
+               if (conn->events[i].proc == proc)
+               {
+                       conn->events[i].data = data;
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+/*
+ * Obtain the "instance data", if any, for the event.
+ */
+void *
+PQinstanceData(const PGconn *conn, PGEventProc proc)
+{
+       int i;
+
+       if (!conn || !proc)
+               return NULL;
+
+       for (i = 0; i < conn->nEvents; i++)
+       {
+               if (conn->events[i].proc == proc)
+                       return conn->events[i].data;
+       }
+
+       return NULL;
+}
+
+/*
+ * Set some "instance data" for an event within a PGresult.
+ * Returns nonzero on success, zero on failure.
+ */
+int
+PQresultSetInstanceData(PGresult *result, PGEventProc proc, void *data)
+{
+       int i;
+
+       if (!result || !proc)
+               return FALSE;
+
+       for (i = 0; i < result->nEvents; i++)
+       {
+               if (result->events[i].proc == proc)
+               {
+                       result->events[i].data = data;
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+/*
+ * Obtain the "instance data", if any, for the event.
+ */
+void *
+PQresultInstanceData(const PGresult *result, PGEventProc proc)
+{
+       int i;
+
+       if (!result || !proc)
+               return NULL;
+
+       for (i = 0; i < result->nEvents; i++)
+               if (result->events[i].proc == proc)
+                       return result->events[i].data;
+
+       return NULL;
+}
diff --git a/src/interfaces/libpq/libpq-events.h b/src/interfaces/libpq/libpq-events.h
new file mode 100644 (file)
index 0000000..33e2d5b
--- /dev/null
@@ -0,0 +1,91 @@
+/*-------------------------------------------------------------------------
+ *
+ * libpq-events.h
+ *       This file contains definitions that are useful to applications
+ *       that invoke the libpq "events" API, but are not interesting to
+ *       ordinary users of libpq.
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-events.h,v 1.1 2008/09/17 04:31:08 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef LIBPQ_EVENTS_H
+#define LIBPQ_EVENTS_H
+
+#include "libpq-fe.h"
+
+#ifdef __cplusplus
+extern         "C"
+{
+#endif
+
+/* Callback Event Ids */
+typedef enum
+{
+       PGEVT_REGISTER,
+       PGEVT_CONNRESET,
+       PGEVT_CONNDESTROY,
+       PGEVT_RESULTCREATE,
+       PGEVT_RESULTCOPY,
+       PGEVT_RESULTDESTROY
+} PGEventId;
+
+typedef struct
+{
+       const PGconn *conn;
+} PGEventRegister;
+
+typedef struct
+{
+       const PGconn *conn;
+} PGEventConnReset;
+
+typedef struct
+{
+       const PGconn *conn;
+} PGEventConnDestroy;
+
+typedef struct
+{
+       const PGconn *conn;
+       PGresult *result;
+} PGEventResultCreate;
+
+typedef struct
+{
+       const PGresult *src;
+       PGresult *dest;
+} PGEventResultCopy;
+
+typedef struct
+{
+       const PGresult *result;
+} PGEventResultDestroy;
+
+typedef int (*PGEventProc) (PGEventId evtId, void *evtInfo, void *passThrough);
+
+/* Registers an event proc with the given PGconn. */
+extern int     PQregisterEventProc(PGconn *conn, PGEventProc proc,
+                                                               const char *name, void *passThrough);
+
+/* Sets the PGconn instance data for the provided proc to data. */
+extern int     PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data);
+
+/* Gets the PGconn instance data for the provided proc. */
+extern void *PQinstanceData(const PGconn *conn, PGEventProc proc);
+
+/* Sets the PGresult instance data for the provided proc to data. */
+extern int     PQresultSetInstanceData(PGresult *result, PGEventProc proc, void *data);
+
+/* Gets the PGresult instance data for the provided proc. */
+extern void *PQresultInstanceData(const PGresult *result, PGEventProc proc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBPQ_EVENTS_H */
index 53d79b059f6228083cc397436c4d3e732e4c1357..f923b9684085df9846aebc0db34e62336db3c63e 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.142 2008/03/19 00:39:33 ishii Exp $
+ * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.143 2008/09/17 04:31:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -28,6 +28,14 @@ extern               "C"
  */
 #include "postgres_ext.h"
 
+/*
+ * Option flags 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
@@ -192,6 +200,21 @@ typedef struct
        }                       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
  * ----------------
@@ -430,13 +453,12 @@ extern void PQfreemem(void *ptr);
 /* Note: depending on this is deprecated; use PQconnectionNeedsPassword(). */
 #define PQnoPasswordSupplied   "fe_sendauth: no password supplied\n"
 
-/*
- * 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.
- */
+/* Create and manipulate PGresults */
 extern PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status);
-
+extern PGresult *PQcopyResult(const PGresult *src, int flags);
+extern int PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs);
+extern void *PQresultAlloc(PGresult *res, size_t nBytes);
+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,
index fd94952f1802e2f294a50c73801b7f162d9f1d9e..fd29c092148f15010bc206281ba19b7fbbd83269 100644 (file)
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.131 2008/05/29 22:02:44 tgl Exp $
+ * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.132 2008/09/17 04:31:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,6 +22,7 @@
 
 /* We assume libpq-fe.h has already been included. */
 #include "postgres_fe.h"
+#include "libpq-events.h"
 
 #include <time.h>
 #include <sys/types.h>
@@ -100,19 +101,6 @@ union pgresult_data
        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
 {
@@ -162,6 +150,14 @@ typedef struct
        void       *noticeProcArg;
 } PGNoticeHooks;
 
+typedef struct PGEvent
+{
+       PGEventProc     proc;                   /* the function to call on events */
+       char       *name;                       /* used only for error messages */
+       void       *passThrough;        /* pointer supplied at registration time */
+       void       *data;                       /* optional state (instance) data */
+} PGEvent;
+
 struct pg_result
 {
        int                     ntups;
@@ -182,6 +178,8 @@ struct pg_result
         * on the PGresult don't have to reference the PGconn.
         */
        PGNoticeHooks noticeHooks;
+       PGEvent    *events;
+       int                     nEvents;
        int                     client_encoding;        /* encoding id */
 
        /*
@@ -303,6 +301,11 @@ struct pg_conn
        /* Callback procedures for notice message processing */
        PGNoticeHooks noticeHooks;
 
+       /* Event procs registered via PQregisterEventProc */
+       PGEvent    *events;                     /* expandable array of event data */
+       int                     nEvents;                /* number of active events */
+       int                     eventArraySize; /* allocated array size */
+
        /* Status indicators */
        ConnStatusType status;
        PGAsyncStatusType asyncStatus;
index 4355ec9a28b847a3df98ee46bf90aafd1138e73c..dcbe5b5e01b103d997c5edbe2f8a36cd0abaa089 100644 (file)
@@ -3,7 +3,7 @@ package Install;
 #
 # Package that provides 'make install' functionality for msvc builds
 #
-# $PostgreSQL: pgsql/src/tools/msvc/Install.pm,v 1.30 2008/09/05 16:54:39 momjian Exp $
+# $PostgreSQL: pgsql/src/tools/msvc/Install.pm,v 1.31 2008/09/17 04:31:08 tgl Exp $
 #
 use strict;
 use warnings;
@@ -393,7 +393,9 @@ sub CopyIncludeFiles
     lcopy('src/include/libpq/libpq-fs.h', $target . '/include/libpq/')
       || croak 'Could not copy libpq-fs.h';
 
-    CopyFiles('Libpq headers', $target . '/include/', 'src/interfaces/libpq/', 'libpq-fe.h');
+    CopyFiles('Libpq headers',
+             $target . '/include/', 'src/interfaces/libpq/',
+             'libpq-fe.h', 'libpq-events.h');
     CopyFiles(
         'Libpq internal headers',
         $target .'/include/internal/',