Skip to content

Commit 00d4f2a

Browse files
committed
Improve connection-failure error handling in contrib/postgres_fdw.
postgres_fdw tended to say "unknown error" if it tried to execute a command on an already-dead connection, because some paths in libpq just return a null PGresult for such cases. Out-of-memory might result in that, too. To fix, pass the PGconn to pgfdw_report_error, and look at its PQerrorMessage() string if we can't get anything out of the PGresult. Also, fix the transaction-exit logic to reliably drop a dead connection. It was attempting to do that already, but it assumed that only connection cache entries with xact_depth > 0 needed to be examined. The folly in that is that if we fail while issuing START TRANSACTION, we'll not have bumped xact_depth. (At least for the case I was testing, this fix masks the other problem; but it still seems like a good idea to have the PGconn fallback logic.) Per investigation of bug #9087 from Craig Lucas. Backpatch to 9.3 where this code was introduced.
1 parent 489e6ac commit 00d4f2a

File tree

3 files changed

+100
-85
lines changed

3 files changed

+100
-85
lines changed

contrib/postgres_fdw/connection.c

Lines changed: 85 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ do_sql_command(PGconn *conn, const char *sql)
355355

356356
res = PQexec(conn, sql);
357357
if (PQresultStatus(res) != PGRES_COMMAND_OK)
358-
pgfdw_report_error(ERROR, res, true, sql);
358+
pgfdw_report_error(ERROR, res, conn, true, sql);
359359
PQclear(res);
360360
}
361361

