Make dblink interruptible, via new libpqsrv APIs.
This replaces dblink's blocking libpq calls, allowing cancellation and allowing DROP DATABASE (of a database not involved in the query). Apart from explicit dblink_cancel_query() calls, dblink still doesn't cancel the remote side. The replacement for the blocking calls consists of new, general-purpose query execution wrappers in the libpqsrv facility. Out-of-tree extensions should adopt these. Use them in postgres_fdw, replacing a local implementation from which the libpqsrv implementation derives. This is a bug fix for dblink. Code inspection identified the bug at least thirteen years ago, but user complaints have not appeared. Hence, no back-patch for now. Discussion: https://postgr.es/m/20231122012945.74@rfd.leadboat.com
This commit is contained in:
parent
0efc831847
commit
d3c5f37dd5
8 changed files with 180 additions and 95 deletions
|
@ -61,6 +61,7 @@
|
|||
#include "utils/memutils.h"
|
||||
#include "utils/rel.h"
|
||||
#include "utils/varlena.h"
|
||||
#include "utils/wait_event.h"
|
||||
|
||||
PG_MODULE_MAGIC;
|
||||
|
||||
|
@ -133,6 +134,7 @@ static HTAB *remoteConnHash = NULL;
|
|||
/* custom wait event values, retrieved from shared memory */
|
||||
static uint32 dblink_we_connect = 0;
|
||||
static uint32 dblink_we_get_conn = 0;
|
||||
static uint32 dblink_we_get_result = 0;
|
||||
|
||||
/*
|
||||
* Following is list that holds multiple remote connections.
|
||||
|
@ -252,6 +254,9 @@ dblink_init(void)
|
|||
{
|
||||
if (!pconn)
|
||||
{
|
||||
if (dblink_we_get_result == 0)
|
||||
dblink_we_get_result = WaitEventExtensionNew("DblinkGetResult");
|
||||
|
||||
pconn = (remoteConn *) MemoryContextAlloc(TopMemoryContext, sizeof(remoteConn));
|
||||
pconn->conn = NULL;
|
||||
pconn->openCursorCount = 0;
|
||||
|
@ -442,7 +447,7 @@ dblink_open(PG_FUNCTION_ARGS)
|
|||
/* If we are not in a transaction, start one */
|
||||
if (PQtransactionStatus(conn) == PQTRANS_IDLE)
|
||||
{
|
||||
res = PQexec(conn, "BEGIN");
|
||||
res = libpqsrv_exec(conn, "BEGIN", dblink_we_get_result);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
dblink_res_internalerror(conn, res, "begin error");
|
||||
PQclear(res);
|
||||
|
@ -461,7 +466,7 @@ dblink_open(PG_FUNCTION_ARGS)
|
|||
(rconn->openCursorCount)++;
|
||||
|
||||
appendStringInfo(&buf, "DECLARE %s CURSOR FOR %s", curname, sql);
|
||||
res = PQexec(conn, buf.data);
|
||||
res = libpqsrv_exec(conn, buf.data, dblink_we_get_result);
|
||||
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
dblink_res_error(conn, conname, res, fail,
|
||||
|
@ -530,7 +535,7 @@ dblink_close(PG_FUNCTION_ARGS)
|
|||
appendStringInfo(&buf, "CLOSE %s", curname);
|
||||
|
||||
/* close the cursor */
|
||||
res = PQexec(conn, buf.data);
|
||||
res = libpqsrv_exec(conn, buf.data, dblink_we_get_result);
|
||||
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
{
|
||||
dblink_res_error(conn, conname, res, fail,
|
||||
|
@ -550,7 +555,7 @@ dblink_close(PG_FUNCTION_ARGS)
|
|||
{
|
||||
rconn->newXactForCursor = false;
|
||||
|
||||
res = PQexec(conn, "COMMIT");
|
||||
res = libpqsrv_exec(conn, "COMMIT", dblink_we_get_result);
|
||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
dblink_res_internalerror(conn, res, "commit error");
|
||||
PQclear(res);
|
||||
|
@ -632,7 +637,7 @@ dblink_fetch(PG_FUNCTION_ARGS)
|
|||
* PGresult will be long-lived even though we are still in a short-lived
|
||||
* memory context.
|
||||
*/
|
||||
res = PQexec(conn, buf.data);
|
||||
res = libpqsrv_exec(conn, buf.data, dblink_we_get_result);
|
||||
if (!res ||
|
||||
(PQresultStatus(res) != PGRES_COMMAND_OK &&
|
||||
PQresultStatus(res) != PGRES_TUPLES_OK))
|
||||
|
@ -780,7 +785,7 @@ dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
|
|||
else
|
||||
{
|
||||
/* async result retrieval, do it the old way */
|
||||
PGresult *res = PQgetResult(conn);
|
||||
PGresult *res = libpqsrv_get_result(conn, dblink_we_get_result);
|
||||
|
||||
/* NULL means we're all done with the async results */
|
||||
if (res)
|
||||
|
@ -1088,7 +1093,8 @@ materializeQueryResult(FunctionCallInfo fcinfo,
|
|||
PQclear(sinfo.last_res);
|
||||
PQclear(sinfo.cur_res);
|
||||
/* and clear out any pending data in libpq */
|
||||
while ((res = PQgetResult(conn)) != NULL)
|
||||
while ((res = libpqsrv_get_result(conn, dblink_we_get_result)) !=
|
||||
NULL)
|
||||
PQclear(res);
|
||||
PG_RE_THROW();
|
||||
}
|
||||
|
@ -1115,7 +1121,7 @@ storeQueryResult(volatile storeInfo *sinfo, PGconn *conn, const char *sql)
|
|||
{
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
sinfo->cur_res = PQgetResult(conn);
|
||||
sinfo->cur_res = libpqsrv_get_result(conn, dblink_we_get_result);
|
||||
if (!sinfo->cur_res)
|
||||
break;
|
||||
|
||||
|
@ -1443,7 +1449,7 @@ dblink_exec(PG_FUNCTION_ARGS)
|
|||
if (!conn)
|
||||
dblink_conn_not_avail(conname);
|
||||
|
||||
res = PQexec(conn, sql);
|
||||
res = libpqsrv_exec(conn, sql, dblink_we_get_result);
|
||||
if (!res ||
|
||||
(PQresultStatus(res) != PGRES_COMMAND_OK &&
|
||||
PQresultStatus(res) != PGRES_TUPLES_OK))
|
||||
|
@ -2739,8 +2745,8 @@ dblink_res_error(PGconn *conn, const char *conname, PGresult *res,
|
|||
|
||||
/*
|
||||
* If we don't get a message from the PGresult, try the PGconn. This is
|
||||
* needed because for connection-level failures, PQexec may just return
|
||||
* NULL, not a PGresult at all.
|
||||
* needed because for connection-level failures, PQgetResult may just
|
||||
* return NULL, not a PGresult at all.
|
||||
*/
|
||||
if (message_primary == NULL)
|
||||
message_primary = pchomp(PQerrorMessage(conn));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue