diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index deb052e5c4..2db369e906 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1,4 +1,4 @@ - + <application>libpq</application> - C Library @@ -2063,38 +2063,6 @@ PGresult *PQdescribePortal(PGconn *conn, const char *portalName); - - - - PQmakeEmptyPGresult - - PQmakeEmptyPGresult - - - - - - Constructs an empty PGresult object with the given status. - - PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status); - - - - - This is libpq's internal function to allocate and - initialize an empty PGresult object. This - function returns NULL if memory could not be allocated. It is - exported because some applications find it useful to generate result - objects (particularly objects with error status) themselves. If - conn is not null and status - indicates an error, the current error message of the specified - connection is copied into the PGresult. - Note that PQclear should eventually be called - on the object, just as with a PGresult - returned by libpq itself. - - - @@ -4598,6 +4566,170 @@ char *pg_encoding_to_char(int encoding_id); + + + + PQmakeEmptyPGresult + + PQmakeEmptyPGresult + + + + + + Constructs an empty PGresult object with the given status. + + PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status); + + + + + This is libpq's internal function to allocate and + initialize an empty PGresult object. This + function returns NULL if memory could not be allocated. It is + exported because some applications find it useful to generate result + objects (particularly objects with error status) themselves. If + conn is not null and status + indicates an error, the current error message of the specified + connection is copied into the PGresult. + Also, if conn is not null, any event handlers + registered in the connection are copied into the + PGresult (but they don't get + PGEVT_RESULTCREATE calls). + 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. The copy is + not linked to the source result in any way and + PQclear must be called when the copy is no longer + needed. If the function fails, NULL is returned. + + + PGresult *PQcopyResult(const PGresult *src, int flags); + + + + + This is not intended to make an exact copy. The returned result is + always put into PGRES_TUPLES_OK status, and does not + copy any error message in the source. (It does copy the command status + string, however.) The flags argument determines + what else is copied. It is a bitwise OR of several flags. + PG_COPYRES_ATTRS specifies copying the source + result's attributes (column definitions). + PG_COPYRES_TUPLES specifies copying the source + result's tuples. (This implies copying the attributes, too.) + PG_COPYRES_NOTICEHOOKS specifies + copying the source result's notify hooks. + PG_COPYRES_EVENTS specifies copying the source + result's events. (But any instance data associated with the source + is not copied.) + + + + + + + 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. + If the attDescs pointer is 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. However, the tup_num argument must be + less than or equal to PQntuples, meaning this + function can only grow the tuples array one tuple at a time. But any + field of any existing tuple can be modified in any order. If a value at + field_num already exists, 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 the result's private storage, + thus is no longer needed after the function + returns. If the function fails, the return value is zero. If the + function succeeds, the return value is non-zero. + + + + + + + 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. The result is + guaranteed to be adequately aligned for any type of data, + just as for malloc. + + + + @@ -4711,6 +4843,551 @@ defaultNoticeProcessor(void *arg, const char *message) + + Event System + + + libpq's event system is designed to notify + registered event handlers about interesting + libpq events, such as the creation or + destruction of PGconn and + PGresult objects. A principal use case is that + this allows applications to associate their own data with a + PGconn or PGresult + and ensure that that data is freed at an appropriate time. + + + + Each registered event handler is associated with two pieces of data, + known to libpq only as opaque void * + pointers. There is a passthrough pointer that is provided + by the application when the event handler is registered with a + PGconn. The passthrough pointer never changes for the + life of the PGconn and all PGresults + generated from it; so if used, it must point to long-lived data. + In addition there is an instance data pointer, which starts + out NULL in every PGconn and PGresult. + This pointer can be manipulated using the + PQinstanceData, + PQsetInstanceData, + PQresultInstanceData and + PQsetResultInstanceData functions. Note that + unlike the passthrough pointer, instance data of a PGconn + is not automatically inherited by PGresults created from + it. libpq does not know what passthrough + and instance data pointers point to (if anything) and will never attempt + to free them — that is the responsibility of the event handler. + + + + Event Types + + + The enum PGEventId names the types of events handled by + the event system. All its values have names beginning with + PGEVT. For each event type, there is a corresponding + event info structure that carries the parameters passed to the event + handlers. The event types are: + + + + + 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 event handler 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 cast 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. + + + + + + PGEVT_CONNRESET + + + The connection reset event is fired on completion of + 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; +} PGEventConnReset; + + + When a PGEVT_CONNRESET event is received, the + evtInfo pointer should be cast to a + PGEventConnReset *. Although the contained + PGconn was just reset, all event data remains + unchanged. This event should be used to reset/reload/requery any + associated instanceData. + + + + + + PGEVT_CONNDESTROY + + + The connection destroy event is fired in response to + PQfinish. It is the event procedure's + responsibility to properly clean up its event data as libpq has no + ability to manage this memory. Failure to clean up will lead + to memory leaks. + + +typedef struct +{ + const PGconn *conn; +} PGEventConnDestroy; + + + When a PGEVT_CONNDESTROY event is received, the + evtInfo pointer should be cast to a + PGEventConnDestroy *. This event is fired + prior to PQfinish performing any other 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 creation event is fired in response to any query execution + function that generates a result, including + PQgetResult. This event will only be fired after + the result has been created successfully. + + +typedef struct +{ + const PGconn *conn; + PGresult *result; +} PGEventResultCreate; + + + When a PGEVT_RESULTCREATE event is received, the + evtInfo pointer should be cast to a + PGEventResultCreate *. The + conn is 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. The event procedure must not try to + PQclear the result object for itself. + + + + + + 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 cast to a + PGEventResultCopy *. The + src result is what was 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 clean up its event data as libpq has no + ability to manage this memory. Failure to clean up will lead + to memory leaks. + + +typedef struct +{ + const PGresult *result; +} PGEventResultDestroy; + + + When a PGEVT_RESULTDESTROY event is received, the + evtInfo pointer should be cast to a + PGEventResultDestroy *. This event is fired + prior to PQclear performing any other 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 + + + + + + PGEventProc is a typedef for a pointer to an + event procedure, that is, the user callback function that receives + events from libpq. The signature of an event procedure must be + + +int eventproc(PGEventId evtId, void *evtInfo, void *passThrough) + + + The evtId parameter indicates which + PGEVT event occurred. The + evtInfo pointer must be cast to the appropriate + structure type to obtain further information about the event. + The passThrough parameter is the pointer + provided to PQregisterEventProc when the event + procedure was registered. The function should return a non-zero value + if it succeeds and zero if it fails. + + + + A particular event procedure can be registered only once in any + PGconn. This is because the address of the procedure + is used as a lookup key to identify the associated instance data. + + + + + + + + Event Support Functions + + + + + PQregisterEventProc + + PQregisterEventProc + + + + + + Registers an event callback procedure with libpq. + + + int PQregisterEventProc(PGconn *conn, PGEventProc proc, + const char *name, void *passThrough); + + + + + An event procedure must be registered once on each + PGconn you want to receive events about. There is no + limit, other than memory, on the number of event procedures that + can be registered with a connection. The function returns a non-zero + value if it succeeds and zero if it fails. + + + + 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 refer to the event procedure in error messages. + This value cannot be NULL or a zero-length string. The name string is + copied into the PGconn, so what is passed need not be + long-lived. The passThrough pointer is passed + to the proc whenever an event occurs. This + argument can be NULL. + + + + + + + PQsetInstanceData + + PQsetInstanceData + + + + + Sets the conn's instanceData for proc to data. This returns non-zero + for success and zero for failure. (Failure is only possible if + the proc has not been properly registered in the conn.) + + + int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data); + + + + + + + + PQinstanceData + + PQinstanceData + + + + + Returns the conn's instanceData associated with proc, or NULL + if there is none. + + + void *PQinstanceData(const PGconn *conn, PGEventProc proc); + + + + + + + + PQresultSetInstanceData + + PQresultSetInstanceData + + + + + Sets the result's instanceData for proc to data. This returns non-zero + for success and zero for failure. (Failure is only possible if the + proc has not been properly registered in the result.) + + + int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data); + + + + + + + + PQresultInstanceData + + PQresultInstanceData + + + + + Returns the result's instanceData associated with proc, or NULL + if there is none. + + + void *PQresultInstanceData(const PGresult *res, PGEventProc proc); + + + + + + + + + Event Example + + + Here is a skeleton example of managing private data associated with + libpq connections and results. + + + +/* required header for libpq events (note: includes libpq-fe.h) */ +#include <libpq-events.h> + +/* The instanceData */ +typedef struct +{ + int n; + char *str; +} mydata; + +/* PGEventProc */ +static int myEventProc(PGEventId evtId, void *evtInfo, void *passThrough); + +int +main(void) +{ + mydata *data; + PGresult *res; + PGconn *conn = PQconnectdb("dbname = postgres"); + + if (PQstatus(conn) != CONNECTION_OK) + { + fprintf(stderr, "Connection to database failed: %s", + PQerrorMessage(conn)); + PQfinish(conn); + return 1; + } + + /* called once on any connection that should receive events. + * Sends a PGEVT_REGISTER to myEventProc. + */ + if (!PQregisterEventProc(conn, myEventProc, "mydata_proc", NULL)) + { + fprintf(stderr, "Cannot register PGEventProc\n"); + PQfinish(conn); + return 1; + } + + /* conn instanceData is available */ + data = PQinstanceData(conn, myEventProc); + + /* Sends a PGEVT_RESULTCREATE to myEventProc */ + res = PQexec(conn, "SELECT 1 + 1"); + + /* result instanceData is available */ + data = PQresultInstanceData(res, myEventProc); + + /* If PG_COPYRES_EVENTS is used, sends a PGEVT_RESULTCOPY to myEventProc */ + res_copy = PQcopyResult(res, PG_COPYRES_TUPLES | PG_COPYRES_EVENTS); + + /* result instanceData is available if PG_COPYRES_EVENTS was + * used during the PQcopyResult call. + */ + data = PQresultInstanceData(res_copy, myEventProc); + + /* Both clears send a PGEVT_RESULTDESTROY to myEventProc */ + PQclear(res); + PQclear(res_copy); + + /* Sends a PGEVT_CONNDESTROY to myEventProc */ + PQfinish(conn); + + return 0; +} + +static int +myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) +{ + switch (evtId) + { + case PGEVT_REGISTER: + { + PGEventRegister *e = (PGEventRegister *)evtInfo; + mydata *data = get_mydata(e->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 */ +} + + + + Environment Variables @@ -5263,7 +5940,7 @@ defaultNoticeProcessor(void *arg, const char *message) to inside libpq), you can use PQinitSSL(int) to tell libpq that the SSL library has already been initialized by your - application. + application. See diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index 62885fbb5e..7bace86345 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -5,7 +5,7 @@ # Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group # Portions Copyright (c) 1994, Regents of the University of California # -# $PostgreSQL: pgsql/src/interfaces/libpq/Makefile,v 1.166 2008/04/16 14:19:56 adunstan Exp $ +# $PostgreSQL: pgsql/src/interfaces/libpq/Makefile,v 1.167 2008/09/17 04:31:08 tgl Exp $ # #------------------------------------------------------------------------- @@ -32,6 +32,7 @@ LIBS := $(LIBS:-lpgport=) OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \ fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \ + libpq-events.o \ md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o \ $(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o win32error.o, $(LIBOBJS)) @@ -106,6 +107,7 @@ $(top_builddir)/src/port/pg_config_paths.h: install: all installdirs install-lib $(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)' + $(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)' $(INSTALL_DATA) $(srcdir)/libpq-int.h '$(DESTDIR)$(includedir_internal)' $(INSTALL_DATA) $(srcdir)/pqexpbuffer.h '$(DESTDIR)$(includedir_internal)' $(INSTALL_DATA) $(srcdir)/pg_service.conf.sample '$(DESTDIR)$(datadir)/pg_service.conf.sample' @@ -114,7 +116,11 @@ installdirs: installdirs-lib $(mkinstalldirs) '$(DESTDIR)$(includedir)' '$(DESTDIR)$(includedir_internal)' uninstall: uninstall-lib - rm -f '$(DESTDIR)$(includedir)/libpq-fe.h' '$(DESTDIR)$(includedir_internal)/libpq-int.h' '$(DESTDIR)$(includedir_internal)/pqexpbuffer.h' '$(DESTDIR)$(datadir)/pg_service.conf.sample' + rm -f '$(DESTDIR)$(includedir)/libpq-fe.h' + rm -f '$(DESTDIR)$(includedir)/libpq-events.h' + rm -f '$(DESTDIR)$(includedir_internal)/libpq-int.h' + rm -f '$(DESTDIR)$(includedir_internal)/pqexpbuffer.h' + rm -f '$(DESTDIR)$(datadir)/pg_service.conf.sample' clean distclean: clean-lib rm -f $(OBJS) pg_config_paths.h crypt.c getaddrinfo.c inet_aton.c noblock.c open.c pgstrcasecmp.c snprintf.c strerror.c strlcpy.c thread.c md5.c ip.c encnames.c wchar.c win32error.c pgsleep.c pthread.h libpq.rc diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index f8809f841e..c720efce4b 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -1,4 +1,4 @@ -# $PostgreSQL: pgsql/src/interfaces/libpq/exports.txt,v 1.19 2008/03/19 00:39:33 ishii Exp $ +# $PostgreSQL: pgsql/src/interfaces/libpq/exports.txt,v 1.20 2008/09/17 04:31:08 tgl Exp $ # Functions to be exported by libpq DLLs PQconnectdb 1 PQsetdbLogin 2 @@ -140,4 +140,13 @@ lo_truncate 137 PQconnectionUsedPassword 138 pg_valid_server_encoding_id 139 PQconnectionNeedsPassword 140 -lo_import_with_oid 141 +lo_import_with_oid 141 +PQcopyResult 142 +PQsetResultAttrs 143 +PQsetvalue 144 +PQresultAlloc 145 +PQregisterEventProc 146 +PQinstanceData 147 +PQsetInstanceData 148 +PQresultInstanceData 149 +PQresultSetInstanceData 150 diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e687c1558..7e77c9a5c7 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.359 2008/05/29 22:02:44 tgl Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.360 2008/09/17 04:31:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1974,6 +1974,21 @@ makeEmptyPGconn(void) static void freePGconn(PGconn *conn) { + int i; + + /* let any event procs clean up their state data */ + for (i = 0; i < conn->nEvents; i++) + { + PGEventConnDestroy evt; + + evt.conn = conn; + (void) conn->events[i].proc(PGEVT_CONNDESTROY, &evt, + conn->events[i].passThrough); + free(conn->events[i].name); + } + + if (conn->events) + free(conn->events); if (conn->pghost) free(conn->pghost); if (conn->pghostaddr) @@ -2155,8 +2170,30 @@ PQreset(PGconn *conn) { closePGconn(conn); - if (connectDBStart(conn)) - (void) connectDBComplete(conn); + if (connectDBStart(conn) && connectDBComplete(conn)) + { + /* + * Notify event procs of successful reset. We treat an event + * proc failure as disabling the connection ... good idea? + */ + int i; + + for (i = 0; i < conn->nEvents; i++) + { + PGEventConnReset evt; + + evt.conn = conn; + if (!conn->events[i].proc(PGEVT_CONNRESET, &evt, + conn->events[i].passThrough)) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("PGEventProc \"%s\" failed during PGEVT_CONNRESET event\n"), + conn->events[i].name); + break; + } + } + } } } @@ -2190,7 +2227,36 @@ PostgresPollingStatusType PQresetPoll(PGconn *conn) { if (conn) - return PQconnectPoll(conn); + { + PostgresPollingStatusType status = PQconnectPoll(conn); + + if (status == PGRES_POLLING_OK) + { + /* + * Notify event procs of successful reset. We treat an event + * proc failure as disabling the connection ... good idea? + */ + int i; + + for (i = 0; i < conn->nEvents; i++) + { + PGEventConnReset evt; + + evt.conn = conn; + if (!conn->events[i].proc(PGEVT_CONNRESET, &evt, + conn->events[i].passThrough)) + { + conn->status = CONNECTION_BAD; + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("PGEventProc \"%s\" failed during PGEVT_CONNRESET event\n"), + conn->events[i].name); + return PGRES_POLLING_FAILED; + } + } + } + + return status; + } return PGRES_POLLING_FAILED; } diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 149a0b73f6..7db303ce00 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/interfaces/libpq/fe-exec.c,v 1.197 2008/09/10 17:01:07 tgl Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/fe-exec.c,v 1.198 2008/09/17 04:31:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -48,6 +48,7 @@ static int static_client_encoding = PG_SQL_ASCII; static bool static_std_strings = false; +static PGEvent *dupEvents(PGEvent *events, int count); static bool PQsendQueryStart(PGconn *conn); static int PQsendQueryGuts(PGconn *conn, const char *command, @@ -63,6 +64,7 @@ static bool PQexecStart(PGconn *conn); static PGresult *PQexecFinish(PGconn *conn); static int PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target); +static int check_field_number(const PGresult *res, int field_num); /* ---------------- @@ -128,13 +130,8 @@ static int PQsendDescribe(PGconn *conn, char desc_type, * PQmakeEmptyPGresult * returns a newly allocated, initialized PGresult with given status. * If conn is not NULL and status indicates an error, the conn's - * errorMessage is copied. - * - * Note this is exported --- you wouldn't think an application would need - * to build its own PGresults, but this has proven useful in both libpgtcl - * and the Perl5 interface, so maybe it's not so unreasonable. + * errorMessage is copied. Also, any PGEvents are copied from the conn. */ - PGresult * PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) { @@ -154,6 +151,8 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) result->resultStatus = status; result->cmdStatus[0] = '\0'; result->binary = 0; + result->events = NULL; + result->nEvents = 0; result->errMsg = NULL; result->errFields = NULL; result->null_field[0] = '\0'; @@ -181,6 +180,18 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) pqSetResultError(result, conn->errorMessage.data); break; } + + /* copy events last; result must be valid if we need to PQclear */ + if (conn->nEvents > 0) + { + result->events = dupEvents(conn->events, conn->nEvents); + if (!result->events) + { + PQclear(result); + return NULL; + } + result->nEvents = conn->nEvents; + } } else { @@ -195,6 +206,301 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) return result; } +/* + * PQsetResultAttrs + * + * Set the attributes for a given result. This function fails if there are + * already attributes contained in the provided result. The call is + * ignored if numAttributes is is zero or attDescs is NULL. If the + * function fails, it returns zero. If the function succeeds, it + * returns a non-zero value. + */ +int +PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs) +{ + int i; + + /* If attrs already exist, they cannot be overwritten. */ + if (!res || res->numAttributes > 0) + return FALSE; + + /* ignore no-op request */ + if (numAttributes <= 0 || !attDescs) + return TRUE; + + res->attDescs = (PGresAttDesc *) + PQresultAlloc(res, numAttributes * sizeof(PGresAttDesc)); + + if (!res->attDescs) + return FALSE; + + res->numAttributes = numAttributes; + memcpy(res->attDescs, attDescs, numAttributes * sizeof(PGresAttDesc)); + + /* deep-copy the attribute names, and determine format */ + res->binary = 1; + for (i = 0; i < res->numAttributes; i++) + { + if (res->attDescs[i].name) + res->attDescs[i].name = pqResultStrdup(res, res->attDescs[i].name); + else + res->attDescs[i].name = res->null_field; + + if (!res->attDescs[i].name) + return FALSE; + + if (res->attDescs[i].format == 0) + res->binary = 0; + } + + return TRUE; +} + +/* + * PQcopyResult + * + * Returns a deep copy of the provided 'src' PGresult, which cannot be NULL. + * The 'flags' argument controls which portions of the result will or will + * NOT be copied. The created result is always put into the + * PGRES_TUPLES_OK status. The source result error message is not copied, + * although cmdStatus is. + * + * To set custom attributes, use PQsetResultAttrs. That function requires + * that there are no attrs contained in the result, so to use that + * function you cannot use the PG_COPYRES_ATTRS or PG_COPYRES_TUPLES + * options with this function. + * + * Options: + * PG_COPYRES_ATTRS - Copy the source result's attributes + * + * PG_COPYRES_TUPLES - Copy the source result's tuples. This implies + * copying the attrs, seeeing how the attrs are needed by the tuples. + * + * PG_COPYRES_EVENTS - Copy the source result's events. + * + * PG_COPYRES_NOTICEHOOKS - Copy the source result's notice hooks. + */ +PGresult * +PQcopyResult(const PGresult *src, int flags) +{ + PGresult *dest; + int i; + + if (!src) + return NULL; + + dest = PQmakeEmptyPGresult(NULL, PGRES_TUPLES_OK); + if (!dest) + return NULL; + + /* Always copy these over. Is cmdStatus really useful here? */ + dest->client_encoding = src->client_encoding; + strcpy(dest->cmdStatus, src->cmdStatus); + + /* Wants attrs? */ + if (flags & (PG_COPYRES_ATTRS | PG_COPYRES_TUPLES)) + { + if (!PQsetResultAttrs(dest, src->numAttributes, src->attDescs)) + { + PQclear(dest); + return NULL; + } + } + + /* Wants to copy tuples? */ + if (flags & PG_COPYRES_TUPLES) + { + int tup, field; + + for (tup = 0; tup < src->ntups; tup++) + { + for (field = 0; field < src->numAttributes; field++) + { + if (!PQsetvalue(dest, tup, field, + src->tuples[tup][field].value, + src->tuples[tup][field].len)) + { + PQclear(dest); + return NULL; + } + } + } + } + + /* Wants to copy notice hooks? */ + if (flags & PG_COPYRES_NOTICEHOOKS) + dest->noticeHooks = src->noticeHooks; + + /* + * Wants to copy PGEvents? NB: this should be last, as we don't want + * to trigger RESULTDESTROY events on a useless PGresult. + */ + if ((flags & PG_COPYRES_EVENTS) && src->nEvents > 0) + { + dest->events = dupEvents(src->events, src->nEvents); + if (!dest->events) + { + PQclear(dest); + return NULL; + } + dest->nEvents = src->nEvents; + } + + /* Okay, trigger PGEVT_RESULTCOPY event */ + for (i = 0; i < dest->nEvents; i++) + { + PGEventResultCopy evt; + + evt.src = src; + evt.dest = dest; + if (!dest->events[i].proc(PGEVT_RESULTCOPY, &evt, + dest->events[i].passThrough)) + { + PQclear(dest); + return NULL; + } + } + + return dest; +} + +/* + * Copy an array of PGEvents (with no extra space for more) + * Does not duplicate the event instance data, sets this to NULL + */ +static PGEvent * +dupEvents(PGEvent *events, int count) +{ + PGEvent *newEvents; + int i; + + if (!events || count <= 0) + return NULL; + + newEvents = (PGEvent *) malloc(count * sizeof(PGEvent)); + if (!newEvents) + return NULL; + + memcpy(newEvents, events, count * sizeof(PGEvent)); + + /* NULL out the data pointers and deep copy names */ + for (i = 0; i < count; i++) + { + newEvents[i].data = NULL; + newEvents[i].name = strdup(newEvents[i].name); + if (!newEvents[i].name) + { + while (--i >= 0) + free(newEvents[i].name); + free(newEvents); + return NULL; + } + } + + return newEvents; +} + + +/* + * Sets the value for a tuple field. The tup_num must be less than or + * equal to PQntuples(res). If it is equal, a new tuple is created and + * added to the result. + * Returns a non-zero value for success and zero for failure. + */ +int +PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len) +{ + PGresAttValue *attval; + + if (!check_field_number(res, field_num)) + return FALSE; + + /* Invalid tup_num, must be <= ntups */ + if (tup_num < 0 || tup_num > res->ntups) + return FALSE; + + /* need to grow the tuple table? */ + if (res->ntups >= res->tupArrSize) + { + int n = res->tupArrSize ? res->tupArrSize * 2 : 128; + PGresAttValue **tups; + + if (res->tuples) + tups = (PGresAttValue **) realloc(res->tuples, n * sizeof(PGresAttValue *)); + else + tups = (PGresAttValue **) malloc(n * sizeof(PGresAttValue *)); + + if (!tups) + return FALSE; + + memset(tups + res->tupArrSize, 0, + (n - res->tupArrSize) * sizeof(PGresAttValue *)); + res->tuples = tups; + res->tupArrSize = n; + } + + /* need to allocate a new tuple? */ + if (tup_num == res->ntups && !res->tuples[tup_num]) + { + PGresAttValue *tup; + int i; + + tup = (PGresAttValue *) + pqResultAlloc(res, res->numAttributes * sizeof(PGresAttValue), + TRUE); + + if (!tup) + return FALSE; + + /* initialize each column to NULL */ + for (i = 0; i < res->numAttributes; i++) + { + tup[i].len = NULL_LEN; + tup[i].value = res->null_field; + } + + res->tuples[tup_num] = tup; + res->ntups++; + } + + attval = &res->tuples[tup_num][field_num]; + + /* treat either NULL_LEN or NULL value pointer as a NULL field */ + if (len == NULL_LEN || value == NULL) + { + attval->len = NULL_LEN; + attval->value = res->null_field; + } + else if (len <= 0) + { + attval->len = 0; + attval->value = res->null_field; + } + else + { + attval->value = (char *) pqResultAlloc(res, len + 1, TRUE); + if (!attval->value) + return FALSE; + attval->len = len; + memcpy(attval->value, value, len); + attval->value[len] = '\0'; + } + + return TRUE; +} + +/* + * pqResultAlloc - exported routine to allocate local storage in a PGresult. + * + * We force all such allocations to be maxaligned, since we don't know + * whether the value might be binary. + */ +void * +PQresultAlloc(PGresult *res, size_t nBytes) +{ + return pqResultAlloc(res, nBytes, TRUE); +} + /* * pqResultAlloc - * Allocate subsidiary storage for a PGresult. @@ -353,10 +659,24 @@ void PQclear(PGresult *res) { PGresult_data *block; + int i; if (!res) return; + for (i = 0; i < res->nEvents; i++) + { + PGEventResultDestroy evt; + + evt.result = res; + (void) res->events[i].proc(PGEVT_RESULTDESTROY, &evt, + res->events[i].passThrough); + free(res->events[i].name); + } + + if (res->events) + free(res->events); + /* Free all the subsidiary blocks */ while ((block = res->curBlock) != NULL) { @@ -373,6 +693,8 @@ PQclear(PGresult *res) res->tuples = NULL; res->paramDescs = NULL; res->errFields = NULL; + res->events = NULL; + res->nEvents = 0; /* res->curBlock was zeroed out earlier */ /* Free the PGresult structure itself */ @@ -1270,6 +1592,29 @@ PQgetResult(PGconn *conn) break; } + if (res) + { + int i; + + for (i = 0; i < res->nEvents; i++) + { + PGEventResultCreate evt; + + evt.conn = conn; + evt.result = res; + if (!res->events[i].proc(PGEVT_RESULTCREATE, &evt, + res->events[i].passThrough)) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("PGEventProc \"%s\" failed during PGEVT_RESULTCREATE event\n"), + res->events[i].name); + pqSetResultError(res, conn->errorMessage.data); + res->resultStatus = PGRES_FATAL_ERROR; + break; + } + } + } + return res; } diff --git a/src/interfaces/libpq/libpq-events.c b/src/interfaces/libpq/libpq-events.c new file mode 100644 index 0000000000..7d3d1cb26c --- /dev/null +++ b/src/interfaces/libpq/libpq-events.c @@ -0,0 +1,176 @@ +/*------------------------------------------------------------------------- + * + * libpq-events.c + * functions for supporting the libpq "events" API + * + * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-events.c,v 1.1 2008/09/17 04:31:08 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres_fe.h" + +#include "libpq-fe.h" +#include "libpq-int.h" + + +/* + * Registers an event proc with the given PGconn. + * + * The same proc can't be registered more than once in a PGconn. This + * restriction is required because we use the proc address to identify + * the event for purposes such as PQinstanceData(). + * + * The name argument is used within error messages to aid in debugging. + * A name must be supplied, but it needn't be unique. The string is + * copied, so the passed value needn't be long-lived. + * + * The passThrough argument is an application specific pointer and can be set + * to NULL if not required. It is passed through to the event proc whenever + * the event proc is called, and is not otherwise touched by libpq. + * + * The function returns a non-zero if successful. If the function fails, + * zero is returned. + */ +int +PQregisterEventProc(PGconn *conn, PGEventProc proc, + const char *name, void *passThrough) +{ + int i; + PGEventRegister regevt; + + if (!proc || !conn || !name || !*name) + return FALSE; /* bad arguments */ + + for (i = 0; i < conn->nEvents; i++) + { + if (conn->events[i].proc == proc) + return FALSE; /* already registered */ + } + + if (conn->nEvents >= conn->eventArraySize) + { + PGEvent *e; + int newSize; + + newSize = conn->eventArraySize ? conn->eventArraySize * 2 : 8; + if (conn->events) + e = (PGEvent *) realloc(conn->events, newSize * sizeof(PGEvent)); + else + e = (PGEvent *) malloc(newSize * sizeof(PGEvent)); + + if (!e) + return FALSE; + + conn->eventArraySize = newSize; + conn->events = e; + } + + conn->events[conn->nEvents].proc = proc; + conn->events[conn->nEvents].name = strdup(name); + if (!conn->events[conn->nEvents].name) + return FALSE; + conn->events[conn->nEvents].passThrough = passThrough; + conn->events[conn->nEvents].data = NULL; + conn->nEvents++; + + regevt.conn = conn; + if (!proc(PGEVT_REGISTER, ®evt, passThrough)) + { + conn->nEvents--; + free(conn->events[conn->nEvents].name); + return FALSE; + } + + return TRUE; +} + +/* + * Set some "instance data" for an event within a PGconn. + * Returns nonzero on success, zero on failure. + */ +int +PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data) +{ + int i; + + if (!conn || !proc) + return FALSE; + + for (i = 0; i < conn->nEvents; i++) + { + if (conn->events[i].proc == proc) + { + conn->events[i].data = data; + return TRUE; + } + } + + return FALSE; +} + +/* + * Obtain the "instance data", if any, for the event. + */ +void * +PQinstanceData(const PGconn *conn, PGEventProc proc) +{ + int i; + + if (!conn || !proc) + return NULL; + + for (i = 0; i < conn->nEvents; i++) + { + if (conn->events[i].proc == proc) + return conn->events[i].data; + } + + return NULL; +} + +/* + * Set some "instance data" for an event within a PGresult. + * Returns nonzero on success, zero on failure. + */ +int +PQresultSetInstanceData(PGresult *result, PGEventProc proc, void *data) +{ + int i; + + if (!result || !proc) + return FALSE; + + for (i = 0; i < result->nEvents; i++) + { + if (result->events[i].proc == proc) + { + result->events[i].data = data; + return TRUE; + } + } + + return FALSE; +} + +/* + * Obtain the "instance data", if any, for the event. + */ +void * +PQresultInstanceData(const PGresult *result, PGEventProc proc) +{ + int i; + + if (!result || !proc) + return NULL; + + for (i = 0; i < result->nEvents; i++) + if (result->events[i].proc == proc) + return result->events[i].data; + + return NULL; +} diff --git a/src/interfaces/libpq/libpq-events.h b/src/interfaces/libpq/libpq-events.h new file mode 100644 index 0000000000..33e2d5b046 --- /dev/null +++ b/src/interfaces/libpq/libpq-events.h @@ -0,0 +1,91 @@ +/*------------------------------------------------------------------------- + * + * libpq-events.h + * This file contains definitions that are useful to applications + * that invoke the libpq "events" API, but are not interesting to + * ordinary users of libpq. + * + * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-events.h,v 1.1 2008/09/17 04:31:08 tgl Exp $ + * + *------------------------------------------------------------------------- + */ + +#ifndef LIBPQ_EVENTS_H +#define LIBPQ_EVENTS_H + +#include "libpq-fe.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Callback Event Ids */ +typedef enum +{ + PGEVT_REGISTER, + PGEVT_CONNRESET, + PGEVT_CONNDESTROY, + PGEVT_RESULTCREATE, + PGEVT_RESULTCOPY, + PGEVT_RESULTDESTROY +} PGEventId; + +typedef struct +{ + const PGconn *conn; +} PGEventRegister; + +typedef struct +{ + const PGconn *conn; +} PGEventConnReset; + +typedef struct +{ + const PGconn *conn; +} PGEventConnDestroy; + +typedef struct +{ + const PGconn *conn; + PGresult *result; +} PGEventResultCreate; + +typedef struct +{ + const PGresult *src; + PGresult *dest; +} PGEventResultCopy; + +typedef struct +{ + const PGresult *result; +} PGEventResultDestroy; + +typedef int (*PGEventProc) (PGEventId evtId, void *evtInfo, void *passThrough); + +/* Registers an event proc with the given PGconn. */ +extern int PQregisterEventProc(PGconn *conn, PGEventProc proc, + const char *name, void *passThrough); + +/* Sets the PGconn instance data for the provided proc to data. */ +extern int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data); + +/* Gets the PGconn instance data for the provided proc. */ +extern void *PQinstanceData(const PGconn *conn, PGEventProc proc); + +/* Sets the PGresult instance data for the provided proc to data. */ +extern int PQresultSetInstanceData(PGresult *result, PGEventProc proc, void *data); + +/* Gets the PGresult instance data for the provided proc. */ +extern void *PQresultInstanceData(const PGresult *result, PGEventProc proc); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBPQ_EVENTS_H */ diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 53d79b059f..f923b96840 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.142 2008/03/19 00:39:33 ishii Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.143 2008/09/17 04:31:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -28,6 +28,14 @@ extern "C" */ #include "postgres_ext.h" +/* + * Option flags for PQcopyResult + */ +#define PG_COPYRES_ATTRS 0x01 +#define PG_COPYRES_TUPLES 0x02 /* Implies PG_COPYRES_ATTRS */ +#define PG_COPYRES_EVENTS 0x04 +#define PG_COPYRES_NOTICEHOOKS 0x08 + /* Application-visible enum types */ typedef enum @@ -192,6 +200,21 @@ typedef struct } u; } PQArgBlock; +/* ---------------- + * PGresAttDesc -- Data about a single attribute (column) of a query result + * ---------------- + */ +typedef struct pgresAttDesc +{ + char *name; /* column name */ + Oid tableid; /* source table, if known */ + int columnid; /* source column, if known */ + int format; /* format code for value (text/binary) */ + Oid typid; /* type id */ + int typlen; /* type size */ + int atttypmod; /* type-specific modifier info */ +} PGresAttDesc; + /* ---------------- * Exported functions of libpq * ---------------- @@ -430,13 +453,12 @@ extern void PQfreemem(void *ptr); /* Note: depending on this is deprecated; use PQconnectionNeedsPassword(). */ #define PQnoPasswordSupplied "fe_sendauth: no password supplied\n" -/* - * Make an empty PGresult with given status (some apps find this - * useful). If conn is not NULL and status indicates an error, the - * conn's errorMessage is copied. - */ +/* Create and manipulate PGresults */ extern PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status); - +extern PGresult *PQcopyResult(const PGresult *src, int flags); +extern int PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs); +extern void *PQresultAlloc(PGresult *res, size_t nBytes); +extern int PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len); /* Quoting strings before inclusion in queries. */ extern size_t PQescapeStringConn(PGconn *conn, diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index fd94952f18..fd29c09214 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -12,7 +12,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.131 2008/05/29 22:02:44 tgl Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.132 2008/09/17 04:31:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -22,6 +22,7 @@ /* We assume libpq-fe.h has already been included. */ #include "postgres_fe.h" +#include "libpq-events.h" #include #include @@ -100,19 +101,6 @@ union pgresult_data char space[1]; /* dummy for accessing block as bytes */ }; -/* Data about a single attribute (column) of a query result */ - -typedef struct pgresAttDesc -{ - char *name; /* column name */ - Oid tableid; /* source table, if known */ - int columnid; /* source column, if known */ - int format; /* format code for value (text/binary) */ - Oid typid; /* type id */ - int typlen; /* type size */ - int atttypmod; /* type-specific modifier info */ -} PGresAttDesc; - /* Data about a single parameter of a prepared statement */ typedef struct pgresParamDesc { @@ -162,6 +150,14 @@ typedef struct void *noticeProcArg; } PGNoticeHooks; +typedef struct PGEvent +{ + PGEventProc proc; /* the function to call on events */ + char *name; /* used only for error messages */ + void *passThrough; /* pointer supplied at registration time */ + void *data; /* optional state (instance) data */ +} PGEvent; + struct pg_result { int ntups; @@ -182,6 +178,8 @@ struct pg_result * on the PGresult don't have to reference the PGconn. */ PGNoticeHooks noticeHooks; + PGEvent *events; + int nEvents; int client_encoding; /* encoding id */ /* @@ -303,6 +301,11 @@ struct pg_conn /* Callback procedures for notice message processing */ PGNoticeHooks noticeHooks; + /* Event procs registered via PQregisterEventProc */ + PGEvent *events; /* expandable array of event data */ + int nEvents; /* number of active events */ + int eventArraySize; /* allocated array size */ + /* Status indicators */ ConnStatusType status; PGAsyncStatusType asyncStatus; diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm index 4355ec9a28..dcbe5b5e01 100644 --- a/src/tools/msvc/Install.pm +++ b/src/tools/msvc/Install.pm @@ -3,7 +3,7 @@ package Install; # # Package that provides 'make install' functionality for msvc builds # -# $PostgreSQL: pgsql/src/tools/msvc/Install.pm,v 1.30 2008/09/05 16:54:39 momjian Exp $ +# $PostgreSQL: pgsql/src/tools/msvc/Install.pm,v 1.31 2008/09/17 04:31:08 tgl Exp $ # use strict; use warnings; @@ -393,7 +393,9 @@ sub CopyIncludeFiles lcopy('src/include/libpq/libpq-fs.h', $target . '/include/libpq/') || croak 'Could not copy libpq-fs.h'; - CopyFiles('Libpq headers', $target . '/include/', 'src/interfaces/libpq/', 'libpq-fe.h'); + CopyFiles('Libpq headers', + $target . '/include/', 'src/interfaces/libpq/', + 'libpq-fe.h', 'libpq-events.h'); CopyFiles( 'Libpq internal headers', $target .'/include/internal/',