@@ -454,6 +454,7 @@ GetPrepStmtNumber(PGconn *conn)
454454
*
455455
* elevel: error level to use (typically ERROR, but might be less)
456456
* res: PGresult containing the error
457+
* conn: connection we did the query on
457458
* clear: if true, PQclear the result (otherwise caller will handle it)
458459
* sql: NULL, or text of remote command we tried to execute
459460
*
@@ -462,7 +463,8 @@ GetPrepStmtNumber(PGconn *conn)
462463
* marked with have_error = true.
463464
*/
464465
void
465-
pgfdw_report_error(int elevel, PGresult *res, bool clear, const char *sql)
466+
pgfdw_report_error(int elevel, PGresult *res, PGconn *conn,
467+
bool clear, const char *sql)
466468
{
467469
/* If requested, PGresult must be released before leaving this function. */
468470
PG_TRY();
@@ -483,6 +485,14 @@ pgfdw_report_error(int elevel, PGresult *res, bool clear, const char *sql)
483485
else
484486
sqlstate = ERRCODE_CONNECTION_FAILURE;
485487

488+
/*
489+
* If we don't get a message from the PGresult, try the PGconn. This
490+
* is needed because for connection-level failures, PQexec may just
491+
* return NULL, not a PGresult at all.
492+
*/
493+
if (message_primary == NULL)
494+
message_primary = PQerrorMessage(conn);
495+
486496
ereport(elevel,
487497
(errcode(sqlstate),
488498
message_primary ? errmsg_internal("%s", message_primary) :
@@ -525,83 +535,88 @@ pgfdw_xact_callback(XactEvent event, void *arg)
525535
{
526536
PGresult *res;
527537

528-
/* We only care about connections with open remote transactions */
529-
if (entry->conn == NULL || entry->xact_depth == 0)
538+
/* Ignore cache entry if no open connection right now */
539+
if (entry->conn == NULL)
530540
continue;
531541

532-
elog(DEBUG3, "closing remote transaction on connection %p",
533-
entry->conn);
534-
535-
switch (event)
542+
/* If it has an open remote transaction, try to close it */
543+
if (entry->xact_depth > 0)
536544
{
537-
case XACT_EVENT_PRE_COMMIT:
538-
/* Commit all remote transactions during pre-commit */
539-
do_sql_command(entry->conn, "COMMIT TRANSACTION");
540-
541-
/*
542-
* If there were any errors in subtransactions, and we made
543-
* prepared statements, do a DEALLOCATE ALL to make sure we
544-
* get rid of all prepared statements. This is annoying and
545-
* not terribly bulletproof, but it's probably not worth
546-
* trying harder.
547-
*
548-
* DEALLOCATE ALL only exists in 8.3 and later, so this
549-
* constrains how old a server postgres_fdw can communicate
550-
* with. We intentionally ignore errors in the DEALLOCATE, so
551-
* that we can hobble along to some extent with older servers
552-
* (leaking prepared statements as we go; but we don't really
553-
* support update operations pre-8.3 anyway).
554-
*/
555-
if (entry->have_prep_stmt && entry->have_error)
556-
{
557-
res = PQexec(entry->conn, "DEALLOCATE ALL");
558-
PQclear(res);
559-
}
560-
entry->have_prep_stmt = false;
561-
entry->have_error = false;
562-
break;
563-
case XACT_EVENT_PRE_PREPARE:
564-
565-
/*
566-
* We disallow remote transactions that modified anything,
567-
* since it's not really reasonable to hold them open until
568-
* the prepared transaction is committed. For the moment,
569-
* throw error unconditionally; later we might allow read-only
570-
* cases. Note that the error will cause us to come right
571-
* back here with event == XACT_EVENT_ABORT, so we'll clean up
572-
* the connection state at that point.
573-
*/
574-
ereport(ERROR,
575-
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
576-
errmsg("cannot prepare a transaction that modified remote tables")));
577-
break;
578-
case XACT_EVENT_COMMIT:
579-
case XACT_EVENT_PREPARE:
580-
/* Should not get here -- pre-commit should have handled it */
581-
elog(ERROR, "missed cleaning up connection during pre-commit");
582-
break;
583-
case XACT_EVENT_ABORT:
584-
/* Assume we might have lost track of prepared statements */
585-
entry->have_error = true;
586-
/* If we're aborting, abort all remote transactions too */
587-
res = PQexec(entry->conn, "ABORT TRANSACTION");
588-
/* Note: can't throw ERROR, it would be infinite loop */
589-
if (PQresultStatus(res) != PGRES_COMMAND_OK)
590-
pgfdw_report_error(WARNING, res, true,
591-
"ABORT TRANSACTION");
592-
else
593-
{
594-
PQclear(res);
595-
/* As above, make sure we've cleared any prepared stmts */
545+
elog(DEBUG3, "closing remote transaction on connection %p",
546+
entry->conn);
547+
548+
switch (event)
549+
{
550+
case XACT_EVENT_PRE_COMMIT:
551+
/* Commit all remote transactions during pre-commit */
552+
do_sql_command(entry->conn, "COMMIT TRANSACTION");
553+
554+
/*
555+
* If there were any errors in subtransactions, and we
556+
* made prepared statements, do a DEALLOCATE ALL to make
557+
* sure we get rid of all prepared statements. This is
558+
* annoying and not terribly bulletproof, but it's
559+
* probably not worth trying harder.
560+
*
561+
* DEALLOCATE ALL only exists in 8.3 and later, so this
562+
* constrains how old a server postgres_fdw can
563+
* communicate with. We intentionally ignore errors in
564+
* the DEALLOCATE, so that we can hobble along to some
565+
* extent with older servers (leaking prepared statements
566+
* as we go; but we don't really support update operations
567+
* pre-8.3 anyway).
568+
*/
596569
if (entry->have_prep_stmt && entry->have_error)
597570
{
598571
res = PQexec(entry->conn, "DEALLOCATE ALL");
599572
PQclear(res);
600573
}
601574
entry->have_prep_stmt = false;
602575
entry->have_error = false;
603-
}
604-
break;
576+
break;
577+
case XACT_EVENT_PRE_PREPARE:
578+
579+
/*
580+
* We disallow remote transactions that modified anything,
581+
* since it's not very reasonable to hold them open until
582+
* the prepared transaction is committed. For the moment,
583+
* throw error unconditionally; later we might allow
584+
* read-only cases. Note that the error will cause us to
585+
* come right back here with event == XACT_EVENT_ABORT, so
586+
* we'll clean up the connection state at that point.
587+
*/
588+
ereport(ERROR,
589+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
590+
errmsg("cannot prepare a transaction that modified remote tables")));
591+
break;
592+
case XACT_EVENT_COMMIT:
593+
case XACT_EVENT_PREPARE:
594+
/* Pre-commit should have closed the open transaction */
595+
elog(ERROR, "missed cleaning up connection during pre-commit");
596+
break;
597+
case XACT_EVENT_ABORT:
598+
/* Assume we might have lost track of prepared statements */
599+
entry->have_error = true;
600+
/* If we're aborting, abort all remote transactions too */
601+
res = PQexec(entry->conn, "ABORT TRANSACTION");
602+
/* Note: can't throw ERROR, it would be infinite loop */
603+
if (PQresultStatus(res) != PGRES_COMMAND_OK)
604+
pgfdw_report_error(WARNING, res, entry->conn, true,
605+
"ABORT TRANSACTION");
606+
else
607+
{
608+
PQclear(res);
609+
/* As above, make sure to clear any prepared stmts */
610+
if (entry->have_prep_stmt && entry->have_error)
611+
{
612+
res = PQexec(entry->conn, "DEALLOCATE ALL");
613+
PQclear(res);
614+
}
615+
entry->have_prep_stmt = false;
616+
entry->have_error = false;
617+
}
618+
break;
619+
}
605620
}
606621

607622
/* Reset state to show we're out of a transaction */
@@ -689,7 +704,7 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
689704
curlevel, curlevel);
690705
res = PQexec(entry->conn, sql);
691706
if (PQresultStatus(res) != PGRES_COMMAND_OK)
692-
pgfdw_report_error(WARNING, res, true, sql);
707+
pgfdw_report_error(WARNING, res, entry->conn, true, sql);
693708
else
694709
PQclear(res);
695710
}

