Thread: Re: [PATCHES] libpq events patch (with sgml docs)
Alvaro Herrera wrote: > Andrew Chernow escribió: > >> ! printfPQExpBuffer(&conn->errorMessage, >> ! libpq_gettext("PGEventProc \"%s\" failed during PGEVT_CONNRESET event\n"), >> ! conn->events[i].name); >> ! else >> ! printfPQExpBuffer(&conn->errorMessage, >> ! libpq_gettext("PGEventProc \"addr:%p\" failed during PGEVT_CONNRESET event\n"), >> ! conn->events[i].proc); >> ! break; > > Please don't do this. It creates extra unnecessary work for > translators. Better create a local var, assign either "name" or > "addr:<value>" to it, and then use that in the message. > > (For the record, I'd prefer that the name is made mandatory.) > The name is now mandatory, no need to toggle the format string anymore. -- 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 19:36:39 -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 19:36:39 -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 19:36:39 -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 19:36:39 -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 19:36:39 -0000 *************** *** 60,72 **** int resultFormat); static void parseInput(PGconn *conn); static bool PQexecStart(PGconn *conn); static PGresult *PQexecFinish(PGconn *conn); static int PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target); ! /* ---------------- * Space management for PGresult. * * Formerly, libpq did a separate malloc() for each field of each tuple * returned by a query. This was remarkably expensive --- malloc/free --- 60,72 ---- int resultFormat); static void parseInput(PGconn *conn); static bool PQexecStart(PGconn *conn); static PGresult *PQexecFinish(PGconn *conn); static int PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target); ! static int check_field_number(const PGresult *res, int field_num); /* ---------------- * Space management for PGresult. * * Formerly, libpq did a separate malloc() for each field of each tuple * returned by a query. This was remarkably expensive --- malloc/free *************** *** 121,141 **** --- 121,170 ---- #define PGRESULT_DATA_BLOCKSIZE 2048 #define PGRESULT_ALIGN_BOUNDARY MAXIMUM_ALIGNOF /* from configure */ #define PGRESULT_BLOCK_OVERHEAD Max(sizeof(PGresult_data), PGRESULT_ALIGN_BOUNDARY) #define PGRESULT_SEP_ALLOC_THRESHOLD (PGRESULT_DATA_BLOCKSIZE / 2) + /* Does not duplicate the 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); + newEvents[i].data = NULL; + } + + return newEvents; + } + /* * PQmakeEmptyPGresult * returns a newly allocated, initialized PGresult with given status. * If conn is not NULL and status indicates an error, the conn's * errorMessage is copied. * * Note this is exported --- you wouldn't think an application would need * to build its own PGresults, but this has proven useful in both libpgtcl * and the Perl5 interface, so maybe it's not so unreasonable. + * + * Updated April 2008 - If conn is not NULL, event states will be copied + * from the PGconn to the created PGresult. */ PGresult * PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) { PGresult *result; *************** *** 157,175 **** --- 186,219 ---- 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 **** --- 236,494 ---- 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 **** --- 640,672 ---- * 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); --- 1499,1511 ---- * memory). */ PGresult * PQgetResult(PGconn *conn) { ! PGresult *res=NULL; if (!conn) return NULL; /* Parse any available data, if our state permits. */ parseInput(conn); *************** *** 1267,1278 **** --- 1574,1613 ---- 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 19:36:39 -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 19:36:39 -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
Andrew Chernow escribió: > /* > * PQmakeEmptyPGresult > * returns a newly allocated, initialized PGresult with given status. > * If conn is not NULL and status indicates an error, the conn's > * errorMessage is copied. > * > * Note this is exported --- you wouldn't think an application would need > * to build its own PGresults, but this has proven useful in both libpgtcl > * and the Perl5 interface, so maybe it's not so unreasonable. > + * > + * Updated April 2008 - If conn is not NULL, event states will be copied > + * from the PGconn to the created PGresult. > */ Don't do this either. We don't really need to know when the thing was changed; the comment should just state what the function does. I had folded the last paragraph into the introductory one, but I think you lost that part of my change. > + /* resultalloc the attribute names. The above memcpy has the attr > + * names pointing at the callers provided attDescs memory. > + */ "resultalloc"? Why not just "allocate"? > * PQclear - > * free's the memory associated with a PGresult > */ I'd add a comment here stating why the event name is not deallocated; otherwise it just looks like it's being leaked. -- Alvaro Herrera http://www.CommandPrompt.com/ The PostgreSQL Company - Command Prompt, Inc.
Alvaro Herrera wrote: > Andrew Chernow escribió: > >> * and the Perl5 interface, so maybe it's not so unreasonable. >> + * >> + * Updated April 2008 - If conn is not NULL, event states will be copied >> + * from the PGconn to the created PGresult. >> */ > > Don't do this either. We don't really need to know when the thing was > Changed that and made it one paragraph. >> + /* resultalloc the attribute names. The above memcpy has the attr >> + * names pointing at the callers provided attDescs memory. >> + */ > > "resultalloc"? Why not just "allocate"? > Because resultalloc is what a PGresult uses to allocate its attribute names (See line 513 of fe-protocol3.c). Also, PQclear frees its memory blocks, not individual items like attname. >> * PQclear - >> * free's the memory associated with a PGresult >> */ > > I'd add a comment here stating why the event name is not deallocated; > otherwise it just looks like it's being leaked. > > The event name is being deallocated. -- 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:03:03 -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:03:03 -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:03:03 -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:03:03 -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:03:04 -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,169 ---- #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); + 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 **** --- 185,218 ---- 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 **** --- 235,493 ---- 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 **** --- 639,671 ---- * 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); --- 1498,1510 ---- * memory). */ PGresult * PQgetResult(PGconn *conn) { ! PGresult *res=NULL; if (!conn) return NULL; /* Parse any available data, if our state permits. */ parseInput(conn); *************** *** 1267,1278 **** --- 1573,1612 ---- 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:03:04 -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:03:04 -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
Andrew Chernow escribió: > Alvaro Herrera wrote: >> Andrew Chernow escribió: >>> * PQclear - >>> * free's the memory associated with a PGresult >>> */ >> >> I'd add a comment here stating why the event name is not deallocated; >> otherwise it just looks like it's being leaked. > > The event name is being deallocated. Doh! Sorry, you're right. In that case, you're missing a NULL result check from strdup() in dupEvents() ;-) -- Alvaro Herrera http://www.CommandPrompt.com/ The PostgreSQL Company - Command Prompt, Inc.
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
Andrew Chernow <ac@esilo.com> writes: > Missed that one. Good catch :) Update attached. Looking at this now. Question: why does PQgetResult invoke the RESULTCREATE event only for non-error results? This seems a rather odd asymmetry, particularly in view of the fact that a RESULTDESTROY event will occur for error results. And surely we do not need to micro-optimize error cases for speed. regards, tom lane
Tom Lane wrote: > Andrew Chernow <ac@esilo.com> writes: >> Missed that one. Good catch :) Update attached. > > Looking at this now. Question: why does PQgetResult invoke the > RESULTCREATE event only for non-error results? It didn't seem useful to have the eventproc implementation allocate its private storage (or do whatever prep work it does), just to have it PQclear'd once the user gets the dead result back. I guess we figured an error result typically has a short life-span, not very useful outside of indicating an error. Is there a use case you think requires the opposite behavior? > odd asymmetry, particularly in view of the fact that a RESULTDESTROY > event will occur for error results. And surely we do not need to > micro-optimize error cases for speed. > It is odd. Actually, it looks like a bug to me. PQgetResult is the behavior we were after, don't trigger the event if the creation failed. Same thing occurs during a conn reset. Looks like PQclear needs to check resultStatus before it triggers RESULTDESTROY events. But before I fix this and put a patch up, please let me know if you prefer the opposite behavior ... trigger events on success or failure (including connreset). -- Andrew Chernow eSilo, LLC every bit counts http://www.esilo.com/
Andrew Chernow <ac@esilo.com> writes: > Tom Lane wrote: >> Looking at this now. Question: why does PQgetResult invoke the >> RESULTCREATE event only for non-error results? > It didn't seem useful to have the eventproc implementation allocate its private > storage (or do whatever prep work it does), just to have it PQclear'd once the > user gets the dead result back. I guess we figured an error result typically > has a short life-span, not very useful outside of indicating an error. Well, you could do a PQresultStatus and skip any prep work if you actually saw a need to micro-optimize such cases. > It is odd. Actually, it looks like a bug to me. PQgetResult is the > behavior we were after, don't trigger the event if the creation > failed. Same thing occurs during a conn reset. Looks like PQclear > needs to check resultStatus before it triggers RESULTDESTROY events. If you did that, then you WOULD have a bug. Consider case where you successfully init two events, and the third one fails. You'll now change the result's status to ERROR. If you don't RESULTDESTROY then the first two events will leak whatever storage they allocated. The reason I suggested not being conditional about the init call was to reduce the probability of bugs of similar ilks associated with an event proc assuming that it could only get a DESTROY call for something it had seen CREATEd --- for example, if it were frobbing a reference count for some long-lived data, it could very easily screw up the reference count that way. I suppose that the risk of an earlier event proc failing means it has to cope with that case anyway, but I don't think it's a particularly good idea to have arbitrary asymmetries increasing the odds of a problem. regards, tom lane
Tom Lane wrote: >> It is odd. Actually, it looks like a bug to me. PQgetResult is the >> behavior we were after, don't trigger the event if the creation >> failed. Same thing occurs during a conn reset. Looks like PQclear >> needs to check resultStatus before it triggers RESULTDESTROY events. > > If you did that, then you WOULD have a bug. Consider case where you > successfully init two events, and the third one fails. You'll now > change the result's status to ERROR. If you don't RESULTDESTROY > then the first two events will leak whatever storage they allocated. > > The reason I suggested not being conditional about the init call was > to reduce the probability of bugs of similar ilks associated with > an event proc assuming that it could only get a DESTROY call for > something it had seen CREATEd --- for example, if it were frobbing > a reference count for some long-lived data, it could very easily > screw up the reference count that way. I suppose that the risk of > an earlier event proc failing means it has to cope with that case > anyway, but I don't think it's a particularly good idea to have > arbitrary asymmetries increasing the odds of a problem. > > regards, tom lane > > Yeah. Good point. I fixed it to always fire events. -- 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 17 Sep 2008 03:54:20 -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 17 Sep 2008 03:54:20 -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 17 Sep 2008 03:54:20 -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 17 Sep 2008 03:54:20 -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.197 diff -C6 -r1.197 fe-exec.c *** src/interfaces/libpq/fe-exec.c 10 Sep 2008 17:01:07 -0000 1.197 --- src/interfaces/libpq/fe-exec.c 17 Sep 2008 03:54:20 -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,1617 ---- libpq_gettext("unexpected asyncStatus: %d\n"), (int) conn->asyncStatus); res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); break; } + if (res) + { + 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 17 Sep 2008 03:54:20 -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 17 Sep 2008 03:54:20 -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
Andrew Chernow <ac@esilo.com> writes: > Yeah. Good point. I fixed it to always fire events. Applied with editorial revisions --- I don't think I changed any functionality, but I fixed a number of corner-case bugs and editorialized on style a bit. The general question of symmetry between RESULTCREATE and RESULTDESTROY callbacks is still bothering me. As committed, PQmakeEmptyPGresult will copy events into its result, but not fire RESULTCREATE for them ... but they'll get RESULTDESTROY when it's deleted. Is that what we want? I don't have a lot of faith that PQgetResult is the only place inside libpq that needs to fire RESULTCREATE, either. Now, it's questionable how tense we need to be about that as long as event proc failure aborts calling of later event procs. That means that procs have to be robust against getting DESTROY with no CREATE calls in any case. Should we try to make that less uncertain? regards, tom lane
> Now, it's questionable how tense we need to be about that as long as > event proc failure aborts calling of later event procs. That means > that procs have to be robust against getting DESTROY with no CREATE > calls in any case. Should we try to make that less uncertain? > > I attached a patch that adds a 'needDestroy' member to PGEvent It is set when resultcreate or resultcopy succeeds and checked during a PQclear. That *should* resolve the issue of "no resultcreate but gets a resultdestroy". > The general question of symmetry between RESULTCREATE and RESULTDESTROY > callbacks is still bothering me. As committed, PQmakeEmptyPGresult > will copy events into its result, but not fire RESULTCREATE for them > ... but they'll get RESULTDESTROY when it's deleted. Is that what we > want? PQmakeEmptyPGResult was given thought. The problem is every internal function that generates a result calls PQmakeEmptyPGResult, but those cases should not fire a resultcreate. resultcreate should be called when the result is fully constructed (tuples and all) so the eventproc gets a useful PGresult. One solution is to do something like the below: PGresult * PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) { return pqMakeEmptyPGresult(conn, status, TRUE); } PGresult * pqMakeEmptyPGresult(PGconn *conn, ExecStatusType status, int fireEvents) { // existing function, only change is handling fireEvents } I am willing to create a patch for this. Is this an acceptable idea? > I don't have a lot of faith that PQgetResult is the only place > inside libpq that needs to fire RESULTCREATE, either. > I looked again and I didn't see anything. Is there something your thinking of? ISTM that PQgetResult is called every where a result is created (outside of PQmakeEmptyPGresult). -- Andrew Chernow eSilo, LLC every bit counts http://www.esilo.com/ Index: src/interfaces/libpq/fe-exec.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v retrieving revision 1.198 diff -C6 -r1.198 fe-exec.c *** src/interfaces/libpq/fe-exec.c 17 Sep 2008 04:31:08 -0000 1.198 --- src/interfaces/libpq/fe-exec.c 17 Sep 2008 10:40:41 -0000 *************** *** 356,367 **** --- 356,369 ---- if (!dest->events[i].proc(PGEVT_RESULTCOPY, &evt, dest->events[i].passThrough)) { PQclear(dest); return NULL; } + + dest->events[i].needDestroy = TRUE; } return dest; } /* *************** *** 378,394 **** 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); --- 380,396 ---- return NULL; newEvents = (PGEvent *) malloc(count * sizeof(PGEvent)); if (!newEvents) return NULL; for (i = 0; i < count; i++) { + newEvents[i].proc = events[i].proc; + newEvents[i].passThrough = events[i].passThrough; + newEvents[i].needDestroy = FALSE; newEvents[i].data = NULL; newEvents[i].name = strdup(newEvents[i].name); if (!newEvents[i].name) { while (--i >= 0) free(newEvents[i].name); *************** *** 663,679 **** 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); --- 665,685 ---- if (!res) return; for (i = 0; i < res->nEvents; i++) { ! if(res->events[i].needDestroy) ! { ! 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); *************** *** 1609,1620 **** --- 1615,1628 ---- 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; } + + res->events[i].needDestroy = TRUE; } } return res; } Index: src/interfaces/libpq/libpq-int.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v retrieving revision 1.132 diff -C6 -r1.132 libpq-int.h *** src/interfaces/libpq/libpq-int.h 17 Sep 2008 04:31:08 -0000 1.132 --- src/interfaces/libpq/libpq-int.h 17 Sep 2008 10:40:41 -0000 *************** *** 153,164 **** --- 153,165 ---- 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 */ + int needDestroy; /* indicates a PGEVT_RESULTDESTROY is needed. */ } PGEvent; struct pg_result { int ntups; int numAttributes;
Andrew Chernow wrote: > > Now, it's questionable how tense we need to be about that as long as > > event proc failure aborts calling of later event procs. That means > > that procs have to be robust against getting DESTROY with no CREATE > > calls in any case. Should we try to make that less uncertain? > > > > > > I attached a patch that adds a 'needDestroy' member to PGEvent It is > set when resultcreate or resultcopy succeeds and checked during a > PQclear. That *should* resolve the issue of "no resultcreate but gets a > resultdestroy". > > Shoot. I have a booboo in that last patch. Attached is the corrected version. -- Andrew Chernow eSilo, LLC every bit counts http://www.esilo.com/ Index: src/interfaces/libpq/fe-exec.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v retrieving revision 1.198 diff -C6 -r1.198 fe-exec.c *** src/interfaces/libpq/fe-exec.c 17 Sep 2008 04:31:08 -0000 1.198 --- src/interfaces/libpq/fe-exec.c 17 Sep 2008 10:49:10 -0000 *************** *** 356,367 **** --- 356,369 ---- if (!dest->events[i].proc(PGEVT_RESULTCOPY, &evt, dest->events[i].passThrough)) { PQclear(dest); return NULL; } + + dest->events[i].needDestroy = TRUE; } return dest; } /* *************** *** 378,396 **** 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; --- 380,398 ---- return NULL; newEvents = (PGEvent *) malloc(count * sizeof(PGEvent)); if (!newEvents) return NULL; for (i = 0; i < count; i++) { + newEvents[i].proc = events[i].proc; + newEvents[i].passThrough = events[i].passThrough; + newEvents[i].needDestroy = FALSE; newEvents[i].data = NULL; ! newEvents[i].name = strdup(events[i].name); if (!newEvents[i].name) { while (--i >= 0) free(newEvents[i].name); free(newEvents); return NULL; *************** *** 663,679 **** 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); --- 665,685 ---- if (!res) return; for (i = 0; i < res->nEvents; i++) { ! if(res->events[i].needDestroy) ! { ! 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); *************** *** 1609,1620 **** --- 1615,1628 ---- 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; } + + res->events[i].needDestroy = TRUE; } } return res; } Index: src/interfaces/libpq/libpq-int.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v retrieving revision 1.132 diff -C6 -r1.132 libpq-int.h *** src/interfaces/libpq/libpq-int.h 17 Sep 2008 04:31:08 -0000 1.132 --- src/interfaces/libpq/libpq-int.h 17 Sep 2008 10:49:10 -0000 *************** *** 153,164 **** --- 153,165 ---- 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 */ + int needDestroy; /* indicates a PGEVT_RESULTDESTROY is needed. */ } PGEvent; struct pg_result { int ntups; int numAttributes;
Andrew Chernow <ac@esilo.com> writes: >>> Now, it's questionable how tense we need to be about that as long as >>> event proc failure aborts calling of later event procs. That means >>> that procs have to be robust against getting DESTROY with no CREATE >>> calls in any case. Should we try to make that less uncertain? > I attached a patch that adds a 'needDestroy' member to PGEvent It is > set when resultcreate or resultcopy succeeds and checked during a > PQclear. That *should* resolve the issue of "no resultcreate but gets > a resultdestroy". Some thought would need to be given to how that interacts with RESULTCOPY. Presumably on the destination side, RESULTCOPY is the equivalent of RESULTCREATE, ie, don't DESTROY if you didn't get COPY. But what of the source side? Arguably you shouldn't invoke COPY on events that were never initialized in this object. I also think that a little bit of thought should go into whether or not to call DESTROY on an event that *did* get a CREATE and failed it. You could argue that one either way: should a failing CREATE operation be expected to fully clean up after itself, or should it be expected to leave things in a state where DESTROY can clean up properly? I'm not entirely sure, but option A might force duplication of code between CREATE's failure path and DESTROY. Whichever semantics we choose needs to be documented. Also, if we choose option B, then the same would hold for REGISTER versus CONNDESTROY: failing REGISTER should still mean that you get a CONNDESTROY. So maybe that's an argument for option A, because that's how REGISTER works now. Lastly, the idea that was in the back of my mind was to resolve this issue by unconditionally calling all the event procs at CREATE time, not by cutting off the later ones if an earlier one failed. Again, I do not see a performance argument for skipping the extra steps, and it seems to me that it might improve symmetry/robustness. I'm not necessarily wedded to that approach but it bears thinking about. regards, tom lane
Tom Lane wrote: > > Some thought would need to be given to how that interacts with > RESULTCOPY. Presumably on the destination side, RESULTCOPY is the > equivalent of RESULTCREATE, ie, don't DESTROY if you didn't get COPY. > But what of the source side? Arguably you shouldn't invoke COPY on > events that were never initialized in this object. > You are correct. The resultcopy function needs to check if the src result eventproc was initialized. BUT, that should not be possible unless someone is not checking return values. Meaning, the only way to have an uninitialized eventproc in this instance is if something failed during a resultcreate. Actually, if you call PQmakeEmptyPGResult then you can also run into this problem. That can be solved by adding an argument to makeResult as I previously suggested. > I also think that a little bit of thought should go into whether or > not to call DESTROY on an event that *did* get a CREATE and failed it. > You could argue that one either way: should a failing CREATE operation > be expected to fully clean up after itself, or should it be expected > to leave things in a state where DESTROY can clean up properly? > I'm not entirely sure, but option A might force duplication of code > between CREATE's failure path and DESTROY. Whichever semantics we > choose needs to be documented. > If a resultcreate fails, than I think that resultdestroy should not be delivered to that eventproc (same for resultcopy). That is how register works and how I saw this patch working (eventhough it appears I have a few logical errors). I don't think it makes sense to do it otherwise, it would be like calling free after a malloc failure. The needDestroy member should be called resultInitialized. Although the conn doesn't reference the 'resultInitialized' member, I should initialize it to FALSE. I did not do this in the last patch ... register function. -- Andrew Chernow eSilo, LLC every bit counts http://www.esilo.com/
Andrew Chernow <ac@esilo.com> writes: > Tom Lane wrote: >> Some thought would need to be given to how that interacts with >> RESULTCOPY. Presumably on the destination side, RESULTCOPY is the >> equivalent of RESULTCREATE, ie, don't DESTROY if you didn't get COPY. >> But what of the source side? Arguably you shouldn't invoke COPY on >> events that were never initialized in this object. > You are correct. The resultcopy function needs to check if the src > result eventproc was initialized. BUT, that should not be possible > unless someone is not checking return values. Meaning, the only way to > have an uninitialized eventproc in this instance is if something failed > during a resultcreate. Actually, if you call PQmakeEmptyPGResult then > you can also run into this problem. That can be solved by adding an > argument to makeResult as I previously suggested. I still think it would be a good idea to expend a couple more lines in PQcopyResult to cover the case --- the likelihood of there being code paths that make an empty result and never invoke RESULTCREATE just seems too high. Defensive programming 'n all that. In any case I'm not convinced that we should force a RESULTCREATE cycle on external callers of PQmakeEmptyPGResult. If you guys didn't see a need for it in your development then it probably doesn't exist. > If a resultcreate fails, than I think that resultdestroy should not be > delivered to that eventproc (same for resultcopy). That is how register > works and how I saw this patch working (eventhough it appears I have a > few logical errors). I don't think it makes sense to do it otherwise, > it would be like calling free after a malloc failure. I can live with that definition, but please document it. > The needDestroy member should be called resultInitialized. Yeah, probably, so that you can set it FALSE in conn events and continue to use memcpy to move things over. regards, tom lane
New patch following our discussion with updated docs. >> few logical errors). I don't think it makes sense to do it otherwise, >> it would be like calling free after a malloc failure. > > I can live with that definition, but please document it. > > To build on this analogy, PGEVT_CONNRESET is like a realloc. Meaning, the initial malloc "PGEVT_REGISTER" worked by the realloc "PGEVT_CONNRESET" didn't ... you still have free "PGEVT_CONNDESTROY" the initial. Its documented that way. Basically if a register succeeds, a destroy will always be sent regardless of what happens with a reset. -- 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 17 Sep 2008 03:54:20 -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 17 Sep 2008 03:54:20 -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 17 Sep 2008 03:54:20 -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 17 Sep 2008 03:54:20 -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.197 diff -C6 -r1.197 fe-exec.c *** src/interfaces/libpq/fe-exec.c 10 Sep 2008 17:01:07 -0000 1.197 --- src/interfaces/libpq/fe-exec.c 17 Sep 2008 03:54:20 -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,1617 ---- libpq_gettext("unexpected asyncStatus: %d\n"), (int) conn->asyncStatus); res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); break; } + if (res) + { + 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 17 Sep 2008 03:54:20 -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 17 Sep 2008 03:54:20 -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 */
Andrew Chernow wrote: > New patch following our discussion with updated docs. > >>> few logical errors). I don't think it makes sense to do it >>> otherwise, it would be like calling free after a malloc failure. >> >> I can live with that definition, but please document it. >> >> > > To build on this analogy, PGEVT_CONNRESET is like a realloc. Meaning, > the initial malloc "PGEVT_REGISTER" worked by the realloc > "PGEVT_CONNRESET" didn't ... you still have free "PGEVT_CONNDESTROY" the > initial. Its documented that way. Basically if a register succeeds, a > destroy will always be sent regardless of what happens with a reset. > > I attached the wrong patch. I'm sorry. -- 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.261 diff -C6 -r1.261 libpq.sgml *** doc/src/sgml/libpq.sgml 17 Sep 2008 04:31:08 -0000 1.261 --- doc/src/sgml/libpq.sgml 17 Sep 2008 14:19:29 -0000 *************** *** 4911,4923 **** 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> --- 4911,4925 ---- 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>. When returning a failure code, all ! cleanup must be performed as no <literal>PGEVT_CONNDESTROY</literal> ! event will be sent. </para> </listitem> </varlistentry> <varlistentry> <term><literal>PGEVT_CONNRESET</literal></term> *************** *** 4941,4953 **** 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> --- 4943,4956 ---- 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>. A PGEVT_CONNDESTROY event ! is always sent, regardless of the event procedure's return value. </para> </listitem> </varlistentry> <varlistentry> <term><literal>PGEVT_CONNDESTROY</literal></term> *************** *** 5000,5023 **** <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; --- 5003,5030 ---- <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. When returning a ! failure code, all cleanup must be performed as no ! <literal>PGEVT_RESULTDESTROY</literal> event will be sent. </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. Only source result event procedures that have ! successfully handled the <literal>PGEVT_RESULTCREATE</literal> event, ! will be copied to the destination result. <synopsis> typedef struct { const PGresult *src; PGresult *dest; *************** *** 5029,5041 **** <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> --- 5036,5050 ---- <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. When returning a ! failure code, all cleanup must be performed as no ! <literal>PGEVT_RESULTDESTROY</literal> event will be sent. </para> </listitem> </varlistentry> <varlistentry> <term><literal>PGEVT_RESULTDESTROY</literal></term> Index: src/interfaces/libpq/fe-exec.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v retrieving revision 1.198 diff -C6 -r1.198 fe-exec.c *** src/interfaces/libpq/fe-exec.c 17 Sep 2008 04:31:08 -0000 1.198 --- src/interfaces/libpq/fe-exec.c 17 Sep 2008 14:19:29 -0000 *************** *** 346,366 **** 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; } --- 346,371 ---- dest->nEvents = src->nEvents; } /* Okay, trigger PGEVT_RESULTCOPY event */ for (i = 0; i < dest->nEvents; i++) { ! if(src->events[i].resultInitialized) { ! PGEventResultCopy evt; ! ! evt.src = src; ! evt.dest = dest; ! if (!dest->events[i].proc(PGEVT_RESULTCOPY, &evt, ! dest->events[i].passThrough)) ! { ! PQclear(dest); ! return NULL; ! } ! ! dest->events[i].resultInitialized = TRUE; } } return dest; } *************** *** 378,396 **** 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; --- 383,401 ---- return NULL; newEvents = (PGEvent *) malloc(count * sizeof(PGEvent)); if (!newEvents) return NULL; for (i = 0; i < count; i++) { + newEvents[i].proc = events[i].proc; + newEvents[i].passThrough = events[i].passThrough; + newEvents[i].resultInitialized = FALSE; newEvents[i].data = NULL; ! newEvents[i].name = strdup(events[i].name); if (!newEvents[i].name) { while (--i >= 0) free(newEvents[i].name); free(newEvents); return NULL; *************** *** 663,679 **** 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); --- 668,688 ---- if (!res) return; for (i = 0; i < res->nEvents; i++) { ! if(res->events[i].resultInitialized) ! { ! 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); *************** *** 1609,1620 **** --- 1618,1631 ---- 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; } + + res->events[i].resultInitialized = TRUE; } } return res; } Index: src/interfaces/libpq/libpq-events.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-events.c,v retrieving revision 1.1 diff -C6 -r1.1 libpq-events.c *** src/interfaces/libpq/libpq-events.c 17 Sep 2008 04:31:08 -0000 1.1 --- src/interfaces/libpq/libpq-events.c 17 Sep 2008 14:19:29 -0000 *************** *** 73,84 **** --- 73,85 ---- 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->events[conn->nEvents].resultInitialized = FALSE; conn->nEvents++; regevt.conn = conn; if (!proc(PGEVT_REGISTER, ®evt, passThrough)) { conn->nEvents--; Index: src/interfaces/libpq/libpq-int.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v retrieving revision 1.132 diff -C6 -r1.132 libpq-int.h *** src/interfaces/libpq/libpq-int.h 17 Sep 2008 04:31:08 -0000 1.132 --- src/interfaces/libpq/libpq-int.h 17 Sep 2008 14:19:29 -0000 *************** *** 153,164 **** --- 153,165 ---- 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 */ + int resultInitialized; /* indicates a PGEVT_RESULTCREATE succeeded. */ } PGEvent; struct pg_result { int ntups; int numAttributes;
Are there any plans to commit these libpq-events changes this fest? http://archives.postgresql.org/pgsql-hackers/2008-09/msg01109.php I wanted to release an updated libpqtypes but am waiting on the above patch. If not, I'll release it now. -- Andrew Chernow eSilo, LLC every bit counts http://www.esilo.com/
Andrew Chernow <ac@esilo.com> writes: > Are there any plans to commit these libpq-events changes this fest? Sorry about that, I got distracted. Will look at it shortly. regards, tom lane
Andrew Chernow <ac@esilo.com> writes: >> To build on this analogy, PGEVT_CONNRESET is like a realloc. Meaning, >> the initial malloc "PGEVT_REGISTER" worked by the realloc >> "PGEVT_CONNRESET" didn't ... you still have free "PGEVT_CONNDESTROY" the >> initial. Its documented that way. Basically if a register succeeds, a >> destroy will always be sent regardless of what happens with a reset. > I attached the wrong patch. I'm sorry. I had a further thought about this: after applying this patch, it is essentially useless for the exposed PQmakeEmptyPGresult function to copy events into the result. If it doesn't give them a RESULTCREATE call, then they cannot receive RESULTCOPY or RESULTDESTROY either, so they might as well not be there. The argument for not having PQmakeEmptyPGresult fire RESULTCREATE still makes sense, but I am thinking that maybe what we ought to do is expose a new function named something like PQfireResultCreateEvents() that just does that. This would allow an application to exactly emulate what PQgetResult does: make an empty PGresult, fill it, then fire the create events. I'll go ahead and apply this patch in a little bit, but if you concur with the above reasoning, please put together a followon patch to add such a function. regards, tom lane
Tom Lane wrote: > > I'll go ahead and apply this patch in a little bit, but if you concur > with the above reasoning, please put together a followon patch to add > such a function. > > regards, tom lane > > I attached a patch. You have to copy the events in PQmakeEmptyPGResult because there is no where else to do this, other than copyresult but that is different because it copies from a result not a conn. PQmakeEmptyPGResult - must copy events here PQsetResultAttrs - set attributes PQsetvalue - set tuple values PQfireResultCreateEvents(conn,res) - now fire resultcreate event PQgetResult now calls PQfireResultCreateEvents. BTW, the event system might be an alternative solution for PGNoticeHooks (PGEVT_NOTICE). -- 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.262 diff -C6 -r1.262 libpq.sgml *** doc/src/sgml/libpq.sgml 19 Sep 2008 16:40:40 -0000 1.262 --- doc/src/sgml/libpq.sgml 19 Sep 2008 18:05:55 -0000 *************** *** 4592,4604 **** <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> --- 4592,4606 ---- <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). Although, ! <function>PQfireResultCreateEvents</function> can be used to fire ! <literal>PGEVT_RESULTCREATE</> events. 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> *************** *** 5242,5253 **** --- 5244,5279 ---- void *PQresultInstanceData(const PGresult *res, PGEventProc proc); </synopsis> </para> </listitem> </varlistentry> </variablelist> + + <varlistentry> + <term> + <function>PQfireResultCreateEvents</function> + <indexterm> + <primary>PQfireResultCreateEvents</primary> + </indexterm> + </term> + <listitem> + <para> + Manually fires a <literal>PGEVT_RESULTCREATE</literal> event. This is + useful for applications that create a result using + <function>PQmakeEmptyPGResult</function>, which does not fire a + <literal>PGEVT_RESULTCREATE</literal> event. It allows an application + to create the result, fill it and then fire the creation event. This + returns non-zero for success and zero for failure. + + <synopsis> + int PQfireResultCreateEvents(const PGconn conn, PGresult *res); + </synopsis> + </para> + </listitem> + </varlistentry> + </variablelist> </sect2> <sect2 id="libpq-events-example"> <title>Event Example</title> <para> Index: src/interfaces/libpq/exports.txt =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/exports.txt,v retrieving revision 1.20 diff -C6 -r1.20 exports.txt *** src/interfaces/libpq/exports.txt 17 Sep 2008 04:31:08 -0000 1.20 --- src/interfaces/libpq/exports.txt 19 Sep 2008 18:05:55 -0000 *************** *** 147,152 **** --- 147,153 ---- PQresultAlloc 145 PQregisterEventProc 146 PQinstanceData 147 PQsetInstanceData 148 PQresultInstanceData 149 PQresultSetInstanceData 150 + PQfireResultCreateEvents 151 Index: src/interfaces/libpq/fe-exec.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v retrieving revision 1.199 diff -C6 -r1.199 fe-exec.c *** src/interfaces/libpq/fe-exec.c 19 Sep 2008 16:40:40 -0000 1.199 --- src/interfaces/libpq/fe-exec.c 19 Sep 2008 18:05:55 -0000 *************** *** 1595,1630 **** libpq_gettext("unexpected asyncStatus: %d\n"), (int) conn->asyncStatus); res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); 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; ! } ! res->events[i].resultInitialized = TRUE; ! } ! } ! return res; } /* * PQexec --- 1595,1608 ---- libpq_gettext("unexpected asyncStatus: %d\n"), (int) conn->asyncStatus); res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); break; } ! /* Function performs error handling: message and resultStatus. */ ! (void) PQfireResultCreateEvents(conn, res); return res; } /* * PQexec Index: src/interfaces/libpq/libpq-events.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-events.c,v retrieving revision 1.2 diff -C6 -r1.2 libpq-events.c *** src/interfaces/libpq/libpq-events.c 19 Sep 2008 16:40:40 -0000 1.2 --- src/interfaces/libpq/libpq-events.c 19 Sep 2008 18:05:55 -0000 *************** *** 172,177 **** --- 172,209 ---- for (i = 0; i < result->nEvents; i++) if (result->events[i].proc == proc) return result->events[i].data; return NULL; } + + int + PQfireResultCreateEvents(const PGconn *conn, PGresult *res) + { + int i; + + if (!conn || !res) + return FALSE; + + 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; + return FALSE; + } + + res->events[i].resultInitialized = TRUE; + } + + return TRUE; + } + Index: src/interfaces/libpq/libpq-events.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-events.h,v retrieving revision 1.1 diff -C6 -r1.1 libpq-events.h *** src/interfaces/libpq/libpq-events.h 17 Sep 2008 04:31:08 -0000 1.1 --- src/interfaces/libpq/libpq-events.h 19 Sep 2008 18:05:55 -0000 *************** *** 81,91 **** --- 81,93 ---- /* 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); + extern int PQfireResultCreateEvents(const PGconn *conn, PGresult *res); + #ifdef __cplusplus } #endif #endif /* LIBPQ_EVENTS_H */
On Fri, Sep 19, 2008 at 2:14 PM, Andrew Chernow <ac@esilo.com> wrote: > > BTW, the event system might be an alternative solution for PGNoticeHooks > (PGEVT_NOTICE). > Another possible use of the event hooks -- just spitballing here -- is to generate an event when a notification comes through (you would still receive events the old way., by command or PQconsumeInput). Maybe this would eventually replace the current notification interface (wasn't this going to be changed anyways?) merlin
BTW, why are all the conn parameters to events declared "const"? Isn't the reason for passing them in mainly to give the event proc a chance to issue queries? regards, tom lane
Tom Lane wrote: > BTW, why are all the conn parameters to events declared "const"? Isn't Because it looked prettier? Kidding. No idea, do you want me to change that or do you want to? > the reason for passing them in mainly to give the event proc a chance > to issue queries? > > Partly. You also want to give the eventproc a chance to issue a PQinstanceData call, so it can copy stuff to the created result. -- Andrew Chernow eSilo, LLC every bit counts http://www.esilo.com/
Andrew Chernow <ac@esilo.com> writes: > Tom Lane wrote: >> BTW, why are all the conn parameters to events declared "const"? Isn't > Because it looked prettier? Kidding. No idea, do you want me to change > that or do you want to? I'll fix it; it's hardly worth passing a patch around for. regards, tom lane
Andrew Chernow <ac@esilo.com> writes: > I attached a patch. You have to copy the events in PQmakeEmptyPGResult > because there is no where else to do this, other than copyresult but > that is different because it copies from a result not a conn. Applied ... > PQgetResult now calls PQfireResultCreateEvents. ... except I didn't do that because the error handling didn't seem appropriate. Since PQmakeEmptyPGResult allows a null conn, PQfireResultCreateEvents ought to as well. So I just made it return false on failure. regards, tom lane
"Merlin Moncure" <mmoncure@gmail.com> writes: > On Fri, Sep 19, 2008 at 2:14 PM, Andrew Chernow <ac@esilo.com> wrote: >> BTW, the event system might be an alternative solution for PGNoticeHooks >> (PGEVT_NOTICE). > Another possible use of the event hooks -- just spitballing here -- is > to generate an event when a notification comes through (you would > still receive events the old way., by command or PQconsumeInput). Seems rather pointless; you'd just be adding multiple ways to do things we can do perfectly well now. > Maybe this would eventually replace the current notification interface > (wasn't this going to be changed anyways?) No, I don't think anyone was unhappy with the libpq API for it. regards, tom lane