Re: [PATCHES] libpq events patch (with sgml docs) - Mailing list pgsql-hackers
From | Andrew Chernow |
---|---|
Subject | Re: [PATCHES] libpq events patch (with sgml docs) |
Date | |
Msg-id | 48C19759.90402@esilo.com Whole thread Raw |
In response to | Re: [PATCHES] libpq events patch (with sgml docs) (Alvaro Herrera <alvherre@commandprompt.com>) |
Responses |
Re: [PATCHES] libpq events patch (with sgml docs)
|
List | pgsql-hackers |
Alvaro Herrera wrote: > > Doh! Sorry, you're right. In that case, you're missing a NULL result > check from strdup() in dupEvents() ;-) > Missed that one. Good catch :) Update attached. -- Andrew Chernow eSilo, LLC every bit counts http://www.esilo.com/ Index: doc/src/sgml/libpq.sgml =================================================================== RCS file: /projects/cvsroot/pgsql/doc/src/sgml/libpq.sgml,v retrieving revision 1.260 diff -C6 -r1.260 libpq.sgml *** doc/src/sgml/libpq.sgml 27 Jun 2008 02:44:31 -0000 1.260 --- doc/src/sgml/libpq.sgml 5 Sep 2008 20:25:29 -0000 *************** *** 2092,2103 **** --- 2092,2222 ---- 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. + <synopsis> + PGresult *PQcopyResult(const PGresult *src, int flags); + </synopsis> + </para> + + <para> + The returned result is always put into <literal>PGRES_TUPLES_OK</literal> status. + It is not linked to the source result in any way and + <function>PQclear</function> must be called when the result is no longer needed. + If the function fails, NULL is returned. + </para> + + <para> + Optionally, the <parameter>flags</parameter> argument can be used to copy + more parts of the result. <literal>PG_COPYRES_ATTRS</literal> will copy the + source result's attributes. <literal>PG_COPYRES_TUPLES</literal> will copy + the source result's tuples. This implies copying the attrs, being how the + attrs are needed by the tuples. <literal>PG_COPYRES_EVENTS</literal> will + copy the source result's events. <literal>PG_COPYRES_NOTICEHOOKS</literal> + will copy the source result's notify hooks. + </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, thus the + <parameter>attDescs</parameter> are no longer needed by <parameter>res</parameter> after + the function returns. If the <parameter>attDescs</parameter> are 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. + 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 in order one tuple at a time. Although, any field from any + existing tuple can be modified in any order. If a value at <parameter>field_num</parameter> + already eixsts, 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 result's private storage, thus no longer needed by <parameter>res</parameter> 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>. + </para> + </listitem> + </varlistentry> </variablelist> </para> </sect2> <sect2 id="libpq-exec-select-info"> <title>Retrieving Query Result Information</title> *************** *** 4558,4569 **** --- 4677,5157 ---- </listitem> </varlistentry> </variablelist> </sect1> + <sect1 id="libpq-events"> + <title>Event System</title> + + <para> + The event system is designed to notify registered event handlers + about particular libpq events; such as the creation or destruction + of <structname>PGconn</structname> and <structname>PGresult</structname> + objects. One use case is that this allows applications to associate + their own data with a <structname>PGconn</structname> and + <structname>PGresult</structname>, that can retrieved via + <function>PQinstanceData</function> and <function>PQresultInstanceData</function>. + Basically, its like adding members to the opaque conn or result + structures at runtime. + </para> + + <para> + Below is a list of event system concepts: + <itemizedlist mark='bullet'> + <listitem> + <para> + <literal>eventId</literal> - Identifies which event was fired by libpq. All + events begin with <literal>PGEVT_</literal>. + </para> + </listitem> + <listitem> + <para> + <literal>eventInfo</literal> - Every <literal>eventId</literal> has a corresponding + event info structure, that contains data to aid in processing the event. + </para> + </listitem> + <listitem> + <para> + <literal>instanceData</literal> - The instance data is memory associated with a + <structname>PGconn</structname> or <structname>PGresult</structname>. The data is + created and destroyed along with with the connection or result. + Typically, instance data is created in response to + a <literal>PGEVT_REGISTER</literal> event, but can be created at any time or + not at all. Management of instance data is done using the <function>PQinstanceData</function>, + <function>PQsetInstanceData</function>, <function>PQresultInstanceData</function> and + <function>PQsetResultInstanceData</function> functions. + </para> + </listitem> + </itemizedlist> + </para> + + + <sect2 id="libpq-events-types"> + <title>Event Types</title> + <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 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 casted 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>. The connection can be used to initialize any + application specific needs: like allocating structures as the event <literal>instanceData</literal> + or executing SQL statements. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>PGEVT_CONNRESET</literal></term> + <listitem> + <para> + The connection reset event is fired in response to a <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; + } PGEventReset;</synopsis> + + When a <literal>PGEVT_CONNRESET</literal> event is received, the <parameter>evtInfo</parameter> + pointer should be casted to a <structname>PGEventReset*</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 assocaited <literal>instanceData</literal>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>PGEVT_CONNDESTROY</literal></term> + <listitem> + <para> + The connection destroy event is fired in response to a <function>PQfinish</function>. + It is the event procedure's responsibility to properly cleanup its event data as libpq + has no ability to manage this memory. Failure to properly cleanup 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 casted to a <structname>PGEventConnDestroy*</structname>. This event is fired + prior to <function>PQfinish</function> performing any 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 create event is fired in response to any exec function that generates a result, + including <function>PQgetResult</function>. This event will only be fired after the result + has been created successfully, with a result status of either <literal>PGRES_COMMAND_OK</literal>, + <literal>PGRES_TUPLES_OK</literal> or <literal>PGRES_EMPTY_QUERY</literal>. + <synopsis> + typedef struct + { + const PGconn *conn; + const PGresult *result; + } PGEventResultCreate;</synopsis> + + When a <literal>PGEVT_RESULTCREATE</literal> event is received, the <parameter>evtInfo</parameter> + pointer should be casted to a <structname>PGEventResultCreate*</structname>. The <parameter>conn</parameter> + was 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. Do not attempt to + clear the result object contained in the <structname>PGEventResultCreate</structname> structure. + </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 casted to a <structname>PGEventResultCopy*</structname>. The <parameter>src</parameter> + result is what is being 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 cleanup its event data as libpq + has no ability to manage this memory. Failure to properly cleanup 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 casted to a <structname>PGEventResultDestroy*</structname>. This event is fired + prior to <function>PQclear</function> performing any 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> + </para> + </sect2> + + <sect2 id="libpq-events-proc"> + <title>Event Callback Procedure</title> + <variablelist> + <varlistentry> + <term> + <function>PGEventProc</function> + <indexterm> + <primary>PGEventProc</primary> + </indexterm> + </term> + + <listitem> + <para> + User callback function that receives events from libpq. The address of the event + proc is also used to lookup the <literal>instanceData</literal> + associated with a <structname>PGconn</structname> or <structname>PGresult</structname>. + <synopsis>typedef int (*PGEventProc)(PGEventId evtId, void *evtInfo, void *passThrough);</synopsis> + </para> + + <para> + The <parameter>evtId</parameter> inidcates which <literal>PGEVT_</literal> event occurred. + The <parameter>evtInfo</parameter> must be casted to the appropriate structure: ex. PGEVT_REGISTER + means cast evtInfo to a PGEventRegister pointer. The <parameter>passThrough</parameter> is the + pointer provided to the <function>PQregisterEventProc</function>, which can be NULL. The function + should return a non-zero value if it succeeds and zero if it fails. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="libpq-events-funcs"> + <title>Event 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> + Registration must be called once on every PGconn you want to receive events about. + There is no limit 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 contruct error messages to aid + in debugging. This value cannot be NULL or a zero-length string. + 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>PQinstanceData</function> + <indexterm> + <primary>PQinstanceData</primary> + </indexterm> + </term> + <listitem> + <para> + Returns the conn's instanceData associated with proc. + <synopsis>void *PQinstanceData(const PGconn *conn, PGEventProc proc);</synopsis> + </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 reutrns non-zero for + succees and zero for failure. + <synopsis>int PQsetInstanceData(PGconn *conn, 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 by proc. + <synopsis>void *PQresultInstanceData(const PGresult *res, 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 reutrns non-zero for + succees and zero for failure. + <synopsis>int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);</synopsis> + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="libpq-events-example"> + <title>Event Example</title> + <programlisting> + /* required header for libpq events (note: includes libpq-fe.h)*/ + #include <libpq-events.h> + + /* The instanceData */ + typedef struct + { + int n; + char *str; + } mydata; + + /* PGEventProc */ + 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_RESULTDESTORY to myEventProc */ + PQclear(res); + PQclear(res_copy); + + /* Sends a PGEVT_CONNDESTROY to myEventProc */ + PQfinish(conn); + + return 0; + } + + int myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) + { + switch (evtId) + { + case PGEVT_REGISTER: + { + PGEventRegister *e = (PGEventRegister *)evtInfo; + mydata *data = get_mydata(e->conn); + + /* associate app specific data with connection */ + PQsetInstanceData(e->conn, myEventProc, data); + break; + } + + case PGEVT_CONNRESET: + { + PGEventConnReset *e = (PGEventConnReset *)evtInfo; + mydata *data = PQinstanceData(e->conn, myEventProc); + if (data) + memset(data, 0, sizeof(mydata)); + break; + } + + case PGEVT_CONNDESTROY: + { + PGEventConnDestroy *e = (PGEventConnDestroy *)evtInfo; + mydata *data = PQinstanceData(e->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->conn, myEventProc); + mydata *res_data = dup_mydata(conn_data); + + /* associate app specific data with result (copy it from conn) */ + PQsetResultInstanceData(e->result, myEventProc, res_data); + break; + } + + case PGEVT_RESULTCOPY: + { + PGEventResultCopy *e = (PGEventResultCopy *)evtInfo; + mydata *src_data = PQresultInstanceData(e->src, myEventProc); + mydata *dest_data = dup_mydata(src_data); + + /* associate app specific data with result (copy it from a result) */ + PQsetResultInstanceData(e->dest, myEventProc, dest_data); + break; + } + + case PGEVT_RESULTDESTROY: + { + PGEventResultDestroy *e = (PGEventResultDestroy *)evtInfo; + mydata *data = PQresultInstanceData(e->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-misc"> <title>Miscellaneous Functions</title> <para> As always, there are some functions that just don't fit anywhere. </para> 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 5 Sep 2008 20:25:30 -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 5 Sep 2008 20:25:30 -0000 *************** *** 138,143 **** --- 138,152 ---- 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 \ 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.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 5 Sep 2008 20:25:30 -0000 *************** *** 1971,1982 **** --- 1971,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; + 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, conn->events[i].passThrough); + 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: --- 2171,2201 ---- 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->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; ! } ! } ! } } } /* * 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. --- 2223,2259 ---- * 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->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; } /* * 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 5 Sep 2008 20:25:30 -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 **** #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) /* * 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. */ PGresult * PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) { PGresult *result; --- 121,177 ---- #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++) + { + newEvents[i].name = strdup(newEvents[i].name); + if (!newEvents[i].name) + { + while (--i >= 0) + free(newEvents[i].name); + free(newEvents); + return NULL; + } + + 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. If conn is ! * not NULL, the PGevents array will be copied from the PGconn to the ! * created PGresult. */ PGresult * PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) { PGresult *result; *************** *** 157,175 **** --- 193,226 ---- 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 **** --- 243,501 ---- 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 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 '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, 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 flags) + { + int i; + PGresult *dest; + PGEventResultCopy evt; + + if (!src) + return NULL; + + /* Automatically turn on attrs flags because you can't copy tuples + * without copying the attrs. _TUPLES implies _ATTRS. + */ + if (flags & PG_COPYRES_TUPLES) + flags |= 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 (flags & PG_COPYRES_NOTICEHOOKS) + dest->noticeHooks = src->noticeHooks; + + /* Wants attrs */ + if ((flags & PG_COPYRES_ATTRS) && + !PQsetResultAttrs(dest, src->numAttributes, src->attDescs)) + { + PQclear(dest); + return NULL; + } + + /* Wants to copy result tuples: use PQsetvalue(). */ + if ((flags & 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 ((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; + } + + /* 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, dest->events[i].passThrough)) + { + 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 **** --- 647,679 ---- * 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, res->events[i].passThrough); + 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); --- 1506,1518 ---- * memory). */ PGresult * PQgetResult(PGconn *conn) { ! PGresult *res=NULL; if (!conn) return NULL; /* Parse any available data, if our state permits. */ parseInput(conn); *************** *** 1267,1278 **** --- 1581,1620 ---- 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, res->events[i].passThrough)) + { + char msg[256]; + + sprintf(msg, + "PGEventProc \"%s\" failed during PGEVT_RESULTCREATE event", + res->events[i].name); + + 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 5 Sep 2008 20:25:30 -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,487 ---- * 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); + /* makes a copy of a result */ + extern PGresult *PQcopyResult(const PGresult *src, int flags); + + /* Sets the attributes of a result, no attributes can already exist. */ + extern int PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs); + + /* Allocate subsidiary storage for a PGresult. */ + 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 5 Sep 2008 20:25:30 -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 */
Attachment
pgsql-hackers by date: