Index: exports.txt =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/exports.txt,v retrieving revision 1.19 diff -C6 -r1.19 exports.txt *** exports.txt 19 Mar 2008 00:39:33 -0000 1.19 --- exports.txt 17 May 2008 12:06:10 -0000 *************** *** 138,143 **** --- 138,149 ---- PQsendDescribePortal 136 lo_truncate 137 PQconnectionUsedPassword 138 pg_valid_server_encoding_id 139 PQconnectionNeedsPassword 140 lo_import_with_oid 141 + PQcopyResult 142 + PQsetvalue 143 + PQresultAlloc 144 + PQregisterEventProc 145 + PQeventData 146 + PQresultEventData 147 Index: fe-connect.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v retrieving revision 1.358 diff -C6 -r1.358 fe-connect.c *** fe-connect.c 16 May 2008 18:30:53 -0000 1.358 --- fe-connect.c 17 May 2008 12:06:11 -0000 *************** *** 998,1009 **** --- 998,1014 ---- * We really shouldn't have been polled in these two cases, but we * can handle it. */ case CONNECTION_BAD: return PGRES_POLLING_FAILED; case CONNECTION_OK: + if(!pqRegisterGlobalEvents(conn)) + { + conn->status = CONNECTION_BAD; + return PGRES_POLLING_FAILED; + } return PGRES_POLLING_OK; /* These are reading states */ case CONNECTION_AWAITING_RESPONSE: case CONNECTION_AUTH_OK: { *************** *** 1816,1827 **** --- 1821,1837 ---- conn->next_eo = EnvironmentOptions; return PGRES_POLLING_WRITING; } /* Otherwise, we are open for business! */ conn->status = CONNECTION_OK; + if(!pqRegisterGlobalEvents(conn)) + { + conn->status = CONNECTION_BAD; + return PGRES_POLLING_FAILED; + } return PGRES_POLLING_OK; } case CONNECTION_SETENV: /* *************** *** 1848,1859 **** --- 1858,1874 ---- default: goto error_return; } /* We are open for business! */ conn->status = CONNECTION_OK; + if(!pqRegisterGlobalEvents(conn)) + { + conn->status = CONNECTION_BAD; + return PGRES_POLLING_FAILED; + } return PGRES_POLLING_OK; default: printfPQExpBuffer(&conn->errorMessage, libpq_gettext( "invalid connection state %c, " *************** *** 1970,1981 **** --- 1985,2016 ---- * 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 info; + + /* Let the event procs cleanup their state data */ + for(i=0; i < conn->evtStateCount; i++) + { + info.conn = conn; + info.data = conn->evtStates[i].data; + (void)conn->evtStates[i].proc(PGEVT_CONNDESTROY, + &info, conn->evtStates[i].arg); + } + + /* free the PGEventState array */ + if(conn->evtStates) + { + free(conn->evtStates); + conn->evtStates = NULL; + conn->evtStateCount = conn->evtStateSize = 0; + } + if (conn->pghost) free(conn->pghost); if (conn->pghostaddr) free(conn->pghostaddr); if (conn->pgport) free(conn->pgport); *************** *** 2151,2164 **** PQreset(PGconn *conn) { if (conn) { closePGconn(conn); ! if (connectDBStart(conn)) ! (void) connectDBComplete(conn); } } /* * PQresetStart: --- 2186,2218 ---- PQreset(PGconn *conn) { if (conn) { closePGconn(conn); ! if (connectDBStart(conn) && connectDBComplete(conn)) ! { ! int i; ! PGEventConnReset info; ! ! for(i=0; i < conn->evtStateCount; i++) ! { ! info.conn = conn; ! info.data = conn->evtStates[i].data; ! ! if(!conn->evtStates[i].proc(PGEVT_CONNRESET, &info, ! conn->evtStates[i].arg)) ! { ! conn->status = CONNECTION_BAD; ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("PGEventProc %p failed during PGEVT_CONNRESET event\n"), ! conn->evtStates[i].proc); ! break; ! } ! } ! } } } /* * PQresetStart: *************** *** 2186,2198 **** * 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. --- 2240,2278 ---- * 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 info; ! ! for(i=0; i < conn->evtStateCount; i++) ! { ! info.conn = conn; ! info.data = conn->evtStates[i].data; ! ! if(!conn->evtStates[i].proc(PGEVT_CONNRESET, &info, ! conn->evtStates[i].arg)) ! { ! conn->status = CONNECTION_BAD; ! printfPQExpBuffer(&conn->errorMessage, ! libpq_gettext("PGEventProc %p failed during PGEVT_CONNRESET event\n"), ! conn->evtStates[i].proc); ! return PGRES_POLLING_FAILED; ! } ! } ! } ! ! return status; ! } return PGRES_POLLING_FAILED; } /* * PQcancelGet: get a PGcancel structure corresponding to a connection. Index: fe-exec.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v retrieving revision 1.194 diff -C6 -r1.194 fe-exec.c *** fe-exec.c 1 Jan 2008 19:46:00 -0000 1.194 --- fe-exec.c 17 May 2008 12:06:12 -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,167 ---- #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 private state data, sets this to NULL */ + static PGEventState * + dupEventStates(PGEventState *states, int count) + { + int i; + PGEventState *newStates; + + if(!states || count <= 0) + return NULL; + + newStates = (PGEventState *)malloc(count * sizeof(PGEventState)); + if(!newStates) + return NULL; + + memcpy(newStates, states, count * sizeof(PGEventState)); + + /* NULL out the data pointer */ + for(i=0; i < count; i++) + newStates[i].data = NULL; + + return newStates; + } + /* * 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 **** --- 183,218 ---- result->errMsg = NULL; result->errFields = NULL; result->null_field[0] = '\0'; result->curBlock = NULL; result->curOffset = 0; result->spaceLeft = 0; + result->evtStateCount = 0; + result->evtStates = NULL; if (conn) { /* copy connection data we might need for operations on PGresult */ result->noticeHooks = conn->noticeHooks; result->client_encoding = conn->client_encoding; + /* copy event states from connection */ + if(conn->evtStateCount > 0) + { + result->evtStates = dupEventStates( + conn->evtStates, conn->evtStateCount); + + if(!result->evtStates) + { + PQclear(result); + return NULL; + } + + result->evtStateCount = conn->evtStateCount; + } + /* consider copying conn's errorMessage */ switch (status) { case PGRES_EMPTY_QUERY: case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: *************** *** 193,204 **** --- 236,490 ---- } return result; } /* + * PQcopyResult + * Returns a deep copy of the provided 'src' PGresult, which cannot be NULL. + * The 'options' argument controls which portions of the result will or will + * NOT be copied. If this value is 0, the entire result is deep copied. + * The created result is always put into the PGRES_TUPLES_OK status. The + * source result error message is not copied, although cmdStatus is. + * + * Options: + * PG_COPYRES_NO_TUPLES - Do not copy the tuples. This option is + * automatically enabled when PG_COPYRES_USE_ATTRS is set. + * + * PG_COPYRES_USE_ATTRS - Indicates that the 'numAttributes' and 'attDescs' + * arguments should be used as the fields for the created result, rather + * than copying them from the source result. When this option is set, + * the tuples will NOT be copied; behaving identically to setting the + * PG_COPYRES_NO_TUPLES option. One must use PQsetvalue to manually + * add tuples to the returned result. NOTE: numAttributes and attDescs + * arguments are ignored unless this option is set! + * + * PG_COPYRES_NO_EVENTPROCS - Indicates that the source result's + * PGEventProcs should NOT be copied to the created result. + * + * PG_COPYRES_NO_NOTICEHOOKS - Indicates that the source result's + * NoticeHooks should NOT be copied to the created result. + */ + + PGresult * + PQcopyResult(const PGresult *src, int numAttributes, + PGresAttDesc *attDescs, int options) + { + int i; + PGresult *dest; + PGEventResultCopy info; + + if(!src) + return NULL; + + /* Automatically turn on no_tuples since use_attrs is set. It makes + * no sense to copy tuples into an unknown set of columns. + */ + if(options & PG_COPYRES_USE_ATTRS) + options |= PG_COPYRES_NO_TUPLES; + + /* If use_attrs is set, verify attr arguments. */ + if((options & PG_COPYRES_USE_ATTRS) && (numAttributes <= 0 || !attDescs)) + return NULL; + + 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(!(options & PG_COPYRES_NO_NOTICEHOOKS)) + dest->noticeHooks = src->noticeHooks; + + /* Copy from src result when not supplying attrs */ + if(!(options & PG_COPYRES_USE_ATTRS) && src->numAttributes > 0) + { + numAttributes = src->numAttributes; + attDescs = src->attDescs; + } + + /* copy attrs */ + if(numAttributes > 0) + { + dest->attDescs = (PGresAttDesc *)PQresultAlloc(dest, + numAttributes * sizeof(PGresAttDesc)); + + if(!dest->attDescs) + { + PQclear(dest); + return NULL; + } + + dest->numAttributes = numAttributes; + memcpy(dest->attDescs, attDescs, numAttributes * sizeof(PGresAttDesc)); + + /* resultalloc the attribute names. The above memcpy has the attr + * names pointing at the source result's private memory (or at the + * callers provided attDescs memory). + */ + dest->binary = 1; + for(i=0; i < numAttributes; i++) + { + if(attDescs[i].name) + dest->attDescs[i].name = pqResultStrdup(dest, attDescs[i].name); + else + dest->attDescs[i].name = dest->null_field; + + if(!dest->attDescs[i].name) + { + PQclear(dest); + return NULL; + } + + /* Although deprecated, because results can have text+binary columns, + * its easy enough to deduce so set it for completeness. + */ + if(dest->attDescs[i].format == 0) + dest->binary = 0; + } + } + + /* Wants to copy result tuples: use PQsetvalue(). */ + if(!(options & PG_COPYRES_NO_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 PGEventProcs. */ + if(!(options & PG_COPYRES_NO_EVENTPROCS) && src->evtStateCount > 0) + { + dest->evtStates = dupEventStates(src->evtStates, src->evtStateCount); + + if(!dest->evtStates) + { + PQclear(dest); + return NULL; + } + + dest->evtStateCount = src->evtStateCount; + } + + /* Trigger PGEVT_RESULTCOPY event for each event proc */ + for(i=0; i < dest->evtStateCount; i++) + { + info.src = src; + info.srcData = src->evtStates[i].data; + info.dest = dest; + info.destData = NULL; + + if(!src->evtStates[i].proc(PGEVT_RESULTCOPY, &info, + src->evtStates[i].arg)) + { + PQclear(dest); + return NULL; + } + + dest->evtStates[i].data = info.destData; + } + + 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*3)/2 : 64; + PGresAttValue **tups = (PGresAttValue **) + (res->tuples ? realloc(res->tuples, n*sizeof(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 = (PGresAttValue *)PQresultAlloc( + res, res->numAttributes * sizeof(PGresAttValue)); + + 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 * a machine allocation boundary. *************** *** 349,365 **** --- 635,669 ---- * PQclear - * free's the memory associated with a PGresult */ void PQclear(PGresult *res) { + int i; PGresult_data *block; + PGEventResultDestroy info; if (!res) return; + for(i=0; i < res->evtStateCount; i++) + { + info.result = res; + info.data = res->evtStates[i].data; + + (void)res->evtStates[i].proc(PGEVT_RESULTDESTROY, &info, + res->evtStates[i].arg); + } + + if(res->evtStates) + { + free(res->evtStates); + res->evtStates = NULL; + res->evtStateCount = 0; + } + /* Free all the subsidiary blocks */ while ((block = res->curBlock) != NULL) { res->curBlock = block->next; free(block); } *************** *** 1190,1202 **** * memory). */ PGresult * PQgetResult(PGconn *conn) { ! PGresult *res; if (!conn) return NULL; /* Parse any available data, if our state permits. */ parseInput(conn); --- 1494,1506 ---- * memory). */ PGresult * PQgetResult(PGconn *conn) { ! PGresult *res=NULL; if (!conn) return NULL; /* Parse any available data, if our state permits. */ parseInput(conn); *************** *** 1265,1276 **** --- 1569,1611 ---- libpq_gettext("unexpected asyncStatus: %d\n"), (int) conn->asyncStatus); res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); break; } + if(res && (res->resultStatus == PGRES_COMMAND_OK || + res->resultStatus == PGRES_TUPLES_OK || + res->resultStatus == PGRES_EMPTY_QUERY)) + { + int i; + PGEventResultCreate info; + + info.conn = conn; + info.result = res; + + for(i=0; i < res->evtStateCount; i++) + { + info.connData = conn->evtStates[i].data; + info.resultData = NULL; + + if(!res->evtStates[i].proc(PGEVT_RESULTCREATE, + &info, res->evtStates[i].arg)) + { + char msg[256]; + sprintf(msg, + "PGEventProc %p failed during PGEVT_RESULTCREATE event", + res->evtStates[i].proc); + pqSetResultError(res, msg); + res->resultStatus = PGRES_FATAL_ERROR; + break; + } + + res->evtStates[i].data = info.resultData; + } + } + return res; } /* * PQexec Index: fe-misc.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v retrieving revision 1.133 diff -C6 -r1.133 fe-misc.c *** fe-misc.c 1 Jan 2008 19:46:00 -0000 1.133 --- fe-misc.c 17 May 2008 12:06:12 -0000 *************** *** 1152,1157 **** --- 1152,1301 ---- } return dgettext("libpq", msgid); } #endif /* ENABLE_NLS */ + + + /* --------------------- + * Events + */ + + static int g_evtStateCount = 0; + static int g_evtStateSize = 0; + static PGEventState *g_evtStates = NULL; + + int + PQregisterEventProc(PGconn *conn, PGEventProc proc, void *arg) + { + int i; + int *count; + int *size; + PGEventState **states; + + if(!proc) + return FALSE; + + /* global register */ + if(!conn) + { + count = &g_evtStateCount; + size = &g_evtStateSize; + states = &g_evtStates; + } + /* per-conn */ + else + { + count = &conn->evtStateCount; + size = &conn->evtStateSize; + states = &conn->evtStates; + } + + for(i=0; i < *count; i++) + if((*states)[i].proc == proc) + return FALSE; /* already registered */ + + if(*count >= *size) + { + PGEventState *e; + int newSize = *size ? (*size * 3) / 2 : 8; + + if(*states) + e = (PGEventState *)realloc(*states, newSize*sizeof(PGEventState)); + else + e = (PGEventState *)malloc(newSize*sizeof(PGEventState)); + + if(!e) + return FALSE; + + *size = newSize; + *states = e; + } + + (*states)[*count].arg = arg; + (*states)[*count].data = NULL; + (*states)[*count].proc = proc; + + /* per-conn register requires triggering an initstate event */ + if(conn) + { + PGEventInitState initState; + initState.data = NULL; + initState.conn = conn; + if(!proc(PGEVT_INITSTATE, &initState, arg)) + return FALSE; + (*states)[*count].data = initState.data; + } + + (*count)++; + return TRUE; + } + + void * + PQeventData(const PGconn *conn, PGEventProc proc, void **arg) + { + int i; + + if(arg) + *arg = NULL; + + if(!conn || !proc) + return NULL; + + for(i=0; i < conn->evtStateCount; i++) + { + if(conn->evtStates[i].proc == proc) + { + if(arg) + *arg = conn->evtStates[i].arg; + return conn->evtStates[i].data; + } + } + + return NULL; + } + + void * + PQresultEventData(const PGresult *result, PGEventProc proc, void **arg) + { + int i; + + if(arg) + *arg = NULL; + + if(!result || !proc) + return NULL; + + for(i=0; i < result->evtStateCount; i++) + { + if(result->evtStates[i].proc == proc) + { + if(arg) + *arg = result->evtStates[i].arg; + return result->evtStates[i].data; + } + } + + return NULL; + } + + /* Registers the global events procs for a conn object. If the procs + * were already registered, the request is ignored and a success value + * is returned. This will happen during a PQreset and PQresetPoll. + * Returns non-zero for success and zero on error. + */ + int + pqRegisterGlobalEvents(PGconn *conn) + { + int i; + + /* Already registered, occurs on PQreset and PQresetPoll */ + if(conn->evtStateCount > 0) + return TRUE; + + for(i=0; i < g_evtStateCount; i++) + if(!PQregisterEventProc(conn, g_evtStates[i].proc, g_evtStates[i].arg)) + return FALSE; + + return TRUE; + } Index: 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 *** libpq-fe.h 19 Mar 2008 00:39:33 -0000 1.142 --- libpq-fe.h 17 May 2008 12:06:12 -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_NO_TUPLES 0x01 + #define PG_COPYRES_USE_ATTRS 0x02 + #define PG_COPYRES_NO_EVENTPROCS 0x04 + #define PG_COPYRES_NO_NOTICEHOOKS 0x08 + /* Application-visible enum types */ typedef enum { /* * Although it is okay to add to this list, values which become unused *************** *** 190,201 **** --- 199,283 ---- 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; + + /* ---------------- + * Events - Allows receiving events from libpq. + * ---------------- + */ + + /* Event Ids */ + typedef enum + { + PGEVT_INITSTATE, + PGEVT_CONNRESET, + PGEVT_CONNDESTROY, + PGEVT_RESULTCREATE, + PGEVT_RESULTCOPY, + PGEVT_RESULTDESTROY + } PGEventId; + + typedef struct + { + void *data; /* create/assign during this event if needed */ + const PGconn *conn; + } PGEventInitState; + + typedef struct + { + void *data; + const PGconn *conn; + } PGEventConnReset; + + typedef struct + { + void *data; + const PGconn *conn; + } PGEventConnDestroy; + + typedef struct + { + void *connData; /* conn's event data */ + const PGconn *conn; + void *resultData; /* create/assign during this event if needed */ + const PGresult *result; + } PGEventResultCreate; + + typedef struct + { + void *srcData; /* src result's event data */ + const PGresult *src; + void *destData; /* create/assign during this event if needed */ + PGresult *dest; + } PGEventResultCopy; + + typedef struct + { + void *data; + const PGresult *result; + } PGEventResultDestroy; + + typedef int (*PGEventProc)(PGEventId evtId, void *evtInfo, void *arg); + + /* ---------------- * Exported functions of libpq * ---------------- */ /* === in fe-connect.c === */ *************** *** 434,445 **** --- 516,546 ---- * 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); + extern PGresult * + PQcopyResult(const PGresult *src, int numAttributes, + PGresAttDesc *attDescs, int options); + + 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, *************** *** 506,517 **** --- 607,661 ---- /* Determine display length of multibyte encoded char at *s */ extern int PQdsplen(const char *s, int encoding); /* Get encoding id from environment variable PGCLIENTENCODING */ extern int PQenv2encoding(void); + /* + * Registers an event proc with the given PGconn. The arg parameter + * is an application specific pointer and can be set to NULL if not + * required. The arg parameter will be passed to the event proc and + * is publically retrieveable via PQeventData or PQresultEventData. + * + * The proc parameter cannot be NULL. + * + * If the conn is NULL, this will register a global event proc. + * Global event procs are automatically registered on every PGconn + * created, removing the need for per-conn registration. + * + * WARNING: Global registrations should only be called from the application's + * main thread, prior to using any libpq functions and creating + * any application threads. Global registration should be treated as an + * init function. + * + * The function returns a non-zero if successful. If the function + * fails, zero is returned. + */ + extern int + PQregisterEventProc(PGconn *conn, PGEventProc proc, void *arg); + + /* + * Gets the event data created by the provided event proc for the given + * conn. The event data is returned, which may be NULL if the event proc + * does not create any event data. The arg parameter can be used to + * get the application specific pointer provided during event proc + * registration. If arg is not needed, it can be set to NULL. + */ + extern void * + PQeventData(const PGconn *conn, PGEventProc proc, void **arg); + + /* + * Gets the event data created by the provided event proc for the given + * result. The event data is returned, which may be NULL if the event proc + * does not create any event data. The arg parameter can be used to + * get the application specific pointer provided during event proc + * registration. If arg is not needed, it can be set to NULL. + */ + extern void * + PQresultEventData(const PGresult *res, PGEventProc proc, void **arg); + /* === in fe-auth.c === */ extern char *PQencryptPassword(const char *passwd, const char *user); /* === in encnames.c === */ Index: libpq-int.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v retrieving revision 1.130 diff -C6 -r1.130 libpq-int.h *** libpq-int.h 16 May 2008 18:30:53 -0000 1.130 --- libpq-int.h 17 May 2008 12:06:12 -0000 *************** *** 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; --- 97,108 ---- *************** *** 159,170 **** --- 146,166 ---- PQnoticeReceiver noticeRec; /* notice message receiver */ void *noticeRecArg; PQnoticeProcessor noticeProc; /* notice message processor */ void *noticeProcArg; } PGNoticeHooks; + typedef struct + { + /* pointer supplied by user */ + void *arg; + /* state data, optionally generated by event proc */ + void *data; + PGEventProc proc; + } PGEventState; + 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 */ + /* Event states */ + int evtStateCount; + PGEventState *evtStates; + /* * 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; + /* Event states */ + int evtStateCount; + int evtStateSize; + PGEventState *evtStates; + /* Status indicators */ ConnStatusType status; PGAsyncStatusType asyncStatus; PGTransactionStatusType xactStatus; /* never changes to ACTIVE */ PGQueryClass queryclass; char *last_query; /* last SQL command, or NULL if unknown */ *************** *** 527,538 **** --- 532,544 ---- extern int pqFlush(PGconn *conn); extern int pqWait(int forRead, int forWrite, PGconn *conn); extern int pqWaitTimed(int forRead, int forWrite, PGconn *conn, time_t finish_time); extern int pqReadReady(PGconn *conn); extern int pqWriteReady(PGconn *conn); + extern int pqRegisterGlobalEvents(PGconn *conn); /* === in fe-secure.c === */ extern int pqsecure_initialize(PGconn *); extern void pqsecure_destroy(void); extern PostgresPollingStatusType pqsecure_open_client(PGconn *);