contrib/postgres_fdw/postgres_fdw.c

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,7 +1040,7 @@ postgresReScanForeignScan(ForeignScanState *node)
10401040
*/
10411041
res = PQexec(fsstate->conn, sql);
10421042
if (PQresultStatus(res) != PGRES_COMMAND_OK)
1043-
pgfdw_report_error(ERROR, res, true, sql);
1043+
pgfdw_report_error(ERROR, res, fsstate->conn, true, sql);
10441044
PQclear(res);
10451045

10461046
/* Now force a fresh FETCH. */
@@ -1374,7 +1374,7 @@ postgresExecForeignInsert(EState *estate,
13741374
0);
13751375
if (PQresultStatus(res) !=
13761376
(fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
1377-
pgfdw_report_error(ERROR, res, true, fmstate->query);
1377+
pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
13781378

13791379
/* Check number of rows affected, and fetch RETURNING tuple if any */
13801380
if (fmstate->has_returning)
@@ -1444,7 +1444,7 @@ postgresExecForeignUpdate(EState *estate,
14441444
0);
14451445
if (PQresultStatus(res) !=
14461446
(fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
1447-
pgfdw_report_error(ERROR, res, true, fmstate->query);
1447+
pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
14481448

14491449
/* Check number of rows affected, and fetch RETURNING tuple if any */
14501450
if (fmstate->has_returning)
@@ -1514,7 +1514,7 @@ postgresExecForeignDelete(EState *estate,
15141514
0);
15151515
if (PQresultStatus(res) !=
15161516
(fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
1517-
pgfdw_report_error(ERROR, res, true, fmstate->query);
1517+
pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
15181518

15191519
/* Check number of rows affected, and fetch RETURNING tuple if any */
15201520
if (fmstate->has_returning)
@@ -1563,7 +1563,7 @@ postgresEndForeignModify(EState *estate,
15631563
*/
15641564
res = PQexec(fmstate->conn, sql);
15651565
if (PQresultStatus(res) != PGRES_COMMAND_OK)
1566-
pgfdw_report_error(ERROR, res, true, sql);
1566+
pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
15671567
PQclear(res);
15681568
fmstate->p_name = NULL;
15691569
}
@@ -1800,7 +1800,7 @@ get_remote_estimate(const char *sql, PGconn *conn,
18001800
*/
18011801
res = PQexec(conn, sql);
18021802
if (PQresultStatus(res) != PGRES_TUPLES_OK)
1803-
pgfdw_report_error(ERROR, res, false, sql);
1803+
pgfdw_report_error(ERROR, res, conn, false, sql);
18041804

18051805
/*
18061806
* Extract cost numbers for topmost plan node. Note we search for a
@@ -1934,7 +1934,7 @@ create_cursor(ForeignScanState *node)
19341934
res = PQexecParams(conn, buf.data, numParams, NULL, values,
19351935
NULL, NULL, 0);
19361936
if (PQresultStatus(res) != PGRES_COMMAND_OK)
1937-
pgfdw_report_error(ERROR, res, true, fsstate->query);
1937+
pgfdw_report_error(ERROR, res, conn, true, fsstate->query);
19381938
PQclear(res);
19391939

19401940
/* Mark the cursor as created, and show no tuples have been retrieved */
@@ -1985,7 +1985,7 @@ fetch_more_data(ForeignScanState *node)
19851985
res = PQexec(conn, sql);
19861986
/* On error, report the original query, not the FETCH. */
19871987
if (PQresultStatus(res) != PGRES_TUPLES_OK)
1988-
pgfdw_report_error(ERROR, res, false, fsstate->query);
1988+
pgfdw_report_error(ERROR, res, conn, false, fsstate->query);
19891989

19901990
/* Convert the data into HeapTuples */
19911991
numrows = PQntuples(res);
@@ -2091,7 +2091,7 @@ close_cursor(PGconn *conn, unsigned int cursor_number)
20912091
*/
20922092
res = PQexec(conn, sql);
20932093
if (PQresultStatus(res) != PGRES_COMMAND_OK)
2094-
pgfdw_report_error(ERROR, res, true, sql);
2094+
pgfdw_report_error(ERROR, res, conn, true, sql);
20952095
PQclear(res);
20962096
}
20972097

@@ -2128,7 +2128,7 @@ prepare_foreign_modify(PgFdwModifyState *fmstate)
21282128
NULL);
21292129

21302130
if (PQresultStatus(res) != PGRES_COMMAND_OK)
2131-
pgfdw_report_error(ERROR, res, true, fmstate->query);
2131+
pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
21322132
PQclear(res);
21332133

21342134
/* This action shows that the prepare has been done. */
@@ -2278,7 +2278,7 @@ postgresAnalyzeForeignTable(Relation relation,
22782278
{
22792279
res = PQexec(conn, sql.data);
22802280
if (PQresultStatus(res) != PGRES_TUPLES_OK)
2281-
pgfdw_report_error(ERROR, res, false, sql.data);
2281+
pgfdw_report_error(ERROR, res, conn, false, sql.data);
22822282

22832283
if (PQntuples(res) != 1 || PQnfields(res) != 1)
22842284
elog(ERROR, "unexpected result from deparseAnalyzeSizeSql query");
@@ -2372,7 +2372,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
23722372
{
23732373
res = PQexec(conn, sql.data);
23742374
if (PQresultStatus(res) != PGRES_COMMAND_OK)
2375-
pgfdw_report_error(ERROR, res, false, sql.data);
2375+
pgfdw_report_error(ERROR, res, conn, false, sql.data);
23762376
PQclear(res);
23772377
res = NULL;
23782378

@@ -2403,7 +2403,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
24032403
res = PQexec(conn, fetch_sql);
24042404
/* On error, report the original query, not the FETCH. */
24052405
if (PQresultStatus(res) != PGRES_TUPLES_OK)
2406-
pgfdw_report_error(ERROR, res, false, sql.data);
2406+
pgfdw_report_error(ERROR, res, conn, false, sql.data);
24072407

24082408
/* Process whatever we got. */
24092409
numrows = PQntuples(res);

contrib/postgres_fdw/postgres_fdw.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ extern PGconn *GetConnection(ForeignServer *server, UserMapping *user,
3030
extern void ReleaseConnection(PGconn *conn);
3131
extern unsigned int GetCursorNumber(PGconn *conn);
3232
extern unsigned int GetPrepStmtNumber(PGconn *conn);
33-
extern void pgfdw_report_error(int elevel, PGresult *res, bool clear,
34-
const char *sql);
33+
extern void pgfdw_report_error(int elevel, PGresult *res, PGconn *conn,
34+
bool clear, const char *sql);
3535

3636
/* in option.c */
3737
extern int ExtractConnectionOptions(List *defelems,

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy