Re: libpq patch for pqtypes hook api and PGresult creation - Mailing list pgsql-patches
From | Andrew Chernow |
---|---|
Subject | Re: libpq patch for pqtypes hook api and PGresult creation |
Date | |
Msg-id | 4800BB14.8050505@esilo.com Whole thread Raw |
In response to | Re: libpq patch for pqtypes hook api and PGresult creation (Andrew Chernow <ac@esilo.com>) |
List | pgsql-patches |
> > Should the hook only be called when the conn or result was > successfull or should the hooks be called for failed > connections/commands as well? > ISTM that the hooks should be called on success and error (doesn't include cases where a NULL conn or result is returned). I think this makes things more useful. If the hooker (pun intended) isn't interested in error results or conns, it can easily ignore them. connCreate: The best solution I found was to hook into PQconnectPoll. This required making the current PQconnectPoll a static named connectPoll and making PQconnectPoll a wrapper to it. The wrapper just calls connectPoll and checks the status for hook points. resultCreate: PQgetResult seemed like the best place. I only notify the hooks if the resultStatus is NOT copyin or copyout. I diff'd fe-connect.c and fe-exec.c against cvs which shows these changes. -- Andrew Chernow eSilo, LLC every bit counts http://www.esilo.com/ Index: fe-connect.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v retrieving revision 1.357 diff -C6 -r1.357 fe-connect.c *** fe-connect.c 31 Mar 2008 02:43:14 -0000 1.357 --- fe-connect.c 12 Apr 2008 13:22:30 -0000 *************** *** 240,252 **** static int parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage); static char *pwdfMatchesString(char *buf, char *token); static char *PasswordFromFile(char *hostname, char *port, char *dbname, char *username); static void default_threadlock(int acquire); ! /* global variable because fe-auth.c needs to access it */ pgthreadlock_t pg_g_threadlock = default_threadlock; /* --- 240,252 ---- static int parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage); static char *pwdfMatchesString(char *buf, char *token); static char *PasswordFromFile(char *hostname, char *port, char *dbname, char *username); static void default_threadlock(int acquire); ! static void notifyConnCreateHooks(PGconn *conn); /* global variable because fe-auth.c needs to access it */ pgthreadlock_t pg_g_threadlock = default_threadlock; /* *************** *** 891,903 **** --- 891,907 ---- connectDBComplete(PGconn *conn) { PostgresPollingStatusType flag = PGRES_POLLING_WRITING; time_t finish_time = ((time_t) -1); if (conn == NULL || conn->status == CONNECTION_BAD) + { + if(conn) + notifyConnCreateHooks(conn); return 0; + } /* * Set up a time limit, if connect_timeout isn't zero. */ if (conn->connect_timeout != NULL) { *************** *** 979,992 **** * o If your backend wants to use Kerberos authentication then you must * supply both a host name and a host address, otherwise this function * may block on gethostname. * * ---------------- */ ! PostgresPollingStatusType ! PQconnectPoll(PGconn *conn) { PGresult *res; char sebuf[256]; if (conn == NULL) return PGRES_POLLING_FAILED; --- 983,997 ---- * o If your backend wants to use Kerberos authentication then you must * supply both a host name and a host address, otherwise this function * may block on gethostname. * * ---------------- */ ! ! static PostgresPollingStatusType ! connectPoll(PGconn *conn) { PGresult *res; char sebuf[256]; if (conn == NULL) return PGRES_POLLING_FAILED; *************** *** 1875,1886 **** --- 1880,1908 ---- * the connection structure must be freed). */ conn->status = CONNECTION_BAD; return PGRES_POLLING_FAILED; } + /* See connectPoll. All this does is wrap the real PQconnectPoll so + * we can trap PGRES_POLLING_OK or PGRES_POLLING_FAILED statuses. This + * could be done in the real connectPoll, but that requires littering the + * function with notifyConnCreateHooks calls because there are many + * places the function returns a status. This solution is much less + * obtrusive and avoids messing with the black magic of connectPoll. + */ + PostgresPollingStatusType + PQconnectPoll(PGconn *conn) + { + PostgresPollingStatusType status = connectPoll(conn); + + if(status == PGRES_POLLING_OK || status == PGRES_POLLING_FAILED) + notifyConnCreateHooks(conn); + + return status; + } /* * makeEmptyPGconn * - create a PGconn data structure with (as yet) no interesting data */ static PGconn * *************** *** 1970,1981 **** --- 1992,2015 ---- * 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 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, (const PGconn *)conn); + } + pqReleaseObjectHooks(); if (conn->pghost) free(conn->pghost); if (conn->pghostaddr) free(conn->pghostaddr); if (conn->pgport) free(conn->pgport); *************** *** 3843,3854 **** --- 3877,3904 ---- pthread_mutex_lock(&singlethread_lock); else pthread_mutex_unlock(&singlethread_lock); #endif } + /* Calls the connCreate hook. */ + static void notifyConnCreateHooks(PGconn *conn) + { + 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, (const PGconn *)conn); + } + + pqReleaseObjectHooks(); + } pgthreadlock_t PQregisterThreadLock(pgthreadlock_t newhandler) { pgthreadlock_t prev = pg_g_threadlock; if (newhandler) 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 12 Apr 2008 13:22:31 -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 *************** *** 192,203 **** --- 192,331 ---- result->client_encoding = PG_SQL_ASCII; } 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. * * nBytes is the amount of space needed for the object. * If isBinary is true, we assume that we need to align the object on *************** *** 350,365 **** --- 478,504 ---- * free's the memory associated with a PGresult */ void 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, (const PGresult *)res); + } + pqReleaseObjectHooks(); /* Free all the subsidiary blocks */ while ((block = res->curBlock) != NULL) { res->curBlock = block->next; free(block); } *************** *** 1179,1190 **** --- 1318,1346 ---- parseInput(conn); /* PQgetResult will return immediately in all states except BUSY. */ return conn->asyncStatus == PGASYNC_BUSY; } + /* Used by PQgetResult */ + static void notifyResultCreateHooks(PGconn *conn, PGresult *res) + { + int objHooksCount; + const PGobjectHooks *objHooks; + + if((objHooksCount = pqGetObjectHooks(&objHooks)) > 0) + { + int i; + for(i=0; i < objHooksCount; i++) + if(objHooks[i].resultCreate) + objHooks[i].resultCreate(objHooks[i].name, (const PGconn *)conn, + (const PGresult *)res); + } + + pqReleaseObjectHooks(); + } /* * PQgetResult * Get the next PGresult produced by a query. Returns NULL if no * query work remains or an error has occurred (e.g. out of * memory). *************** *** 1227,1239 **** /* * conn->errorMessage has been set by pqWait or pqReadData. We * want to append it to any already-received error message. */ pqSaveErrorResult(conn); conn->asyncStatus = PGASYNC_IDLE; ! return pqPrepareAsyncResult(conn); } /* Parse it. */ parseInput(conn); } --- 1383,1403 ---- /* * conn->errorMessage has been set by pqWait or pqReadData. We * want to append it to any already-received error message. */ pqSaveErrorResult(conn); conn->asyncStatus = PGASYNC_IDLE; ! res = pqPrepareAsyncResult(conn); ! ! if(res && res->resultStatus != PGRES_COPY_IN && ! res->resultStatus != PGRES_COPY_OUT) ! { ! notifyResultCreateHooks(conn, res); ! } ! ! return res; } /* Parse it. */ parseInput(conn); } *************** *** 1265,1276 **** --- 1429,1446 ---- libpq_gettext("unexpected asyncStatus: %d\n"), (int) conn->asyncStatus); res = PQmakeEmptyPGresult(conn, PGRES_FATAL_ERROR); break; } + if(res && res->resultStatus != PGRES_COPY_IN && + res->resultStatus != PGRES_COPY_OUT) + { + notifyResultCreateHooks(conn, res); + } + return res; } /* * PQexec
pgsql-patches by date: