pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feedRe: libpq object hooks (libpq events)
15+ messages / 4 participants
[nested] [flat]
* Re: libpq object hooks (libpq events)
@ 2008-05-17 12:28 Andrew Chernow <ac@esilo.com>
0 siblings, 2 replies; 15+ messages in thread
From: Andrew Chernow @ 2008-05-17 12:28 UTC (permalink / raw)
To: Merlin Moncure <mmoncure@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew@dunslane.net>; Alvaro Herrera <alvherre@commandprompt.com>; Patches (PostgreSQL) <pgsql-patches@postgresql.org>
Here is an updated patch for what was called object hooks. This is now
called libpq events. If someone has a better name or hates ours, let us
know.
I am continuing to use the object hooks thread to avoid confusing anyone.
Terminology:
I got rid of calling it object events because it is possible to add
other non-object-related events in the future; maybe a query event.
This system can be used for any type of event, I think it is pretty generic.
event proc - the callback procedure/function implemented outside of
libpq ... PGEventProc. The address of the event proc is used as a
lookup key for getting a conn/result's event data.
event data - the state data managed by the event proc but tracked by
libpq. This allows the event proc implementor to basically add a
dynamic struct member to a conn or result. This is an instance based
value, unlike the "arg pointer".
arg pointer - this is the passthrough/user pointer. I called it 'arg'
as other libpq callbacks used this term for this type of value. This
value can be retrieved via PQeventData, PQresultEventData ... its an
optional double pointer argument.
event state - an internal structure, PGEventState, which contains the
event data, event proc and the 'arg' pointer. Internally, PGconn and
PGresult contain arrays of event states.
event id - PGEventId enum values, PGEVT_xxx. This tells the event proc
which event has occurred.
event info - These are the structures for event ids, like
PGEVT_RESULTDESTROY has a corresponding PGEventResultDestroy structure.
The PGEventProc function's 2nd argument is "void *info". The first
argument is an event id which tells the proc implementor how to cast the
void *. This replaced the initial va_arg idea.
--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/
Attachments:
libpq_events.patch (text/plain, 30.8K) download
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 *);
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-05-18 13:33 Merlin Moncure <mmoncure@gmail.com>
parent: Andrew Chernow <ac@esilo.com>
1 sibling, 1 reply; 15+ messages in thread
From: Merlin Moncure @ 2008-05-18 13:33 UTC (permalink / raw)
To: Andrew Chernow <ac@esilo.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew@dunslane.net>; Alvaro Herrera <alvherre@commandprompt.com>; Patches (PostgreSQL) <pgsql-patches@postgresql.org>
On Sat, May 17, 2008 at 8:28 AM, Andrew Chernow <ac@esilo.com> wrote:
> Here is an updated patch for what was called object hooks. This is now
> called libpq events. If someone has a better name or hates ours, let us
> know.
Let's decide where to go with this. We have no objections to pushing
this back to the next fest (tom's comments on the wiki were noted).
If that's the case though, we would like to wrap up a consenus on the
general implementation in the meantime. Just give us a heads up and
I'll update the wiki.
merlin
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-05-18 14:33 Andrew Chernow <ac@esilo.com>
parent: Merlin Moncure <mmoncure@gmail.com>
0 siblings, 0 replies; 15+ messages in thread
From: Andrew Chernow @ 2008-05-18 14:33 UTC (permalink / raw)
To: Merlin Moncure <mmoncure@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew@dunslane.net>; Alvaro Herrera <alvherre@commandprompt.com>; Patches (PostgreSQL) <pgsql-patches@postgresql.org>
Merlin Moncure wrote:
> On Sat, May 17, 2008 at 8:28 AM, Andrew Chernow <ac@esilo.com> wrote:
>> Here is an updated patch for what was called object hooks. This is now
>> called libpq events. If someone has a better name or hates ours, let us
>> know.
>
>
> Let's decide where to go with this. We have no objections to pushing
> this back to the next fest (tom's comments on the wiki were noted).
> If that's the case though, we would like to wrap up a consenus on the
> general implementation in the meantime. Just give us a heads up and
> I'll update the wiki.
>
> merlin
>
>
Yeah, it would be nice to avoid another push back into July by getting some
feedback now for June; it would be great to squeeze it into May. I'm not
talking about a review, but maybe a couple of "I hate this" or "This works well"
or "Give up coding" :) The implementation of the events (as well as when they
were object hooks) is actually very simple, so a quick glance can probably raise
most concerns. There is very little going on.
In the end, something like this can be done several different ways (choice here
is a matter of taste & style). It would be nice to iron out the API and focus
on other aspects; namely is this general concept appealing enough.
--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-05-20 00:22 Tom Lane <tgl@sss.pgh.pa.us>
parent: Andrew Chernow <ac@esilo.com>
1 sibling, 2 replies; 15+ messages in thread
From: Tom Lane @ 2008-05-20 00:22 UTC (permalink / raw)
To: Andrew Chernow <ac@esilo.com>; +Cc: Merlin Moncure <mmoncure@gmail.com>; Andrew Dunstan <andrew@dunslane.net>; Alvaro Herrera <alvherre@commandprompt.com>; Patches (PostgreSQL) <pgsql-patches@postgresql.org>
Andrew Chernow <ac@esilo.com> writes:
> Here is an updated patch for what was called object hooks. This is now
> called libpq events. If someone has a better name or hates ours, let us
> know.
This is starting to get there, though I am still desperately unhappy
with the notion of "global" registrations. As I've said repeatedly,
I don't want that concept in any shape or form: there are no uses for
it that I regard as good ideas. (And taking out the mutex protection
for the global state isn't making it better, since that makes the
plausible use cases even narrower.)
> Terminology:
> I got rid of calling it object events because it is possible to add
> other non-object-related events in the future; maybe a query event.
> This system can be used for any type of event, I think it is pretty generic.
Hmm. "Event" for a callback seems a bit weird. The reasons for calling
the callback are events, and by extension the information structs you
pass to it could legitimately be called events, but the callback isn't
an event. "Event hook" or "event proc" seem possibly reasonable
nomenclature, though I'd still rather just go with "PGCallback".
Likewise I don't care for "event data", as the data inside that struct
is even less of an event than the proc is. "Per-callback instance data"
is the best description I can think of but it seems a bit awkward.
I've gone with "instance data" and "passthrough data" in the comments
below.
Some other comments in no particular order:
* I'm inclined to split out the hook-related stuff into a separate
header instead of cluttering libpq-fe.h with it, on the theory that
it has a separate audience; average applications will not be interested
in it.
* The proposed API passes the instance data pointer via the event struct
and the passthrough data pointer as a separate parameter. This seems
just weird. Shouldn't we do both the same way? This ties into my
original thought that the event data struct should be const, on the
grounds that you shouldn't have to initialize it more than once for all
the callbacks. But I'm not quite sure how you allocate a new instance
data value if that's the case. Maybe instead of having the ResultCreate
callback scribble on the event data structure, provide an additional
API routine to store the pointer:
PQresultSetInstanceData(PGresult *res, PGeventProc proc,
void *instanceData);
This would cost a couple extra cycles compared to what you have,
but surely not much in the normal case where there are very few
hooks registered. Also, meseems you need such a callback anyway:
what if the hook library desires to realloc its instance data
larger?
* Likewise it's weird to provide PQeventData and PQresultEventData
returning one pointer as function result and one as an output
argument. I'd suggest four functions returning results only,
perhaps
void *PQinstanceData(const PGconn *conn, PGEventProc proc)
void *PQpassthroughData(const PGconn *conn, PGEventProc proc)
void *PQresultInstanceData(const PGresult *res, PGEventProc proc)
void *PQresultPassthroughData(const PGresult *res, PGEventProc proc)
The way you have it might save a few cycles in the case where a client
needs both pointers, but I have a feeling that most callers will only
care about one or the other.
regards, tom lane
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-05-20 02:21 Andrew Chernow <ac@esilo.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 15+ messages in thread
From: Andrew Chernow @ 2008-05-20 02:21 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Merlin Moncure <mmoncure@gmail.com>; Andrew Dunstan <andrew@dunslane.net>; Alvaro Herrera <alvherre@commandprompt.com>; Patches (PostgreSQL) <pgsql-patches@postgresql.org>
Will make all of those changes. We appreciate the help.
1. remove global registration :(
2. New Name: PGCallback
3. use instanceData and passThrough names (passThrough with upper 'T')
3. separate getters for conn/result instanceData and passthrough
4. add a setter for result instance data
- There should also be a PQsetInstanceData(PGconn*, ...)
- I see no need for a passThrough setter
5. move callback stuff to its own header, maybe pgcallback.h?
No issue with any of them. Although, small issue below:
> Maybe instead of having the ResultCreate
> callback scribble on the event data structure, provide an additional
> API routine to store the pointer:
> PQresultSetInstanceData(PGresult *res, PGeventProc proc,
> void *instanceData);
Adding PQresultSetInstanceData doesn't removes the need for a resultcreate
callback event. This is an event the callbacks are informed about (instanceData
or not). It does remove the need for an instance data member in all event info
structures, just use the getter/setter when desired. If the passThrough is
needed, one can use the public getters.
> hooks registered. Also, meseems you need such a callback anyway:
> what if the hook library desires to realloc its instance data
> larger?
>
With your suggestions, this would work:
res = PQexec(conn, "blah");
data = PQresultInstanceData(res, cbfunc);
data = realloc(data, 1024);
PQresultSetInstanceData(res, cbfunc, data);
The API user should have a valid instanceData whenever libpq returns a result,
assuming they registered a callback that allocates instance data during a
resultcreate event.
--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-05-20 02:37 Tom Lane <tgl@sss.pgh.pa.us>
parent: Andrew Chernow <ac@esilo.com>
0 siblings, 2 replies; 15+ messages in thread
From: Tom Lane @ 2008-05-20 02:37 UTC (permalink / raw)
To: Andrew Chernow <ac@esilo.com>; +Cc: Merlin Moncure <mmoncure@gmail.com>; Andrew Dunstan <andrew@dunslane.net>; Alvaro Herrera <alvherre@commandprompt.com>; Patches (PostgreSQL) <pgsql-patches@postgresql.org>
Andrew Chernow <ac@esilo.com> writes:
> 4. add a setter for result instance data
> - There should also be a PQsetInstanceData(PGconn*, ...)
> - I see no need for a passThrough setter
Check, though I assume we're not expecting PQsetInstanceData to
propagate to previously created PGresults?
> 5. move callback stuff to its own header, maybe pgcallback.h?
Should be libpq-something. I was considering libpq-hooks.h or
libpq-events.h, but libpq-callback.h would be OK too.
> Adding PQresultSetInstanceData doesn't removes the need for a resultcreate
> callback event.
No, of course not. What I was imagining was that the resultcreate
callback would call PQresultSetInstanceData.
regards, tom lane
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-05-20 02:53 Merlin Moncure <mmoncure@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 0 replies; 15+ messages in thread
From: Merlin Moncure @ 2008-05-20 02:53 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Andrew Chernow <ac@esilo.com>; Andrew Dunstan <andrew@dunslane.net>; Alvaro Herrera <alvherre@commandprompt.com>; Patches (PostgreSQL) <pgsql-patches@postgresql.org>
On Mon, May 19, 2008 at 10:37 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Andrew Chernow <ac@esilo.com> writes:
>> 4. add a setter for result instance data
>> - There should also be a PQsetInstanceData(PGconn*, ...)
>> - I see no need for a passThrough setter
>
> Check, though I assume we're not expecting PQsetInstanceData to
> propagate to previously created PGresults?
>
>> 5. move callback stuff to its own header, maybe pgcallback.h?
>
> Should be libpq-something. I was considering libpq-hooks.h or
> libpq-events.h, but libpq-callback.h would be OK too.
I think 'events' best describes what is going on. Your other
suggestions are improvements (we maybe tried a little too hard to
minimize exports). Losing the global hooks is no problem (it was
basically syntax sugar).
merlin
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-05-20 03:03 Andrew Chernow <ac@esilo.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 15+ messages in thread
From: Andrew Chernow @ 2008-05-20 03:03 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Merlin Moncure <mmoncure@gmail.com>; Andrew Dunstan <andrew@dunslane.net>; Alvaro Herrera <alvherre@commandprompt.com>; Patches (PostgreSQL) <pgsql-patches@postgresql.org>
Tom Lane wrote:
> Andrew Chernow <ac@esilo.com> writes:
>> 4. add a setter for result instance data
>> - There should also be a PQsetInstanceData(PGconn*, ...)
>> - I see no need for a passThrough setter
>
> Check, though I assume we're not expecting PQsetInstanceData to
> propagate to previously created PGresults?
>
No, not at all. Already created results are on their own. If you want to
modify the instanceData, you can use PQresultSetInstanceData.
>> 5. move callback stuff to its own header, maybe pgcallback.h?
>
> Should be libpq-something. I was considering libpq-hooks.h or
> libpq-events.h, but libpq-callback.h would be OK too.
>
I like events. It sounds like you wanted PGCallback, although I am starting to
think PGEventProc is better than PGcallback, only because it is more consistent
with the term events.
BTW, my suggestion to call this libpq events was not directly referring to the
callback/proc. It was a term for describing the whole #! I think the idea is
to notify interested parties about libpq events, the callback is just an
implementaion for doing that.
>> Adding PQresultSetInstanceData doesn't removes the need for a resultcreate
>> callback event.
>
> No, of course not. What I was imagining was that the resultcreate
> callback would call PQresultSetInstanceData.
>
Sorry, my mistake. You were actually very clear, my reading skills are in question.
--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-05-20 04:28 Andrew Chernow <ac@esilo.com>
parent: Andrew Chernow <ac@esilo.com>
0 siblings, 1 reply; 15+ messages in thread
From: Andrew Chernow @ 2008-05-20 04:28 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Merlin Moncure <mmoncure@gmail.com>; Andrew Dunstan <andrew@dunslane.net>; Alvaro Herrera <alvherre@commandprompt.com>; Patches (PostgreSQL) <pgsql-patches@postgresql.org>
Attached is the latest patch. It has addressed the requested changes found
here: http://archives.postgresql.org/pgsql-patches/2008-05/msg00389.php
Its a tarball because there are two new files, libpq-events.c and
libpq-events.h. The patch is in the tarball as well as attached to the email.
--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/
Attachments:
libpq-events.tgz (application/x-compressed, 8.3K) download
libpq-events.patch (text/x-patch, 24.7K) download
Index: src/interfaces/libpq/Makefile
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/Makefile,v
retrieving revision 1.166
diff -C6 -r1.166 Makefile
*** src/interfaces/libpq/Makefile 16 Apr 2008 14:19:56 -0000 1.166
--- src/interfaces/libpq/Makefile 20 May 2008 04:18:07 -0000
***************
*** 29,41 ****
# the object files from libpgport, this would not be true on all
# platforms.
LIBS := $(LIBS:-lpgport=)
OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
! md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o \
$(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o win32error.o, $(LIBOBJS))
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
--- 29,41 ----
# the object files from libpgport, this would not be true on all
# platforms.
LIBS := $(LIBS:-lpgport=)
OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
! md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o libpq-events.o \
$(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o win32error.o, $(LIBOBJS))
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
***************
*** 103,114 ****
--- 103,115 ----
$(top_builddir)/src/port/pg_config_paths.h:
$(MAKE) -C $(top_builddir)/src/port pg_config_paths.h
install: all installdirs install-lib
$(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)'
+ $(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)'
$(INSTALL_DATA) $(srcdir)/libpq-int.h '$(DESTDIR)$(includedir_internal)'
$(INSTALL_DATA) $(srcdir)/pqexpbuffer.h '$(DESTDIR)$(includedir_internal)'
$(INSTALL_DATA) $(srcdir)/pg_service.conf.sample '$(DESTDIR)$(datadir)/pg_service.conf.sample'
installdirs: installdirs-lib
$(mkinstalldirs) '$(DESTDIR)$(includedir)' '$(DESTDIR)$(includedir_internal)'
Index: src/interfaces/libpq/exports.txt
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/exports.txt,v
retrieving revision 1.19
diff -C6 -r1.19 exports.txt
*** src/interfaces/libpq/exports.txt 19 Mar 2008 00:39:33 -0000 1.19
--- src/interfaces/libpq/exports.txt 20 May 2008 04:18:07 -0000
***************
*** 138,143 ****
--- 138,153 ----
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
+ PQinstanceData 146
+ PQsetInstanceData 147
+ PQresultInstanceData 148
+ PQresultSetInstanceData 149
+ PQpassThroughData 150
+ PQresultPassThroughData 151
\ 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.358
diff -C6 -r1.358 fe-connect.c
*** src/interfaces/libpq/fe-connect.c 16 May 2008 18:30:53 -0000 1.358
--- src/interfaces/libpq/fe-connect.c 20 May 2008 04:18:08 -0000
***************
*** 1970,1981 ****
--- 1970,1999 ----
* release data that is to be held for the life of the PGconn structure.
* If a value ought to be cleared/freed during PQreset(), do it there not here.
*/
static void
freePGconn(PGconn *conn)
{
+ int i;
+ PGEventConnDestroy evt;
+
+ /* Let the event procs cleanup their state data */
+ for(i=0; i < conn->nEvents; i++)
+ {
+ evt.conn = conn;
+ (void)conn->events[i].proc(PGEVT_CONNDESTROY, &evt);
+ }
+
+ /* free the PGEvent array */
+ if(conn->events)
+ {
+ free(conn->events);
+ conn->events = NULL;
+ conn->nEvents = conn->eventArrSize = 0;
+ }
+
if (conn->pghost)
free(conn->pghost);
if (conn->pghostaddr)
free(conn->pghostaddr);
if (conn->pgport)
free(conn->pgport);
***************
*** 2151,2164 ****
PQreset(PGconn *conn)
{
if (conn)
{
closePGconn(conn);
! if (connectDBStart(conn))
! (void) connectDBComplete(conn);
}
}
/*
* PQresetStart:
--- 2169,2199 ----
PQreset(PGconn *conn)
{
if (conn)
{
closePGconn(conn);
! if (connectDBStart(conn) && connectDBComplete(conn))
! {
! int i;
! PGEventConnReset evt;
!
! for(i=0; i < conn->nEvents; i++)
! {
! evt.conn = conn;
!
! if(!conn->events[i].proc(PGEVT_CONNRESET, &evt))
! {
! conn->status = CONNECTION_BAD;
! printfPQExpBuffer(&conn->errorMessage,
! libpq_gettext("PGEventProc %p failed during PGEVT_CONNRESET event\n"),
! conn->events[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.
--- 2221,2257 ----
* closes the existing connection and makes a new one
*/
PostgresPollingStatusType
PQresetPoll(PGconn *conn)
{
if (conn)
! {
! PostgresPollingStatusType status = PQconnectPoll(conn);
!
! if(status == PGRES_POLLING_OK)
! {
! int i;
! PGEventConnReset evt;
!
! for(i=0; i < conn->nEvents; i++)
! {
! evt.conn = conn;
!
! if(!conn->events[i].proc(PGEVT_CONNRESET, &evt))
! {
! conn->status = CONNECTION_BAD;
! printfPQExpBuffer(&conn->errorMessage,
! libpq_gettext("PGEventProc %p failed during PGEVT_CONNRESET event\n"),
! conn->events[i].proc);
! return PGRES_POLLING_FAILED;
! }
! }
! }
!
! return status;
! }
return PGRES_POLLING_FAILED;
}
/*
* PQcancelGet: get a PGcancel structure corresponding to a connection.
Index: src/interfaces/libpq/fe-exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v
retrieving revision 1.194
diff -C6 -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 20 May 2008 04:18:08 -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 event instance data, sets this to NULL */
+ static PGEvent *
+ dupEvents(PGEvent *events, int count)
+ {
+ int i;
+ PGEvent *newEvents;
+
+ if(!events || count <= 0)
+ return NULL;
+
+ newEvents = (PGEvent *)malloc(count * sizeof(PGEvent));
+ if(!newEvents)
+ return NULL;
+
+ memcpy(newEvents, events, count * sizeof(PGEvent));
+
+ /* NULL out the data pointer */
+ for(i=0; i < count; i++)
+ newEvents[i].data = NULL;
+
+ return newEvents;
+ }
+
/*
* PQmakeEmptyPGresult
* returns a newly allocated, initialized PGresult with given status.
* If conn is not NULL and status indicates an error, the conn's
* errorMessage is copied.
*
* Note this is exported --- you wouldn't think an application would need
* to build its own PGresults, but this has proven useful in both libpgtcl
* and the Perl5 interface, so maybe it's not so unreasonable.
+ *
+ * Updated April 2008 - If conn is not NULL, event states will be copied
+ * from the PGconn to the created PGresult.
*/
PGresult *
PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
{
PGresult *result;
***************
*** 157,175 ****
--- 183,216 ----
result->errMsg = NULL;
result->errFields = NULL;
result->null_field[0] = '\0';
result->curBlock = NULL;
result->curOffset = 0;
result->spaceLeft = 0;
+ result->nEvents = 0;
+ result->events = NULL;
if (conn)
{
/* copy connection data we might need for operations on PGresult */
result->noticeHooks = conn->noticeHooks;
result->client_encoding = conn->client_encoding;
+ /* copy events from connection */
+ if(conn->nEvents > 0)
+ {
+ result->events = dupEvents(conn->events, conn->nEvents);
+ if(!result->events)
+ {
+ PQclear(result);
+ return NULL;
+ }
+
+ result->nEvents = conn->nEvents;
+ }
+
/* consider copying conn's errorMessage */
switch (status)
{
case PGRES_EMPTY_QUERY:
case PGRES_COMMAND_OK:
case PGRES_TUPLES_OK:
***************
*** 193,204 ****
--- 234,481 ----
}
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_EVENTS - Indicates that the source result's
+ * events 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 evt;
+
+ 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 PGEvents. */
+ if(!(options & PG_COPYRES_NO_EVENTS) && src->nEvents > 0)
+ {
+ dest->events = dupEvents(src->events, src->nEvents);
+ if(!dest->events)
+ {
+ PQclear(dest);
+ return NULL;
+ }
+
+ dest->nEvents = src->nEvents;
+ }
+
+ /* Trigger PGEVT_RESULTCOPY event */
+ for(i=0; i < dest->nEvents; i++)
+ {
+ evt.src = src;
+ evt.dest = dest;
+ if(!dest->events[i].proc(PGEVT_RESULTCOPY, &evt))
+ {
+ PQclear(dest);
+ return NULL;
+ }
+ }
+
+ return dest;
+ }
+
+ int
+ PQsetvalue(PGresult *res, int tup_num, int field_num,
+ char *value, int len)
+ {
+ PGresAttValue *attval;
+
+ if(!check_field_number(res, field_num))
+ return FALSE;
+
+ /* Invalid tup_num, must be <= ntups */
+ if(tup_num > res->ntups)
+ return FALSE;
+
+ /* need to grow the tuple table */
+ if(res->ntups >= res->tupArrSize)
+ {
+ int n = res->tupArrSize ? (res->tupArrSize*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 ****
--- 626,657 ----
* PQclear -
* free's the memory associated with a PGresult
*/
void
PQclear(PGresult *res)
{
+ int i;
PGresult_data *block;
+ PGEventResultDestroy evt;
if (!res)
return;
+ for(i=0; i < res->nEvents; i++)
+ {
+ evt.result = res;
+ (void)res->events[i].proc(PGEVT_RESULTDESTROY, &evt);
+ }
+
+ if(res->events)
+ {
+ free(res->events);
+ res->events = NULL;
+ res->nEvents = 0;
+ }
+
/* Free all the subsidiary blocks */
while ((block = res->curBlock) != NULL)
{
res->curBlock = block->next;
free(block);
}
***************
*** 1190,1202 ****
* memory).
*/
PGresult *
PQgetResult(PGconn *conn)
{
! PGresult *res;
if (!conn)
return NULL;
/* Parse any available data, if our state permits. */
parseInput(conn);
--- 1482,1494 ----
* memory).
*/
PGresult *
PQgetResult(PGconn *conn)
{
! PGresult *res=NULL;
if (!conn)
return NULL;
/* Parse any available data, if our state permits. */
parseInput(conn);
***************
*** 1265,1276 ****
--- 1557,1593 ----
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 evt;
+
+ for(i=0; i < res->nEvents; i++)
+ {
+ evt.conn = conn;
+ evt.result = res;
+
+ if(!res->events[i].proc(PGEVT_RESULTCREATE, &evt))
+ {
+ char msg[256];
+ sprintf(msg,
+ "PGEventProc %p failed during PGEVT_RESULTCREATE event",
+ res->events[i].proc);
+ pqSetResultError(res, msg);
+ res->resultStatus = PGRES_FATAL_ERROR;
+ break;
+ }
+ }
+ }
+
return res;
}
/*
* PQexec
Index: src/interfaces/libpq/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
*** src/interfaces/libpq/fe-misc.c 1 Jan 2008 19:46:00 -0000 1.133
--- src/interfaces/libpq/fe-misc.c 20 May 2008 04:18:09 -0000
***************
*** 1152,1157 ****
--- 1152,1158 ----
}
return dgettext("libpq", msgid);
}
#endif /* ENABLE_NLS */
+
Index: src/interfaces/libpq/libpq-fe.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-fe.h,v
retrieving revision 1.142
diff -C6 -r1.142 libpq-fe.h
*** src/interfaces/libpq/libpq-fe.h 19 Mar 2008 00:39:33 -0000 1.142
--- src/interfaces/libpq/libpq-fe.h 20 May 2008 04:18:09 -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_EVENTS 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,225 ----
int *ptr; /* can't use void (dec compiler barfs) */
int integer;
} u;
} PQArgBlock;
/* ----------------
+ * PGresAttDesc -- Data about a single attribute (column) of a query result
+ * ----------------
+ */
+ typedef struct pgresAttDesc
+ {
+ char *name; /* column name */
+ Oid tableid; /* source table, if known */
+ int columnid; /* source column, if known */
+ int format; /* format code for value (text/binary) */
+ Oid typid; /* type id */
+ int typlen; /* type size */
+ int atttypmod; /* type-specific modifier info */
+ } PGresAttDesc;
+
+ /* ----------------
* Exported functions of libpq
* ----------------
*/
/* === in fe-connect.c === */
***************
*** 434,445 ****
--- 458,488 ----
* 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,
Index: src/interfaces/libpq/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
*** src/interfaces/libpq/libpq-int.h 16 May 2008 18:30:53 -0000 1.130
--- src/interfaces/libpq/libpq-int.h 20 May 2008 04:18:09 -0000
***************
*** 19,30 ****
--- 19,31 ----
#ifndef LIBPQ_INT_H
#define LIBPQ_INT_H
/* We assume libpq-fe.h has already been included. */
#include "postgres_fe.h"
+ #include "libpq-events.h"
#include <time.h>
#include <sys/types.h>
#ifndef WIN32
#include <sys/time.h>
#endif
***************
*** 97,121 ****
union pgresult_data
{
PGresult_data *next; /* link to next block, or NULL */
char space[1]; /* dummy for accessing block as bytes */
};
- /* Data about a single attribute (column) of a query result */
-
- typedef struct pgresAttDesc
- {
- char *name; /* column name */
- Oid tableid; /* source table, if known */
- int columnid; /* source column, if known */
- int format; /* format code for value (text/binary) */
- Oid typid; /* type id */
- int typlen; /* type size */
- int atttypmod; /* type-specific modifier info */
- } PGresAttDesc;
-
/* Data about a single parameter of a prepared statement */
typedef struct pgresParamDesc
{
Oid typid; /* type id */
} PGresParamDesc;
--- 98,109 ----
***************
*** 159,170 ****
--- 147,167 ----
PQnoticeReceiver noticeRec; /* notice message receiver */
void *noticeRecArg;
PQnoticeProcessor noticeProc; /* notice message processor */
void *noticeProcArg;
} PGNoticeHooks;
+ typedef struct
+ {
+ /* pointer supplied by user */
+ void *passThrough;
+ /* state data, optionally generated by event proc */
+ void *data;
+ PGEventProc proc;
+ } PGEvent;
+
struct pg_result
{
int ntups;
int numAttributes;
PGresAttDesc *attDescs;
PGresAttValue **tuples; /* each PGresTuple is an array of
***************
*** 181,192 ****
--- 178,193 ----
* These fields are copied from the originating PGconn, so that operations
* on the PGresult don't have to reference the PGconn.
*/
PGNoticeHooks noticeHooks;
int client_encoding; /* encoding id */
+ /* registered events, copied from conn */
+ int nEvents;
+ PGEvent *events;
+
/*
* Error information (all NULL if not an error result). errMsg is the
* "overall" error message returned by PQresultErrorMessage. If we have
* per-field info then it is stored in a linked list.
*/
char *errMsg; /* error message, or NULL if no error */
***************
*** 300,311 ****
--- 301,317 ----
/* Optional file to write trace info to */
FILE *Pfdebug;
/* Callback procedures for notice message processing */
PGNoticeHooks noticeHooks;
+ /* registered events via PQregisterEventProc */
+ int nEvents;
+ int eventArrSize;
+ PGEvent *events;
+
/* Status indicators */
ConnStatusType status;
PGAsyncStatusType asyncStatus;
PGTransactionStatusType xactStatus; /* never changes to ACTIVE */
PGQueryClass queryclass;
char *last_query; /* last SQL command, or NULL if unknown */
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-05-20 17:29 Merlin Moncure <mmoncure@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 0 replies; 15+ messages in thread
From: Merlin Moncure @ 2008-05-20 17:29 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Andrew Chernow <ac@esilo.com>; Andrew Dunstan <andrew@dunslane.net>; Alvaro Herrera <alvherre@commandprompt.com>; Patches (PostgreSQL) <pgsql-patches@postgresql.org>
On Mon, May 19, 2008 at 8:22 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Andrew Chernow <ac@esilo.com> writes:
>> Here is an updated patch for what was called object hooks. This is now
>> called libpq events. If someone has a better name or hates ours, let us
>> know.
>
> This is starting to get there,
Interesting you haven't commented on two areas, the actual placement
of the event callers in the libpq code (i'm assuming these are ok),
and the PQcopyResult/PQsetvalue functions. The latter area introduces
some new behavior into standard libpq (imo) so it deserves some
scrutiny.
merlin
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-09-03 00:19 Alvaro Herrera <alvherre@commandprompt.com>
parent: Andrew Chernow <ac@esilo.com>
0 siblings, 2 replies; 15+ messages in thread
From: Alvaro Herrera @ 2008-09-03 00:19 UTC (permalink / raw)
To: Andrew Chernow <ac@esilo.com>; +Cc: Merlin Moncure <mmoncure@gmail.com>; pgsql-hackers
Andrew Chernow escribió:
> Attached is the latest patch. It has addressed the requested changes
> found here:
> http://archives.postgresql.org/pgsql-patches/2008-05/msg00389.php
>
> Its a tarball because there are two new files, libpq-events.c and
> libpq-events.h. The patch is in the tarball as well as attached to the
> email.
I modified this patch slightly. I was about to try libpqtypes on it,
but then I noticed that libpqtypes as published on pgfoundry is based on
a very old version of this patch, so I punted. So, for now, the only
guarantee is that it compiles with no warnings.
However, the only change of any significance that I introduced was that
a "name" is attached to every event proc, so that it can be reported in
error messages, as reporting only %p seems very useless. (I also
removed PQresultAlloc.)
The API seems reasonable to me.
There's one thing that seems a bit baroque, which is the
PG_COPYRES_USE_ATTRS stuff in PQcopyResult. I think that flag
introduces different enough behavior that it should be a routine of its
own, say PQcopyResultAttrs. That way you would leave out the two extra
params in PQcopyResult.
Oh -- one last thing. I am not really sure about the flags to
PQcopyResult. Should there really be flags to _remove_ behavior,
instead of flags that add? i.e. instead of having "0" copy everything,
and have to pass flags for things not to copy, wouldn't it be cleaner to
have 0 copy only base stuff, and require flags to copy extra things?
The main missing thing from this patch is SGML docs for the new libpq
functions.
--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.
Attachments:
pqevents.patch (text/x-diff, 21.3K) download
Index: src/interfaces/libpq/Makefile
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/interfaces/libpq/Makefile,v
retrieving revision 1.166
diff -c -p -r1.166 Makefile
*** src/interfaces/libpq/Makefile 16 Apr 2008 14:19:56 -0000 1.166
--- src/interfaces/libpq/Makefile 2 Sep 2008 21:54:47 -0000
*************** LIBS := $(LIBS:-lpgport=)
*** 32,38 ****
OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
! md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o \
$(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o win32error.o, $(LIBOBJS))
ifeq ($(PORTNAME), cygwin)
--- 32,38 ----
OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
! md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o libpq-events.o \
$(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o win32error.o, $(LIBOBJS))
ifeq ($(PORTNAME), cygwin)
*************** $(top_builddir)/src/port/pg_config_paths
*** 106,111 ****
--- 106,112 ----
install: all installdirs install-lib
$(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)'
+ $(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)'
$(INSTALL_DATA) $(srcdir)/libpq-int.h '$(DESTDIR)$(includedir_internal)'
$(INSTALL_DATA) $(srcdir)/pqexpbuffer.h '$(DESTDIR)$(includedir_internal)'
$(INSTALL_DATA) $(srcdir)/pg_service.conf.sample '$(DESTDIR)$(datadir)/pg_service.conf.sample'
*************** installdirs: installdirs-lib
*** 114,120 ****
$(mkinstalldirs) '$(DESTDIR)$(includedir)' '$(DESTDIR)$(includedir_internal)'
uninstall: uninstall-lib
! rm -f '$(DESTDIR)$(includedir)/libpq-fe.h' '$(DESTDIR)$(includedir_internal)/libpq-int.h' '$(DESTDIR)$(includedir_internal)/pqexpbuffer.h' '$(DESTDIR)$(datadir)/pg_service.conf.sample'
clean distclean: clean-lib
rm -f $(OBJS) pg_config_paths.h crypt.c getaddrinfo.c inet_aton.c noblock.c open.c pgstrcasecmp.c snprintf.c strerror.c strlcpy.c thread.c md5.c ip.c encnames.c wchar.c win32error.c pgsleep.c pthread.h libpq.rc
--- 115,121 ----
$(mkinstalldirs) '$(DESTDIR)$(includedir)' '$(DESTDIR)$(includedir_internal)'
uninstall: uninstall-lib
! rm -f '$(DESTDIR)$(includedir)/libpq-fe.h' '$(DESTDIR)$(includedir)/libpq-events.h' '$(DESTDIR)$(includedir_internal)/libpq-int.h' '$(DESTDIR)$(includedir_internal)/pqexpbuffer.h' '$(DESTDIR)$(datadir)/pg_service.conf.sample'
clean distclean: clean-lib
rm -f $(OBJS) pg_config_paths.h crypt.c getaddrinfo.c inet_aton.c noblock.c open.c pgstrcasecmp.c snprintf.c strerror.c strlcpy.c thread.c md5.c ip.c encnames.c wchar.c win32error.c pgsleep.c pthread.h libpq.rc
Index: src/interfaces/libpq/exports.txt
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/interfaces/libpq/exports.txt,v
retrieving revision 1.19
diff -c -p -r1.19 exports.txt
*** src/interfaces/libpq/exports.txt 19 Mar 2008 00:39:33 -0000 1.19
--- src/interfaces/libpq/exports.txt 2 Sep 2008 22:41:17 -0000
*************** PQconnectionUsedPassword 138
*** 141,143 ****
--- 141,152 ----
pg_valid_server_encoding_id 139
PQconnectionNeedsPassword 140
lo_import_with_oid 141
+ PQcopyResult 142
+ PQsetvalue 143
+ PQregisterEventProc 144
+ PQinstanceData 145
+ PQsetInstanceData 146
+ PQresultInstanceData 147
+ PQresultSetInstanceData 148
+ PQpassThroughData 149
+ PQresultPassThroughData 150
Index: src/interfaces/libpq/fe-connect.c
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/interfaces/libpq/fe-connect.c,v
retrieving revision 1.359
diff -c -p -r1.359 fe-connect.c
*** src/interfaces/libpq/fe-connect.c 29 May 2008 22:02:44 -0000 1.359
--- src/interfaces/libpq/fe-connect.c 2 Sep 2008 23:22:02 -0000
*************** makeEmptyPGconn(void)
*** 1974,1979 ****
--- 1974,1998 ----
static void
freePGconn(PGconn *conn)
{
+ int i;
+ PGEventConnDestroy evt;
+
+ /* Let the event procs cleanup their state data */
+ for (i = 0; i < conn->nEvents; i++)
+ {
+ evt.conn = conn;
+ (void)conn->events[i].proc(PGEVT_CONNDESTROY, &evt);
+ free(conn->events[i].name);
+ }
+
+ /* free the PGEvent array */
+ if (conn->events)
+ {
+ free(conn->events);
+ conn->events = NULL;
+ conn->nEvents = conn->eventArrSize = 0;
+ }
+
if (conn->pghost)
free(conn->pghost);
if (conn->pghostaddr)
*************** PQreset(PGconn *conn)
*** 2155,2162 ****
{
closePGconn(conn);
! if (connectDBStart(conn))
! (void) connectDBComplete(conn);
}
}
--- 2174,2198 ----
{
closePGconn(conn);
! if (connectDBStart(conn) && connectDBComplete(conn))
! {
! int i;
! PGEventConnReset evt;
!
! for (i = 0; i < conn->nEvents; i++)
! {
! evt.conn = conn;
!
! if(!conn->events[i].proc(PGEVT_CONNRESET, &evt))
! {
! conn->status = CONNECTION_BAD;
! printfPQExpBuffer(&conn->errorMessage,
! libpq_gettext("PGEvent \"%s\" failed during PGEVT_CONNRESET event\n"),
! conn->events[i].name);
! break;
! }
! }
! }
}
}
*************** PostgresPollingStatusType
*** 2190,2196 ****
PQresetPoll(PGconn *conn)
{
if (conn)
! return PQconnectPoll(conn);
return PGRES_POLLING_FAILED;
}
--- 2226,2256 ----
PQresetPoll(PGconn *conn)
{
if (conn)
! {
! PostgresPollingStatusType status = PQconnectPoll(conn);
!
! if (status == PGRES_POLLING_OK)
! {
! int i;
! PGEventConnReset evt;
!
! for (i = 0; i < conn->nEvents; i++)
! {
! evt.conn = conn;
!
! if (!conn->events[i].proc(PGEVT_CONNRESET, &evt))
! {
! conn->status = CONNECTION_BAD;
! printfPQExpBuffer(&conn->errorMessage,
! libpq_gettext("PGEvent \"%s\" failed during PGEVT_CONNRESET event\n"),
! conn->events[i].name);
! return PGRES_POLLING_FAILED;
! }
! }
! }
!
! return status;
! }
return PGRES_POLLING_FAILED;
}
Index: src/interfaces/libpq/fe-exec.c
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/interfaces/libpq/fe-exec.c,v
retrieving revision 1.196
diff -c -p -r1.196 fe-exec.c
*** src/interfaces/libpq/fe-exec.c 23 Jun 2008 21:10:49 -0000 1.196
--- src/interfaces/libpq/fe-exec.c 2 Sep 2008 23:21:12 -0000
*************** static bool PQexecStart(PGconn *conn);
*** 63,68 ****
--- 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);
/* ----------------
*************** static int PQsendDescribe(PGconn *conn,
*** 125,134 ****
/*
* 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
--- 126,163 ----
/*
+ * Creates a copy of a PGEvent array. The event proc name is not allocated
+ * separately.
+ *
+ * Note that it doesn't duplicate the event instance data; it is left NULL.
+ */
+ static PGEvent *
+ dupEvents(PGEvent *events, int count)
+ {
+ int i;
+ PGEvent *newEvents;
+
+ if (!events || count <= 0)
+ return NULL;
+
+ newEvents = (PGEvent *) malloc(count * sizeof(PGEvent));
+ if (!newEvents)
+ return NULL;
+
+ memcpy(newEvents, events, count * sizeof(PGEvent));
+
+ /* NULL out the data pointer */
+ for (i = 0; i < count; i++)
+ newEvents[i].data = NULL;
+
+ return newEvents;
+ }
+
+ /*
* PQmakeEmptyPGresult
* returns a newly allocated, initialized PGresult with given status.
* If conn is not NULL and status indicates an error, the conn's
! * errorMessage and events state are 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
*************** PQmakeEmptyPGresult(PGconn *conn, ExecSt
*** 160,165 ****
--- 189,196 ----
result->curBlock = NULL;
result->curOffset = 0;
result->spaceLeft = 0;
+ result->nEvents = 0;
+ result->events = NULL;
if (conn)
{
*************** PQmakeEmptyPGresult(PGconn *conn, ExecSt
*** 167,172 ****
--- 198,216 ----
result->noticeHooks = conn->noticeHooks;
result->client_encoding = conn->client_encoding;
+ /* copy events from connection */
+ if (conn->nEvents > 0)
+ {
+ result->events = dupEvents(conn->events, conn->nEvents);
+ if (!result->events)
+ {
+ PQclear(result);
+ return NULL;
+ }
+
+ result->nEvents = conn->nEvents;
+ }
+
/* consider copying conn's errorMessage */
switch (status)
{
*************** PQmakeEmptyPGresult(PGconn *conn, ExecSt
*** 196,201 ****
--- 240,477 ----
}
/*
+ * 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_EVENTS - Indicates that the source result's
+ * events 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 evt;
+
+ 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), TRUE);
+
+ 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 PGEvents. */
+ if (!(options & PG_COPYRES_NO_EVENTS) && src->nEvents > 0)
+ {
+ dest->events = dupEvents(src->events, src->nEvents);
+ if (!dest->events)
+ {
+ PQclear(dest);
+ return NULL;
+ }
+
+ dest->nEvents = src->nEvents;
+ }
+
+ /* Trigger PGEVT_RESULTCOPY event */
+ for (i = 0; i < dest->nEvents; i++)
+ {
+ evt.src = src;
+ evt.dest = dest;
+ if (!dest->events[i].proc(PGEVT_RESULTCOPY, &evt))
+ {
+ PQclear(dest);
+ return NULL;
+ }
+ }
+
+ return dest;
+ }
+
+ int
+ PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len)
+ {
+ PGresAttValue *attval;
+
+ if (!check_field_number(res, field_num))
+ return FALSE;
+
+ /* Invalid tup_num, must be <= ntups */
+ if (tup_num > res->ntups)
+ return FALSE;
+
+ /* need to grow the tuple table */
+ if (res->ntups >= res->tupArrSize)
+ {
+ int n = res->tupArrSize ? res->tupArrSize * 2 : 128;
+ PGresAttValue **tups;
+
+ if (res->tuples)
+ tups = realloc(res->tuples, n * sizeof(PGresAttValue *));
+ else
+ tups = malloc(n * sizeof(PGresAttValue *));
+
+ if (!tups)
+ return FALSE;
+
+ memset(tups + res->tupArrSize, 0,
+ (n - res->tupArrSize) * sizeof(PGresAttValue *));
+ res->tuples = tups;
+ res->tupArrSize = n;
+ }
+
+ /* need to allocate a new tuple */
+ if (tup_num == res->ntups && !res->tuples[tup_num])
+ {
+ int i;
+ PGresAttValue *tup;
+
+ tup = (PGresAttValue *) pqResultAlloc(res, res->numAttributes * sizeof(PGresAttValue), TRUE);
+
+ if (!tup)
+ return FALSE;
+
+ /* initialize each column to NULL */
+ for (i = 0; i < res->numAttributes; i++)
+ {
+ tup[i].len = NULL_LEN;
+ tup[i].value = res->null_field;
+ }
+
+ res->tuples[tup_num] = tup;
+ res->ntups++;
+ }
+
+ attval = &res->tuples[tup_num][field_num];
+
+ /* On top of NULL_LEN, treat a NULL value as a NULL field */
+ if (len == NULL_LEN || value == NULL)
+ {
+ attval->len = NULL_LEN;
+ attval->value = res->null_field;
+ }
+ else
+ {
+ if (len < 0)
+ len = 0;
+
+ if (len == 0)
+ {
+ attval->len = 0;
+ attval->value = res->null_field;
+ }
+ else
+ {
+ attval->value = (char *) pqResultAlloc(res, len + 1, 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.
*
*************** pqCatenateResultError(PGresult *res, con
*** 352,362 ****
--- 628,655 ----
void
PQclear(PGresult *res)
{
+ int i;
PGresult_data *block;
+ PGEventResultDestroy evt;
if (!res)
return;
+ /* invoke each event's destroy routine */
+ for (i = 0; i < res->nEvents; i++)
+ {
+ evt.result = res;
+ (void) res->events[i].proc(PGEVT_RESULTDESTROY, &evt);
+ }
+
+ /* free the event array */
+ if (res->events)
+ {
+ free(res->events);
+ res->events = NULL;
+ res->nEvents = 0;
+ }
+
/* Free all the subsidiary blocks */
while ((block = res->curBlock) != NULL)
{
*************** PQgetResult(PGconn *conn)
*** 1270,1275 ****
--- 1563,1594 ----
break;
}
+ if (res && res->nEvents > 0 &&
+ (res->resultStatus == PGRES_COMMAND_OK ||
+ res->resultStatus == PGRES_TUPLES_OK ||
+ res->resultStatus == PGRES_EMPTY_QUERY))
+ {
+ int i;
+ PGEventResultCreate evt;
+
+ for (i = 0; i < res->nEvents; i++)
+ {
+ evt.conn = conn;
+ evt.result = res;
+
+ if (!res->events[i].proc(PGEVT_RESULTCREATE, &evt))
+ {
+ char msg[256];
+ sprintf(msg,
+ "PGEvent \"%s\" failed during PGEVT_RESULTCREATE event",
+ res->events[i].name);
+ pqSetResultError(res, msg);
+ res->resultStatus = PGRES_FATAL_ERROR;
+ break;
+ }
+ }
+ }
+
return res;
}
Index: src/interfaces/libpq/libpq-fe.h
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/interfaces/libpq/libpq-fe.h,v
retrieving revision 1.142
diff -c -p -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 2 Sep 2008 23:23:48 -0000
*************** extern "C"
*** 28,33 ****
--- 28,42 ----
*/
#include "postgres_ext.h"
+ /* -----------------------
+ * Options for PQcopyResult
+ */
+
+ #define PG_COPYRES_NO_TUPLES 0x01
+ #define PG_COPYRES_USE_ATTRS 0x02
+ #define PG_COPYRES_NO_EVENTS 0x04
+ #define PG_COPYRES_NO_NOTICEHOOKS 0x08
+
/* Application-visible enum types */
typedef enum
*************** typedef struct
*** 193,198 ****
--- 202,222 ----
} 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
* ----------------
*/
*************** extern void PQfreemem(void *ptr);
*** 437,442 ****
--- 461,481 ----
*/
extern PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status);
+ extern PGresult *
+ PQcopyResult(const PGresult *src, int numAttributes,
+ PGresAttDesc *attDescs, int options);
+
+ /*
+ * 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,
Index: src/interfaces/libpq/libpq-int.h
===================================================================
RCS file: /home/alvherre/Code/cvs/pgsql/src/interfaces/libpq/libpq-int.h,v
retrieving revision 1.131
diff -c -p -r1.131 libpq-int.h
*** src/interfaces/libpq/libpq-int.h 29 May 2008 22:02:44 -0000 1.131
--- src/interfaces/libpq/libpq-int.h 2 Sep 2008 23:24:39 -0000
***************
*** 22,27 ****
--- 22,28 ----
/* We assume libpq-fe.h has already been included. */
#include "postgres_fe.h"
+ #include "libpq-events.h"
#include <time.h>
#include <sys/types.h>
*************** union pgresult_data
*** 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
{
--- 101,106 ----
*************** typedef struct
*** 162,167 ****
--- 150,164 ----
void *noticeProcArg;
} PGNoticeHooks;
+ /* Fields needed for PGEvent processing */
+ typedef struct
+ {
+ char *name; /* for error messages */
+ void *passThrough; /* pointer supplied by user */
+ void *data; /* state data, optionally generated by event proc */
+ PGEventProc proc; /* the function to call on events */
+ } PGEvent;
+
struct pg_result
{
int ntups;
*************** struct pg_result
*** 184,189 ****
--- 181,190 ----
PGNoticeHooks noticeHooks;
int client_encoding; /* encoding id */
+ /* registered events, copied from conn */
+ int nEvents;
+ PGEvent *events;
+
/*
* Error information (all NULL if not an error result). errMsg is the
* "overall" error message returned by PQresultErrorMessage. If we have
*************** struct pg_conn
*** 303,308 ****
--- 304,314 ----
/* Callback procedures for notice message processing */
PGNoticeHooks noticeHooks;
+ /* registered events via PQregisterEventProc */
+ int nEvents;
+ int eventArrSize;
+ PGEvent *events;
+
/* Status indicators */
ConnStatusType status;
PGAsyncStatusType asyncStatus;
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-09-03 03:27 Andrew Chernow <ac@esilo.com>
parent: Alvaro Herrera <alvherre@commandprompt.com>
1 sibling, 1 reply; 15+ messages in thread
From: Andrew Chernow @ 2008-09-03 03:27 UTC (permalink / raw)
To: Alvaro Herrera <alvherre@commandprompt.com>; +Cc: Merlin Moncure <mmoncure@gmail.com>; pgsql-hackers
Alvaro Herrera wrote:
> Andrew Chernow escribió:
>> Attached is the latest patch. It has addressed the requested changes
>> found here:
>> http://archives.postgresql.org/pgsql-patches/2008-05/msg00389.php
>>
>> Its a tarball because there are two new files, libpq-events.c and
>> libpq-events.h. The patch is in the tarball as well as attached to the
>> email.
>
> I modified this patch slightly. I was about to try libpqtypes on it,
> but then I noticed that libpqtypes as published on pgfoundry is based on
> a very old version of this patch, so I punted. So, for now, the only
> guarantee is that it compiles with no warnings.
>
Being that it is now on pgfoundry, we didn't want to make drastic changes. We
wanted to wait to see how this patch unfolds and make a major change then.
"event" version of libpqtypes (not on foundry yet)
http://libpqtypes.esilo.com/libpqtypes-events.tar.gz
> (I also removed PQresultAlloc.)
>
Nooooooo ... removing PQresultAlloc breaks libpqtypes! It also removes some of
the use cases provided by PQsetvalue, which allows one to add to a result (in
our case from scratch).
> There's one thing that seems a bit baroque, which is the
> PG_COPYRES_USE_ATTRS stuff in PQcopyResult. I think that flag
> introduces different enough behavior that it should be a routine of its
> own, say PQcopyResultAttrs. That way you would leave out the two extra
> params in PQcopyResult.
>
Okay. We can move attr copying to its own function.
> Oh -- one last thing. I am not really sure about the flags to
> PQcopyResult. Should there really be flags to _remove_ behavior,
> instead of flags that add?
>
Agreed, that is kinda weird. Probably ended up that because the flags were an
after thought.
> However, the only change of any significance that I introduced was that
> a "name" is attached to every event proc, so that it can be reported in
> error messages, as reporting only %p seems very useless. (I also
> removed PQresultAlloc.)
I don't mind re-introducing the name, but Tom seemed very against this due to
conflicts. If 2 different libraries register the same name, debugging would be
painful.
> The main missing thing from this patch is SGML docs for the
> new libpq functions.
We have no issue here. We are waiting for the patch to gain acceptance so we
only document this once.
--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-09-03 14:09 Andrew Chernow <ac@esilo.com>
parent: Alvaro Herrera <alvherre@commandprompt.com>
1 sibling, 0 replies; 15+ messages in thread
From: Andrew Chernow @ 2008-09-03 14:09 UTC (permalink / raw)
To: Alvaro Herrera <alvherre@commandprompt.com>; +Cc: Merlin Moncure <mmoncure@gmail.com>; pgsql-hackers
Alvaro Herrera wrote:
>
> There's one thing that seems a bit baroque, which is the
> PG_COPYRES_USE_ATTRS stuff in PQcopyResult. I think that flag
> introduces different enough behavior that it should be a routine of its
> own, say PQcopyResultAttrs. That way you would leave out the two extra
> params in PQcopyResult.
>
> Oh -- one last thing. I am not really sure about the flags to
> PQcopyResult. Should there really be flags to _remove_ behavior,
> instead of flags that add? i.e. instead of having "0" copy everything,
> and have to pass flags for things not to copy, wouldn't it be cleaner to
> have 0 copy only base stuff, and require flags to copy extra things?
>
> a "name" is attached to every event proc, so that it can be
> reported in error messages
>
Can someone confirm that an event 'name' should be re-introduced, as
suggested by Alvaro?
Can I get a happy or sad face in regards to below?
New options which add instead of remove.
#define PG_COPYRES_ATTRS 0x01
#define PG_COPYRES_TUPLES 0x02 /* Implies PG_COPYRES_ATTRS */
#define PG_COPYRES_EVENTS 0x04
#define PG_COPYRES_NOTICEHOOKS 0x08
// tuples implies attrs, you need the attrs to copy the tuples.
if(options & PG_COPYRES_TUPLES)
options |= PG_COPYRES_ATTRS; // auto set option
In regards to copying the attrs, the PQcopyResult still needs the
ability to copy the source result's attrs. Although, it doesn't need
the ability to provide custom attrs (which I removed). New prototype
for copyresult:
PGresult *
PQcopyResult(const PGresult *src, int options);
I then added a PQsetResultAttrs. copyresultattrs didn't seem like the
correct name because you are no longer copying attrs from a source
result. You are providing the attrs to 'set'.
int
PQsetResultAttrs(PGresult *res, int numAttributes,
PGresAttDesc *attDescs);
If the result provided to setattrs already contains attributes, I have
the function failing (can't overwrite existing attrs). I think this is
good behavior....
When PQcopyResult needs to copy the source result's attrs, it calls
PQsetResultAttrs.
/* Wants attrs */
if((options & PG_COPYRES_ATTRS) &&
!PQsetResultAttrs(dest, src->numAttributes, src->attDescs))
{
PQclear(dest);
return NULL;
}
So, there is some nice code reuse which indicates to me the code is
segmented well (copyres & setattrs).
--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-09-03 16:55 Alvaro Herrera <alvherre@commandprompt.com>
parent: Andrew Chernow <ac@esilo.com>
0 siblings, 1 reply; 15+ messages in thread
From: Alvaro Herrera @ 2008-09-03 16:55 UTC (permalink / raw)
To: Andrew Chernow <ac@esilo.com>; +Cc: Merlin Moncure <mmoncure@gmail.com>; pgsql-hackers
Andrew Chernow escribió:
> Alvaro Herrera wrote:
>> (I also removed PQresultAlloc.)
>
> Nooooooo ... removing PQresultAlloc breaks libpqtypes! It also removes
> some of the use cases provided by PQsetvalue, which allows one to add to
> a result (in our case from scratch).
I don't really see the point -- it's the same as pqResultAlloc, except
that you have to pass an extra argument. There's no actual
functionality loss.
> > However, the only change of any significance that I introduced was that
> > a "name" is attached to every event proc, so that it can be reported in
> > error messages, as reporting only %p seems very useless. (I also
> > removed PQresultAlloc.)
>
> I don't mind re-introducing the name, but Tom seemed very against this
> due to conflicts. If 2 different libraries register the same name,
> debugging would be painful.
Hmm, is that really a good argument? I don't see how providing a
pointer is a better answer than a name -- it's far less user friendly.
Let's start assuming that no duplicate names would be used, and if there
are any conflicts in the real world, we can just ask the user to fire up
GDB to figure out where each name is pointing to. My guess is that
conflicting names will be a rarity, if we ever get to see them. (Would
two different libraries call themselves "pqtypes", for example?)
--
Alvaro Herrera http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
^ permalink raw reply [nested|flat] 15+ messages in thread
* Re: libpq object hooks (libpq events)
@ 2008-09-03 17:14 Andrew Chernow <ac@esilo.com>
parent: Alvaro Herrera <alvherre@commandprompt.com>
0 siblings, 0 replies; 15+ messages in thread
From: Andrew Chernow @ 2008-09-03 17:14 UTC (permalink / raw)
To: Alvaro Herrera <alvherre@commandprompt.com>; +Cc: Merlin Moncure <mmoncure@gmail.com>; pgsql-hackers
Alvaro Herrera wrote:
> Andrew Chernow escribió:
>> Alvaro Herrera wrote:
>
>>> (I also removed PQresultAlloc.)
>> Nooooooo ... removing PQresultAlloc breaks libpqtypes! It also removes
>> some of the use cases provided by PQsetvalue, which allows one to add to
>> a result (in our case from scratch).
>
> I don't really see the point -- it's the same as pqResultAlloc, except
> that you have to pass an extra argument. There's no actual
> functionality loss.
>
libpqtypes uses it. libpqtypes doesn't have access to any internals of
libpq, including pqResultAlloc. So, I made a public wrapper to the
internal version. The point is to provide public access to the result
allocator.
--
Andrew Chernow
eSilo, LLC
every bit counts
http://www.esilo.com/
^ permalink raw reply [nested|flat] 15+ messages in thread
end of thread, other threads:[~2008-09-03 17:14 UTC | newest]
Thread overview: 15+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2008-05-17 12:28 Re: libpq object hooks (libpq events) Andrew Chernow <ac@esilo.com>
2008-05-18 13:33 ` Merlin Moncure <mmoncure@gmail.com>
2008-05-18 14:33 ` Andrew Chernow <ac@esilo.com>
2008-05-20 00:22 ` Tom Lane <tgl@sss.pgh.pa.us>
2008-05-20 02:21 ` Andrew Chernow <ac@esilo.com>
2008-05-20 02:37 ` Tom Lane <tgl@sss.pgh.pa.us>
2008-05-20 02:53 ` Merlin Moncure <mmoncure@gmail.com>
2008-05-20 03:03 ` Andrew Chernow <ac@esilo.com>
2008-05-20 04:28 ` Andrew Chernow <ac@esilo.com>
2008-09-03 00:19 ` Alvaro Herrera <alvherre@commandprompt.com>
2008-09-03 03:27 ` Andrew Chernow <ac@esilo.com>
2008-09-03 16:55 ` Alvaro Herrera <alvherre@commandprompt.com>
2008-09-03 17:14 ` Andrew Chernow <ac@esilo.com>
2008-09-03 14:09 ` Andrew Chernow <ac@esilo.com>
2008-05-20 17:29 ` Merlin Moncure <mmoncure@gmail.com>
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox