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:00:56 -0000 *************** *** 2092,2103 **** --- 2092,2222 ---- Note that PQclear should eventually be called on the object, just as with a PGresult returned by libpq itself. + + + + PQcopyResult + + PQcopyResult + + + + + + Makes a copy of a PGresult object. + + PGresult *PQcopyResult(const PGresult *src, int flags); + + + + + The returned result is always put into PGRES_TUPLES_OK status. + It is not linked to the source result in any way and + PQclear must be called when the result is no longer needed. + If the function fails, NULL is returned. + + + + Optionally, the flags argument can be used to copy + more parts of the result. PG_COPYRES_ATTRS will copy the + source result's attributes. PG_COPYRES_TUPLES will copy + the source result's tuples. This implies copying the attrs, being how the + attrs are needed by the tuples. PG_COPYRES_EVENTS will + copy the source result's events. PG_COPYRES_NOTICEHOOKS + will copy the source result's notify hooks. + + + + + + + PQsetResultAttrs + + PQsetResultAttrs + + + + + + Sets the attributes of a PGresult object. + + int PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs); + + + + + The provided attDescs are copied into the result, thus the + attDescs are no longer needed by res after + the function returns. If the attDescs are NULL or + numAttributes is less than one, the request is ignored and + the function succeeds. If res 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. + + + + + + + PQsetvalue + + PQsetvalue + + + + + + Sets a tuple field value of a PGresult object. + + int PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len); + + + + + The function will automatically grow the result's internal tuples array as needed. + The tup_num argument must be less than or equal to + PQntuples, 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 field_num + already eixsts, it will be overwritten. If len is -1 or + value is NULL, the field value will + be set to an SQL NULL. The value is copied + into result's private storage, thus no longer needed by res after + the function returns. If the function fails, the return value + is zero. If the function succeeds, the return value is non-zero. + + + + + + + PQresultAlloc + + PQresultAlloc + + + + + + Allocate subsidiary storage for a PGresult object. + + void *PQresultAlloc(PGresult *res, size_t nBytes); + + + + + Any memory allocated with this function, will be freed when + res is cleared. If the function fails, + the return value is NULL. + + + Retrieving Query Result Information *************** *** 4558,4569 **** --- 4677,5158 ---- + + Event System + + + The event system is designed to notify registered event handlers + about particular libpq events; such as the creation or destruction + of PGconn and PGresult + objects. One use case is that this allows applications to associate + their own data with a PGconn and + PGresult, that can retrieved via + PQinstanceData and PQresultInstanceData. + Basically, its like adding members to the opaque conn or result + structures at runtime. + + + + Below is a list of event system concepts: + + + + eventId - Identifies which event was fired by libpq. All + events begin with PGEVT_. + + + + + eventInfo - Every eventId has a corresponding + event info structure, that contains data to aid in processing the event. + + + + + instanceData - The instance data is memory associated with a + PGconn or PGresult. The data is + created and destroyed along with with the connection or result. + Typically, instance data is created in response to + a PGEVT_REGISTER event, but can be created at any time or + not at all. Management of instance data is done using the PQinstanceData, + PQsetInstanceData, PQresultInstanceData and + PQsetResultInstanceData functions. + + + + + + + + Event Types + + + + PGEVT_REGISTER + + + The register event occurs when PQregisterEventProc is + called. It is the ideal time to initialize any instanceData + an event procedure may need. Only one register event will be fired per connection. + If the event procedure fails, the registration is aborted. + + + typedef struct + { + const PGconn *conn; + } PGEventRegister; + + When a PGEVT_REGISTER event is received, the evtInfo + pointer should be casted to a PGEventRegister*. This structure + contains a PGconn that should be in the CONNECTION_OK + status; guaranteed if one calls PQregisterEventProc right after obtaining + a good PGconn. The connection can be used to initialize any + application specific needs: like allocating structures as the event instanceData + or executing SQL statements. + + + + + + PGEVT_CONNRESET + + + The connection reset event is fired in response to a PQreset or PQresetPoll. + 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 PGconn is put into + CONNECTION_BAD status and PQresetPoll will return + PGRES_POLLING_FAILED. + + typedef struct + { + const PGconn *conn; + } PGEventReset; + + When a PGEVT_CONNRESET event is received, the evtInfo + pointer should be casted to a PGEventReset*. Although the contained + PGconn was just reset, all event data remains unchanged. This event + should be used to reset/reload/requery any assocaited instanceData. + + + + + + PGEVT_CONNDESTROY + + + The connection destroy event is fired in response to a PQfinish. + 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. + + typedef struct + { + const PGconn *conn; + } PGEventConnDestroy; + + When a PGEVT_CONNDESTROY event is received, the evtInfo + pointer should be casted to a PGEventConnDestroy*. This event is fired + prior to PQfinish performing any cleanup. The return value of the event + procedure is ignored since there is no way of indicating a failure from PQfinish. + Also, an event procedure failure should not abort the process of cleaning up unwanted memory. + + + + + + PGEVT_RESULTCREATE + + + The result create event is fired in response to any exec function that generates a result, + including PQgetResult. This event will only be fired after the result + has been created successfully, with a result status of either PGRES_COMMAND_OK, + PGRES_TUPLES_OK or PGRES_EMPTY_QUERY. + + typedef struct + { + const PGconn *conn; + const PGresult *result; + } PGEventResultCreate; + + When a PGEVT_RESULTCREATE event is received, the evtInfo + pointer should be casted to a PGEventResultCreate*. The conn + was the connection used to generate the result. This is the ideal place to initialize + any instanceData 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 PGEventResultCreate structure. + + + + + + PGEVT_RESULTCOPY + + + The result copy event is fired in response to PQcopyResult. + This event will only be fired after the copy is complete. + + typedef struct + { + const PGresult *src; + PGresult *dest; + } PGEventResultCopy; + + When a PGEVT_RESULTCOPY event is received, the evtInfo + pointer should be casted to a PGEventResultCopy*. The src + result is what is being copied while the dest result is the copy destination. + This event can be used to provide a deep copy of instanceData, since + PQcopyResult cannot do that. If the event procedure fails, the entire copy operation + will fail and the dest result will be cleared. + + + + + + PGEVT_RESULTDESTROY + + + The result destroy event is fired in response to a PQclear. + 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. + + typedef struct + { + const PGresult *result; + } PGEventResultDestroy; + + When a PGEVT_RESULTDESTROY event is received, the evtInfo + pointer should be casted to a PGEventResultDestroy*. This event is fired + prior to PQclear performing any cleanup. The return value of the event + procedure is ignored since there is no way of indicating a failure from PQclear. + Also, an event procedure failure should not abort the process of cleaning up unwanted memory. + + + + + + + + + Event Callback Procedure + + + + PGEventProc + + PGEventProc + + + + + + User callback function that receives events from libpq. The address of the event + proc is also used to lookup the instanceData + associated with a PGconn or PGresult. + typedef int (*PGEventProc)(PGEventId evtId, void *evtInfo, void *passThrough); + + + + The evtId inidcates which PGEVT_ event occurred. + The evtInfo must be casted to the appropriate structure: ex. PGEVT_REGISTER + means cast evtInfo to a PGEventRegister pointer. The passThrough is the + pointer provided to the PQregisterEventProc, which can be NULL. The function + should return a non-zero value if it succeeds and zero if it fails. + + + + + + + + Event Functions + + + + PQregisterEventProc + + PQregisterEventProc + + + + + + Registers an event callback procedure with libpq. + int PQregisterEventProc(PGconn *conn, PGEventProc proc, const char *name, void *passThrough); + + + + 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. + + + + The proc argument will be called when a libpq event is fired. Its + memory address is also used to lookup instanceData. + The name argument is used to contruct error messages to aid + in debugging. This value can be NULL if not needed, in which case the proc + address will be used in error messages. + The passThrough pointer is passed to the proc whenever + an event occurs. This argument can be NULL. + + + + + + + PQinstanceData + + PQinstanceData + + + + + Returns the conn's instanceData associated with proc. + void *PQinstanceData(const PGconn *conn, PGEventProc proc); + + + + + + + PQsetInstanceData + + PQsetInstanceData + + + + + Sets the conn's instanceData for proc to data. This reutrns non-zero for + succees and zero for failure. + int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data); + + + + + + + PQresultInstanceData + + PQresultInstanceData + + + + + Returns the result's instanceData associated by proc. + void *PQresultInstanceData(const PGresult *res, PGEventProc proc); + + + + + + + PQresultSetInstanceData + + PQresultSetInstanceData + + + + + Sets the result's instanceData for proc to data. This reutrns non-zero for + succees and zero for failure. + int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data); + + + + + + + + Event Example + + /* 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 */ + } + + + Miscellaneous Functions As always, there are some functions that just don't fit anywhere. 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:00:56 -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.c strlcpy.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.c strlcpy.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:00:56 -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:00:57 -0000 *************** *** 1971,1982 **** --- 1971,2002 ---- * release data that is to be held for the life of the PGconn structure. * If a value ought to be cleared/freed during PQreset(), do it there not here. */ static void freePGconn(PGconn *conn) { + int i; + PGEventConnDestroy evt; + + /* Let the event procs cleanup their state data */ + for (i = 0; i < conn->nEvents; i++) + { + evt.conn = conn; + (void)conn->events[i].proc(PGEVT_CONNDESTROY, &evt, conn->events[i].passThrough); + if (conn->events[i].name) + free(conn->events[i].name); + } + + /* free the PGEvent array */ + if (conn->events) + { + free(conn->events); + conn->events = NULL; + conn->nEvents = conn->eventArrSize = 0; + } + if (conn->pghost) free(conn->pghost); if (conn->pghostaddr) free(conn->pghostaddr); if (conn->pgport) free(conn->pgport); *************** *** 2152,2165 **** PQreset(PGconn *conn) { if (conn) { closePGconn(conn); ! if (connectDBStart(conn)) ! (void) connectDBComplete(conn); } } /* * PQresetStart: --- 2172,2207 ---- PQreset(PGconn *conn) { if (conn) { closePGconn(conn); ! if (connectDBStart(conn) && connectDBComplete(conn)) ! { ! int i; ! PGEventConnReset evt; ! ! for (i = 0; i < conn->nEvents; i++) ! { ! evt.conn = conn; ! ! if (!conn->events[i].proc(PGEVT_CONNRESET, &evt, conn->events[i].passThrough)) ! { ! conn->status = CONNECTION_BAD; ! if (conn->events[i].name) ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("PGEventProc \"%s\" failed during PGEVT_CONNRESET event\n"), ! conn->events[i].name); ! else ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("PGEventProc \"addr:%p\" failed during PGEVT_CONNRESET event\n"), ! conn->events[i].proc); ! break; ! } ! } ! } } } /* * PQresetStart: *************** *** 2187,2199 **** * closes the existing connection and makes a new one */ PostgresPollingStatusType PQresetPoll(PGconn *conn) { if (conn) ! return PQconnectPoll(conn); return PGRES_POLLING_FAILED; } /* * PQcancelGet: get a PGcancel structure corresponding to a connection. --- 2229,2270 ---- * closes the existing connection and makes a new one */ PostgresPollingStatusType PQresetPoll(PGconn *conn) { if (conn) ! { ! PostgresPollingStatusType status = PQconnectPoll(conn); ! ! if (status == PGRES_POLLING_OK) ! { ! int i; ! PGEventConnReset evt; ! ! for (i = 0; i < conn->nEvents; i++) ! { ! evt.conn = conn; ! ! if (!conn->events[i].proc(PGEVT_CONNRESET, &evt, conn->events[i].passThrough)) ! { ! conn->status = CONNECTION_BAD; ! if (conn->events[i].name) ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("PGEventProc \"%s\" failed during PGEVT_CONNRESET event\n"), ! conn->events[i].name); ! else ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("PGEventProc \"addr:%p\" failed during PGEVT_CONNRESET event\n"), ! conn->events[i].proc); ! return PGRES_POLLING_FAILED; ! } ! } ! } ! ! return status; ! } return PGRES_POLLING_FAILED; } /* * PQcancelGet: get a PGcancel structure corresponding to a connection. Index: src/interfaces/libpq/fe-exec.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v retrieving revision 1.196 diff -C6 -r1.196 fe-exec.c *** src/interfaces/libpq/fe-exec.c 23 Jun 2008 21:10:49 -0000 1.196 --- src/interfaces/libpq/fe-exec.c 5 Sep 2008 19:00:57 -0000 *************** *** 60,72 **** int resultFormat); static void parseInput(PGconn *conn); static bool PQexecStart(PGconn *conn); static PGresult *PQexecFinish(PGconn *conn); static int PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target); ! /* ---------------- * Space management for PGresult. * * Formerly, libpq did a separate malloc() for each field of each tuple * returned by a query. This was remarkably expensive --- malloc/free --- 60,72 ---- int resultFormat); static void parseInput(PGconn *conn); static bool PQexecStart(PGconn *conn); static PGresult *PQexecFinish(PGconn *conn); static int PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target); ! static int check_field_number(const PGresult *res, int field_num); /* ---------------- * Space management for PGresult. * * Formerly, libpq did a separate malloc() for each field of each tuple * returned by a query. This was remarkably expensive --- malloc/free *************** *** 121,141 **** --- 121,171 ---- #define PGRESULT_DATA_BLOCKSIZE 2048 #define PGRESULT_ALIGN_BOUNDARY MAXIMUM_ALIGNOF /* from configure */ #define PGRESULT_BLOCK_OVERHEAD Max(sizeof(PGresult_data), PGRESULT_ALIGN_BOUNDARY) #define PGRESULT_SEP_ALLOC_THRESHOLD (PGRESULT_DATA_BLOCKSIZE / 2) + /* Does not duplicate the event instance data, sets this to NULL */ + static PGEvent * + dupEvents(PGEvent *events, int count) + { + int i; + PGEvent *newEvents; + + if (!events || count <= 0) + return NULL; + + newEvents = (PGEvent *) malloc(count * sizeof(PGEvent)); + if (!newEvents) + return NULL; + + memcpy(newEvents, events, count * sizeof(PGEvent)); + + /* NULL out the data pointer and deep copy name */ + for (i = 0; i < count; i++) + { + if (newEvents[i].name) + newEvents[i].name = strdup(newEvents[i].name); + newEvents[i].data = NULL; + } + + return newEvents; + } + /* * PQmakeEmptyPGresult * returns a newly allocated, initialized PGresult with given status. * If conn is not NULL and status indicates an error, the conn's * errorMessage is copied. * * Note this is exported --- you wouldn't think an application would need * to build its own PGresults, but this has proven useful in both libpgtcl * and the Perl5 interface, so maybe it's not so unreasonable. + * + * Updated April 2008 - If conn is not NULL, event states will be copied + * from the PGconn to the created PGresult. */ PGresult * PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) { PGresult *result; *************** *** 157,175 **** --- 187,220 ---- result->errMsg = NULL; result->errFields = NULL; result->null_field[0] = '\0'; result->curBlock = NULL; result->curOffset = 0; result->spaceLeft = 0; + result->nEvents = 0; + result->events = NULL; if (conn) { /* copy connection data we might need for operations on PGresult */ result->noticeHooks = conn->noticeHooks; result->client_encoding = conn->client_encoding; + /* copy events from connection */ + if (conn->nEvents > 0) + { + result->events = dupEvents(conn->events, conn->nEvents); + if (!result->events) + { + PQclear(result); + return NULL; + } + + result->nEvents = conn->nEvents; + } + /* consider copying conn's errorMessage */ switch (status) { case PGRES_EMPTY_QUERY: case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: *************** *** 192,203 **** --- 237,495 ---- 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 **** --- 641,674 ---- * 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); + if (res->events[i].name) + free(res->events[i].name); + } + + if (res->events) + { + free(res->events); + res->events = NULL; + res->nEvents = 0; + } + /* Free all the subsidiary blocks */ while ((block = res->curBlock) != NULL) { res->curBlock = block->next; free(block); } *************** *** 1192,1204 **** * memory). */ PGresult * PQgetResult(PGconn *conn) { ! PGresult *res; if (!conn) return NULL; /* Parse any available data, if our state permits. */ parseInput(conn); --- 1501,1513 ---- * memory). */ PGresult * PQgetResult(PGconn *conn) { ! PGresult *res=NULL; if (!conn) return NULL; /* Parse any available data, if our state permits. */ parseInput(conn); *************** *** 1267,1278 **** --- 1576,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]; + + if (res->events[i].name) + sprintf(msg, + "PGEventProc \"%s\" failed during PGEVT_RESULTCREATE event", + res->events[i].name); + else + sprintf(msg, + "PGEventProc \"addr:%p\" failed during PGEVT_RESULTCREATE event", + res->events[i].proc); + + pqSetResultError(res, msg); + res->resultStatus = PGRES_FATAL_ERROR; + break; + } + } + } + return res; } /* * PQexec Index: src/interfaces/libpq/libpq-fe.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-fe.h,v retrieving revision 1.142 diff -C6 -r1.142 libpq-fe.h *** src/interfaces/libpq/libpq-fe.h 19 Mar 2008 00:39:33 -0000 1.142 --- src/interfaces/libpq/libpq-fe.h 5 Sep 2008 19:00:57 -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:00:57 -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 #include #ifndef WIN32 #include #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 */