Index: src/interfaces/libpq/exports.txt =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/exports.txt,v retrieving revision 1.19 diff -c -r1.19 exports.txt *** src/interfaces/libpq/exports.txt 19 Mar 2008 00:39:33 -0000 1.19 --- src/interfaces/libpq/exports.txt 11 Apr 2008 17:36:26 -0000 *************** *** 141,143 **** --- 141,147 ---- pg_valid_server_encoding_id 139 PQconnectionNeedsPassword 140 lo_import_with_oid 141 + PQaddObjectHook 142 + PQremoveObjectHook 143 + PQmakeResult 144 + PQsetvalue 145 \ 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.357 diff -c -r1.357 fe-connect.c *** src/interfaces/libpq/fe-connect.c 31 Mar 2008 02:43:14 -0000 1.357 --- src/interfaces/libpq/fe-connect.c 11 Apr 2008 17:36:26 -0000 *************** *** 866,872 **** --- 866,887 ---- * we are in PGRES_POLLING_WRITING connection state. */ if (PQconnectPoll(conn) == PGRES_POLLING_WRITING) + { + int objHooksCount; + const PGobjectHooks *objHooks; + + if((objHooksCount = pqGetObjectHooks(&objHooks)) > 0) + { + int i; + + for(i=0; i < objHooksCount; i++) + if(objHooks[i].connCreate) + objHooks[i].connCreate(objHooks[i].name, conn); + } + pqReleaseObjectHooks(); + return 1; + } connect_errReturn: if (conn->sock >= 0) *************** *** 1973,1978 **** --- 1988,2006 ---- static void freePGconn(PGconn *conn) { + int objHooksCount; + const PGobjectHooks *objHooks; + + if((objHooksCount = pqGetObjectHooks(&objHooks)) > 0) + { + int i; + + for(i=0; i < objHooksCount; i++) + if(objHooks[i].connDestroy) + objHooks[i].connDestroy(objHooks[i].name, conn); + } + pqReleaseObjectHooks(); + if (conn->pghost) free(conn->pghost); if (conn->pghostaddr) Index: src/interfaces/libpq/fe-exec.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v retrieving revision 1.194 diff -c -r1.194 fe-exec.c *** src/interfaces/libpq/fe-exec.c 1 Jan 2008 19:46:00 -0000 1.194 --- src/interfaces/libpq/fe-exec.c 11 Apr 2008 17:36:27 -0000 *************** *** 63,69 **** static PGresult *PQexecFinish(PGconn *conn); static int PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target); ! /* ---------------- * Space management for PGresult. --- 63,69 ---- 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. *************** *** 139,144 **** --- 139,146 ---- PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status) { PGresult *result; + int objHooksCount; + const PGobjectHooks *objHooks; result = (PGresult *) malloc(sizeof(PGresult)); if (!result) *************** *** 192,200 **** --- 194,341 ---- result->client_encoding = PG_SQL_ASCII; } + if((objHooksCount = pqGetObjectHooks(&objHooks)) > 0) + { + int i; + + for(i=0; i < objHooksCount; i++) + if(objHooks[i].resultCreate) + objHooks[i].resultCreate(objHooks[i].name, conn, result); + } + pqReleaseObjectHooks(); + return result; } + PGresult *PQmakeResult( + PGconn *conn, + int numAttributes, + PGresAttDesc *attDescs) + { + int i; + PGresult *res; + + if(numAttributes <= 0 || !attDescs) + return NULL; + + res = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK); + if(!res) + return NULL; + + res->attDescs = (PGresAttDesc *) + pqResultAlloc(res, numAttributes * sizeof(PGresAttDesc), TRUE); + + if(!res->attDescs) + { + PQclear(res); + return NULL; + } + + res->numAttributes = numAttributes; + memcpy(res->attDescs, attDescs, numAttributes * sizeof(PGresAttDesc)); + + /* resultalloc the attribute names. */ + res->binary = 1; + for(i=0; i < numAttributes; i++) + { + if(attDescs[i].name) + res->attDescs[i].name = pqResultStrdup(res, attDescs[i].name); + else + res->attDescs[i].name = res->null_field; + + if(!res->attDescs[i].name) + { + PQclear(res); + return NULL; + } + + /* 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 res; + } + + 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; + + res->tuples = tups; + res->tupArrSize = n; + } + + /* new 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), 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; + + 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 - * Allocate subsidiary storage for a PGresult. *************** *** 353,362 **** --- 494,515 ---- PQclear(PGresult *res) { PGresult_data *block; + int objHooksCount; + const PGobjectHooks *objHooks; if (!res) return; + if((objHooksCount = pqGetObjectHooks(&objHooks)) > 0) + { + int i; + + for(i=0; i < objHooksCount; i++) + if(objHooks[i].resultDestroy) + objHooks[i].resultDestroy(objHooks[i].name, res); + } + pqReleaseObjectHooks(); + /* Free all the subsidiary blocks */ while ((block = res->curBlock) != NULL) { Index: src/interfaces/libpq/fe-misc.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v retrieving revision 1.133 diff -c -r1.133 fe-misc.c *** src/interfaces/libpq/fe-misc.c 1 Jan 2008 19:46:00 -0000 1.133 --- src/interfaces/libpq/fe-misc.c 11 Apr 2008 17:36:27 -0000 *************** *** 1155,1157 **** --- 1155,1332 ---- } #endif /* ENABLE_NLS */ + + + /* --------------------- + * ObjectHooks + */ + + #ifdef ENABLE_THREAD_SAFETY + # ifdef WIN32 + # include "pthread-win32.h" + static pthread_mutex_t objHookMutex = NULL; + # else + # include + static pthread_mutex_t objHookMutex = PTHREAD_MUTEX_INITIALIZER; + # endif + #endif + + /* Object Hook array management variables. The hooks array is + * dynamically grown as needed. When removing an element, the + * element is memmove'd out. + */ + static int objHooksCount = 0; + static int objHooksArrSize = 0; + static PGobjectHooks *objHooks = NULL; + + int PQaddObjectHook(PGobjectHooks *hook, char **errMsg) + { + int i; + + if(!hook) + { + if(errMsg) + *errMsg = "ObjectHook pointer cannot be NULL"; + return FALSE; + } + + /* hook names must be provided */ + if(!hook->name || !*hook->name) + { + if(errMsg) + *errMsg = "ObjectHook name cannot be NULL or an empty string"; + return FALSE; + } + + /* At least one hook func must be provided (pointless otherwise) */ + if(!hook->connCreate && !hook->connDestroy && + !hook->resultCreate && !hook->resultDestroy) + { + if(errMsg) + *errMsg = "At least one ObjectHook function must be provided"; + return FALSE; + } + + (void)pqGetObjectHooks(NULL); + + /* hook names must be unique, case ignored. */ + for(i=0; i < objHooksCount; i++) + { + if(pg_strcasecmp(objHooks[i].name, hook->name)==0) + { + pqReleaseObjectHooks(); + if(errMsg) + *errMsg = "ObjectHook name already exists"; + return FALSE; + } + } + + /* grow objHooks array */ + if(objHooksCount == objHooksArrSize) + { + int n = objHooksArrSize ? (objHooksArrSize*3)/2 : 4; + + objHooks = (PGobjectHooks *)malloc(sizeof(PGobjectHooks) * n); + if(!objHooks) + { + if(errMsg) + *errMsg = "Out of memory error"; + return FALSE; + } + + objHooksArrSize = n; + } + + memcpy(objHooks + objHooksCount, hook, sizeof(PGobjectHooks)); + objHooks[objHooksCount++].name = strdup(hook->name); + + pqReleaseObjectHooks(); + + if(errMsg) + *errMsg = ""; + return TRUE; + } + + /* Removes a hook from the global hooks array. */ + void PQremoveObjectHook(const char *name) + { + int i; + + if(!name || !*name) + return; + + (void)pqGetObjectHooks(NULL); + + for(i=0; i < objHooksCount; i++) + { + if(pg_strcasecmp(objHooks[i].name, name)==0) + { + free((void *)objHooks[i].name); + memmove(objHooks + i, objHooks + i + 1, + (objHooksCount - i - 1) * sizeof(PGobjectHooks)); + objHooksCount--; + break; + } + } + + pqReleaseObjectHooks(); + } + + /* Gets the object hooks array, storing the array at *hooks and returning + * the number of elements in the array. The hooks array should be + * treated as read-only memory. + * + * When ENABLE_THREAD_SAFETY is set, this will lock a private mutex + * before returning. To unlock the hooks, call pqReleaseObjectHooks. + * Warning, you must call pqReleaseObjectHooks for each call to + * pqGetObjectHooks. + * + * const PGobjectHooks *hooks; + * int count = pqGetObjectHooks(&hooks); + * // ... do some work with hooks + * pqReleaseObjectHooks(); + * + * If the provided hooks argument is NULL, the internal mutex is still + * locked and the function returns the number of hooks registered. + * You still have to call pqReleaseObjectHooks. + * + * If there are no hooks registered, *hooks will be NULL and the + * function returns 0. + */ + int pqGetObjectHooks(const PGobjectHooks **hooks) + { + #ifdef ENABLE_THREAD_SAFETY + # ifdef WIN32 + static long initlock = 0; + + /* copied from init_ssl_system */ + if(objHookMutex == NULL) + { + while (InterlockedExchange(&initlock, 1) == 1) + /* loop, another thread own the lock */ ; + if (objHookMutex == NULL) + pthread_mutex_init(&objHookMutex, NULL); + InterlockedExchange(&initlock, 0); + } + # endif + + pthread_mutex_lock(&objHookMutex); + #endif + + if(hooks) + *hooks = (const PGobjectHooks *)objHooks; + return objHooksCount; + } + + /* Must be called after pqGetObjectHooks, so the mutex can be unlocked + * when ENABLE_THREAD_SAFETY is set. + */ + void pqReleaseObjectHooks(void) + { + #ifdef ENABLE_THREAD_SAFETY + pthread_mutex_unlock(&objHookMutex); + #endif + } + + + Index: src/interfaces/libpq/libpq-fe.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-fe.h,v retrieving revision 1.142 diff -c -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 11 Apr 2008 17:36:27 -0000 *************** *** 193,198 **** --- 193,236 ---- } 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; + + /* ---------------- + * PGobjectHooks -- structure for adding libpq object hooks + * Monitors the creation and deletion of objects. + * ---------------- + */ + + typedef struct + { + const char *name; + + /* Invoked on PQconnectdb, PQsetdbLogin, PQconnectStart or PQreset */ + int (*connCreate)(const char *hookName, PGconn *conn); + + /* Invoked on PQfinish. */ + int (*connDestroy)(const char *hookName, PGconn *conn); + + /* Invoked when PQmakeEmptyResult is called */ + int (*resultCreate)(const char *hookName, PGconn *conn, PGresult *result); + + /* Invoked when PQclear is called */ + int (*resultDestroy)(const char *hookName, PGresult *result); + } PGobjectHooks; + + /* ---------------- * Exported functions of libpq * ---------------- */ *************** *** 437,442 **** --- 475,500 ---- */ extern PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status); + /* + * Makes a new result using the provided field descriptors. If conn is + * not NULL, some information will be copied to the new result. + * The returned result has zero tuples and a resultStatus of PGRES_TUPLES_OK. + * To add tuples and set field values, see PQsetvalue. + */ + extern PGresult *PQmakeResult(PGconn *conn, int numAttributes, + PGresAttDesc *attDescs); + + /* + * 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, *************** *** 509,514 **** --- 567,581 ---- /* Get encoding id from environment variable PGCLIENTENCODING */ extern int PQenv2encoding(void); + /* Add a libpq global Object Hook. If errMsg is not NULL, it will + * be set to an error message if the function fails. This returns + * non-zero on success and zero on failure. + */ + extern int PQaddObjectHook(PGobjectHooks *hook, char **errMsg); + + /* Removes an Object Hook previously added with PQaddObjectHook */ + extern void PQremoveObjectHook(const char *name); + /* === in fe-auth.c === */ extern char *PQencryptPassword(const char *passwd, const char *user); Index: src/interfaces/libpq/libpq-int.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v retrieving revision 1.129 diff -c -r1.129 libpq-int.h *** src/interfaces/libpq/libpq-int.h 1 Jan 2008 19:46:00 -0000 1.129 --- src/interfaces/libpq/libpq-int.h 11 Apr 2008 17:36:27 -0000 *************** *** 100,118 **** 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 { --- 100,105 ---- *************** *** 205,210 **** --- 192,198 ---- int spaceLeft; /* number of free bytes remaining in block */ }; + /* PGAsyncStatusType defines the state of the query-execution state machine */ typedef enum { *************** *** 524,529 **** --- 512,522 ---- extern int pqReadReady(PGconn *conn); extern int pqWriteReady(PGconn *conn); + /* === in objhooks.c === */ + + extern int pqGetObjectHooks(const PGobjectHooks **hooks); + extern void pqReleaseObjectHooks(void); + /* === in fe-secure.c === */ extern int pqsecure_initialize(PGconn *);