1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-11 10:01:57 +03:00

Don't let libpq "event" procs break the state of PGresult objects.

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
This commit is contained in:
Tom Lane
2022-02-18 11:37:27 -05:00
parent de447bb8e6
commit ce1e7a2f71
3 changed files with 31 additions and 51 deletions

View File

@ -363,19 +363,16 @@ PQcopyResult(const PGresult *src, int flags)
/* Okay, trigger PGEVT_RESULTCOPY event */
for (i = 0; i < dest->nEvents; i++)
{
/* We don't fire events that had some previous failure */
if (src->events[i].resultInitialized)
{
PGEventResultCopy evt;
evt.src = src;
evt.dest = dest;
if (!dest->events[i].proc(PGEVT_RESULTCOPY, &evt,
dest->events[i].passThrough))
{
PQclear(dest);
return NULL;
}
dest->events[i].resultInitialized = true;
if (dest->events[i].proc(PGEVT_RESULTCOPY, &evt,
dest->events[i].passThrough))
dest->events[i].resultInitialized = true;
}
}
@ -2124,29 +2121,9 @@ PQgetResult(PGconn *conn)
break;
}
if (res)
{
int i;
for (i = 0; i < res->nEvents; i++)
{
PGEventResultCreate evt;
evt.conn = conn;
evt.result = res;
if (!res->events[i].proc(PGEVT_RESULTCREATE, &evt,
res->events[i].passThrough))
{
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("PGEventProc \"%s\" failed during PGEVT_RESULTCREATE event\n"),
res->events[i].name);
pqSetResultError(res, &conn->errorMessage);
res->resultStatus = PGRES_FATAL_ERROR;
break;
}
res->events[i].resultInitialized = true;
}
}
/* Time to fire PGEVT_RESULTCREATE events, if there are any */
if (res && res->nEvents > 0)
(void) PQfireResultCreateEvents(conn, res);
return res;
}

View File

@ -184,6 +184,7 @@ PQresultInstanceData(const PGresult *result, PGEventProc proc)
int
PQfireResultCreateEvents(PGconn *conn, PGresult *res)
{
int result = true;
int i;
if (!res)
@ -191,19 +192,20 @@ PQfireResultCreateEvents(PGconn *conn, PGresult *res)
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))
return false;
res->events[i].resultInitialized = true;
if (res->events[i].proc(PGEVT_RESULTCREATE, &evt,
res->events[i].passThrough))
res->events[i].resultInitialized = true;
else
result = false;
}
}
return true;
return result;
}