@@ -47,15 +47,18 @@ typedef struct ConnCacheEntry
47
47
PGconn * conn ; /* connection to foreign server, or NULL */
48
48
int xact_depth ; /* 0 = no xact open, 1 = main xact open, 2 =
49
49
* one level of subxact open, etc */
50
+ bool have_prep_stmt ; /* have we prepared any stmts in this xact? */
51
+ bool have_error ; /* have any subxacts aborted in this xact? */
50
52
} ConnCacheEntry ;
51
53
52
54
/*
53
55
* Connection cache (initialized on first use)
54
56
*/
55
57
static HTAB * ConnectionHash = NULL ;
56
58
57
- /* for assigning cursor numbers */
59
+ /* for assigning cursor numbers and prepared statement numbers */
58
60
static unsigned int cursor_number = 0 ;
61
+ static unsigned int prep_stmt_number = 0 ;
59
62
60
63
/* tracks whether any work is needed in callback functions */
61
64
static bool xact_got_connection = false;
@@ -78,6 +81,10 @@ static void pgfdw_subxact_callback(SubXactEvent event,
78
81
* if we don't already have a suitable one, and a transaction is opened at
79
82
* the right subtransaction nesting depth if we didn't do that already.
80
83
*
84
+ * will_prep_stmt must be true if caller intends to create any prepared
85
+ * statements. Since those don't go away automatically at transaction end
86
+ * (not even on error), we need this flag to cue manual cleanup.
87
+ *
81
88
* XXX Note that caching connections theoretically requires a mechanism to
82
89
* detect change of FDW objects to invalidate already established connections.
83
90
* We could manage that by watching for invalidation events on the relevant
@@ -86,7 +93,8 @@ static void pgfdw_subxact_callback(SubXactEvent event,
86
93
* mid-transaction anyway.
87
94
*/
88
95
PGconn *
89
- GetConnection (ForeignServer * server , UserMapping * user )
96
+ GetConnection (ForeignServer * server , UserMapping * user ,
97
+ bool will_prep_stmt )
90
98
{
91
99
bool found ;
92
100
ConnCacheEntry * entry ;
@@ -131,6 +139,8 @@ GetConnection(ForeignServer *server, UserMapping *user)
131
139
/* initialize new hashtable entry (key is already filled in) */
132
140
entry -> conn = NULL ;
133
141
entry -> xact_depth = 0 ;
142
+ entry -> have_prep_stmt = false;
143
+ entry -> have_error = false;
134
144
}
135
145
136
146
/*
@@ -147,6 +157,8 @@ GetConnection(ForeignServer *server, UserMapping *user)
147
157
if (entry -> conn == NULL )
148
158
{
149
159
entry -> xact_depth = 0 ; /* just to be sure */
160
+ entry -> have_prep_stmt = false;
161
+ entry -> have_error = false;
150
162
entry -> conn = connect_pg_server (server , user );
151
163
elog (DEBUG3 , "new postgres_fdw connection %p for server \"%s\"" ,
152
164
entry -> conn , server -> servername );
@@ -157,6 +169,9 @@ GetConnection(ForeignServer *server, UserMapping *user)
157
169
*/
158
170
begin_remote_xact (entry );
159
171
172
+ /* Remember if caller will prepare statements */
173
+ entry -> have_prep_stmt |= will_prep_stmt ;
174
+
160
175
return entry -> conn ;
161
176
}
162
177
@@ -393,13 +408,31 @@ GetCursorNumber(PGconn *conn)
393
408
return ++ cursor_number ;
394
409
}
395
410
411
+ /*
412
+ * Assign a "unique" number for a prepared statement.
413
+ *
414
+ * This works much like GetCursorNumber, except that we never reset the counter
415
+ * within a session. That's because we can't be 100% sure we've gotten rid
416
+ * of all prepared statements on all connections, and it's not really worth
417
+ * increasing the risk of prepared-statement name collisions by resetting.
418
+ */
419
+ unsigned int
420
+ GetPrepStmtNumber (PGconn * conn )
421
+ {
422
+ return ++ prep_stmt_number ;
423
+ }
424
+
396
425
/*
397
426
* Report an error we got from the remote server.
398
427
*
399
428
* elevel: error level to use (typically ERROR, but might be less)
400
429
* res: PGresult containing the error
401
430
* clear: if true, PQclear the result (otherwise caller will handle it)
402
431
* sql: NULL, or text of remote command we tried to execute
432
+ *
433
+ * Note: callers that choose not to throw ERROR for a remote error are
434
+ * responsible for making sure that the associated ConnCacheEntry gets
435
+ * marked with have_error = true.
403
436
*/
404
437
void
405
438
pgfdw_report_error (int elevel , PGresult * res , bool clear , const char * sql )
@@ -480,6 +513,22 @@ pgfdw_xact_callback(XactEvent event, void *arg)
480
513
if (PQresultStatus (res ) != PGRES_COMMAND_OK )
481
514
pgfdw_report_error (ERROR , res , true, "COMMIT TRANSACTION" );
482
515
PQclear (res );
516
+
517
+ /*
518
+ * If there were any errors in subtransactions, and we made
519
+ * prepared statements, do a DEALLOCATE ALL to make sure we
520
+ * get rid of all prepared statements. This is annoying and
521
+ * not terribly bulletproof, but it's probably not worth
522
+ * trying harder. We intentionally ignore any errors in the
523
+ * DEALLOCATE.
524
+ */
525
+ if (entry -> have_prep_stmt && entry -> have_error )
526
+ {
527
+ res = PQexec (entry -> conn , "DEALLOCATE ALL" );
528
+ PQclear (res );
529
+ }
530
+ entry -> have_prep_stmt = false;
531
+ entry -> have_error = false;
483
532
break ;
484
533
case XACT_EVENT_PRE_PREPARE :
485
534
@@ -502,14 +551,26 @@ pgfdw_xact_callback(XactEvent event, void *arg)
502
551
elog (ERROR , "missed cleaning up connection during pre-commit" );
503
552
break ;
504
553
case XACT_EVENT_ABORT :
554
+ /* Assume we might have lost track of prepared statements */
555
+ entry -> have_error = true;
505
556
/* If we're aborting, abort all remote transactions too */
506
557
res = PQexec (entry -> conn , "ABORT TRANSACTION" );
507
558
/* Note: can't throw ERROR, it would be infinite loop */
508
559
if (PQresultStatus (res ) != PGRES_COMMAND_OK )
509
560
pgfdw_report_error (WARNING , res , true,
510
561
"ABORT TRANSACTION" );
511
562
else
563
+ {
512
564
PQclear (res );
565
+ /* As above, make sure we've cleared any prepared stmts */
566
+ if (entry -> have_prep_stmt && entry -> have_error )
567
+ {
568
+ res = PQexec (entry -> conn , "DEALLOCATE ALL" );
569
+ PQclear (res );
570
+ }
571
+ entry -> have_prep_stmt = false;
572
+ entry -> have_error = false;
573
+ }
513
574
break ;
514
575
}
515
576
@@ -593,6 +654,8 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
593
654
}
594
655
else
595
656
{
657
+ /* Assume we might have lost track of prepared statements */
658
+ entry -> have_error = true;
596
659
/* Rollback all remote subtransactions during abort */
597
660
snprintf (sql , sizeof (sql ),
598
661
"ROLLBACK TO SAVEPOINT s%d; RELEASE SAVEPOINT s%d" ,
0 commit comments