mirror of
https://github.com/postgres/postgres.git
synced 2025-06-04 00:02:37 -04:00
As currently implemented, failure of a PGEVT_RESULTCREATE callback causes the PGresult to be converted to an error result. This is intellectually inconsistent (shouldn't a failing callback likewise prevent creation of the error result? what about side-effects on the behavior seen by other event procs? why does PQfireResultCreateEvents act differently from PQgetResult?), but more importantly it destroys any promises we might wish to make about the behavior of libpq in nontrivial operating modes, such as pipeline mode. For example, it's not possible to promise that PGRES_PIPELINE_SYNC results will be returned if an event callback fails on those. With this definition, expecting applications to behave sanely in the face of possibly-failing callbacks seems like a very big lift. Hence, redefine the result of a callback failure as being simply that that event procedure won't be called any more for this PGresult (which was true already). Event procedures can still signal failure back to the application through out-of-band mechanisms, for example via their passthrough arguments. Similarly, don't let failure of a PGEVT_RESULTCOPY callback prevent PQcopyResult from succeeding. That definition allowed a misbehaving event proc to break single-row mode (our sole internal use of PQcopyResult), and it probably had equally deleterious effects for outside uses. Discussion: https://postgr.es/m/3185105.1644960083@sss.pgh.pa.us
212 lines
4.5 KiB
C
212 lines
4.5 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* libpq-events.c
|
|
* functions for supporting the libpq "events" API
|
|
*
|
|
* Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/interfaces/libpq/libpq-events.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres_fe.h"
|
|
|
|
#include "libpq-fe.h"
|
|
#include "libpq-int.h"
|
|
|
|
|
|
/*
|
|
* Registers an event proc with the given PGconn.
|
|
*
|
|
* The same proc can't be registered more than once in a PGconn. This
|
|
* restriction is required because we use the proc address to identify
|
|
* the event for purposes such as PQinstanceData().
|
|
*
|
|
* The name argument is used within error messages to aid in debugging.
|
|
* A name must be supplied, but it needn't be unique. The string is
|
|
* copied, so the passed value needn't be long-lived.
|
|
*
|
|
* The passThrough argument is an application specific pointer and can be set
|
|
* to NULL if not required. It is passed through to the event proc whenever
|
|
* the event proc is called, and is not otherwise touched by libpq.
|
|
*
|
|
* The function returns a non-zero if successful. If the function fails,
|
|
* zero is returned.
|
|
*/
|
|
int
|
|
PQregisterEventProc(PGconn *conn, PGEventProc proc,
|
|
const char *name, void *passThrough)
|
|
{
|
|
int i;
|
|
PGEventRegister regevt;
|
|
|
|
if (!proc || !conn || !name || !*name)
|
|
return false; /* bad arguments */
|
|
|
|
for (i = 0; i < conn->nEvents; i++)
|
|
{
|
|
if (conn->events[i].proc == proc)
|
|
return false; /* already registered */
|
|
}
|
|
|
|
if (conn->nEvents >= conn->eventArraySize)
|
|
{
|
|
PGEvent *e;
|
|
int newSize;
|
|
|
|
newSize = conn->eventArraySize ? conn->eventArraySize * 2 : 8;
|
|
if (conn->events)
|
|
e = (PGEvent *) realloc(conn->events, newSize * sizeof(PGEvent));
|
|
else
|
|
e = (PGEvent *) malloc(newSize * sizeof(PGEvent));
|
|
|
|
if (!e)
|
|
return false;
|
|
|
|
conn->eventArraySize = newSize;
|
|
conn->events = e;
|
|
}
|
|
|
|
conn->events[conn->nEvents].proc = proc;
|
|
conn->events[conn->nEvents].name = strdup(name);
|
|
if (!conn->events[conn->nEvents].name)
|
|
return false;
|
|
conn->events[conn->nEvents].passThrough = passThrough;
|
|
conn->events[conn->nEvents].data = NULL;
|
|
conn->events[conn->nEvents].resultInitialized = false;
|
|
conn->nEvents++;
|
|
|
|
regevt.conn = conn;
|
|
if (!proc(PGEVT_REGISTER, ®evt, passThrough))
|
|
{
|
|
conn->nEvents--;
|
|
free(conn->events[conn->nEvents].name);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Set some "instance data" for an event within a PGconn.
|
|
* Returns nonzero on success, zero on failure.
|
|
*/
|
|
int
|
|
PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data)
|
|
{
|
|
int i;
|
|
|
|
if (!conn || !proc)
|
|
return false;
|
|
|
|
for (i = 0; i < conn->nEvents; i++)
|
|
{
|
|
if (conn->events[i].proc == proc)
|
|
{
|
|
conn->events[i].data = data;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Obtain the "instance data", if any, for the event.
|
|
*/
|
|
void *
|
|
PQinstanceData(const PGconn *conn, PGEventProc proc)
|
|
{
|
|
int i;
|
|
|
|
if (!conn || !proc)
|
|
return NULL;
|
|
|
|
for (i = 0; i < conn->nEvents; i++)
|
|
{
|
|
if (conn->events[i].proc == proc)
|
|
return conn->events[i].data;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Set some "instance data" for an event within a PGresult.
|
|
* Returns nonzero on success, zero on failure.
|
|
*/
|
|
int
|
|
PQresultSetInstanceData(PGresult *result, PGEventProc proc, void *data)
|
|
{
|
|
int i;
|
|
|
|
if (!result || !proc)
|
|
return false;
|
|
|
|
for (i = 0; i < result->nEvents; i++)
|
|
{
|
|
if (result->events[i].proc == proc)
|
|
{
|
|
result->events[i].data = data;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Obtain the "instance data", if any, for the event.
|
|
*/
|
|
void *
|
|
PQresultInstanceData(const PGresult *result, PGEventProc proc)
|
|
{
|
|
int i;
|
|
|
|
if (!result || !proc)
|
|
return NULL;
|
|
|
|
for (i = 0; i < result->nEvents; i++)
|
|
if (result->events[i].proc == proc)
|
|
return result->events[i].data;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Fire RESULTCREATE events for an application-created PGresult.
|
|
*
|
|
* The conn argument can be NULL if event procedures won't use it.
|
|
*/
|
|
int
|
|
PQfireResultCreateEvents(PGconn *conn, PGresult *res)
|
|
{
|
|
int result = true;
|
|
int i;
|
|
|
|
if (!res)
|
|
return false;
|
|
|
|
for (i = 0; i < res->nEvents; i++)
|
|
{
|
|
/* It's possible event was already fired, if so don't repeat it */
|
|
if (!res->events[i].resultInitialized)
|
|
{
|
|
PGEventResultCreate evt;
|
|
|
|
evt.conn = conn;
|
|
evt.result = res;
|
|
if (res->events[i].proc(PGEVT_RESULTCREATE, &evt,
|
|
res->events[i].passThrough))
|
|
res->events[i].resultInitialized = true;
|
|
else
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|