On Fri, 2026-03-06 at 21:49 +0530, Ashutosh Bapat wrote:

> I don't think we need a separate ForeignServerName function. In
> AlterSubscription() we already have ForeignSever object which has
> server name in it. Other two callers invoke
> ForeignServerConnectionString() which in turn fetches ForeignServer
> object. Those callers instead may fetch ForeignServer object
> themselves and pass it to ForeignServerConnectionString() and use it
> in the error message.

Done.

> The patch has changes to pg_dump.c but there is no corresponding
> test.
> But I don't think we need a separate test. If the objects created in
> regress/*.sql tests are not dropped, 002_pg_upgrade.pl would test
> dump/restore of subscriptions with server.

It seems that foreign_data.sql expects there to be zero FDWs, servers,
and user mappings, so it's not quite that simple. I'm not entirely sure
why that is, but I suppose it's meant to be tested in 002_pg_dump.pl
instead.

I wrote the tests there (attached), which revealed that CREATE FOREIGN
DATA WRAPPER ... CONNECTION wasn't being dumped properly. I attached a
separate fix for that.

Unfortunately I don't think we can rely on regress.so being available
when 002_pg_dump.pl runs. Do you have an idea how I can effectively
test the FDW (which is needed to test the server and subscription)? I
suppose I could make it a built-in function, and that wouldn't be so
bad, but not ideal. Right now this test is failing for CI on debian
autoconf.

> I think we need tests for testing changes in connection when ALTER
> SUBSCRIPTION ... SERVER is executed and also those for switching
> between SERVER and CONNECTION.

Done.

Attached series including patches to address Andres's and Amit's
comments, too.

Thank you!

Regards,
        Jeff Davis

From 7726c05c6338bfea1ccac68aaf8288fe8a165107 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Sat, 14 Mar 2026 11:03:53 -0700
Subject: [PATCH v22 1/6] Clean up postgres_fdw/t/010_subscription.pl.

The test was based on test/subscription/002_rep_changes.pl, but had
some leftover copy+paste problems that were useless and/or distracted
from the point of the test.

Discussion: https://postgr.es/m/CAA4eK1+=V_UFNHwcoMFqzy0F4AtS9_GyXhQDUzizgieQPWr=0...@mail.gmail.com
Reported-by: Amit Kapila <[email protected]>
---
 contrib/postgres_fdw/t/010_subscription.pl | 15 ++++-----------
 1 file changed, 4 insertions(+), 11 deletions(-)

diff --git a/contrib/postgres_fdw/t/010_subscription.pl b/contrib/postgres_fdw/t/010_subscription.pl
index 1e41091badc..a04d64bb78c 100644
--- a/contrib/postgres_fdw/t/010_subscription.pl
+++ b/contrib/postgres_fdw/t/010_subscription.pl
@@ -1,7 +1,8 @@
 
 # Copyright (c) 2021-2026, PostgreSQL Global Development Group
 
-# Basic logical replication test
+# Test postgres_fdw foreign server for use with a subscription.
+
 use strict;
 use warnings FATAL => 'all';
 use PostgreSQL::Test::Cluster;
@@ -22,11 +23,6 @@ $node_subscriber->start;
 $node_publisher->safe_psql('postgres',
 	"CREATE TABLE tab_ins AS SELECT a, a + 1 as b FROM generate_series(1,1002) AS a");
 
-# Replicate the changes without columns
-$node_publisher->safe_psql('postgres', "CREATE TABLE tab_no_col()");
-$node_publisher->safe_psql('postgres',
-	"INSERT INTO tab_no_col default VALUES");
-
 # Setup structure on subscriber
 $node_subscriber->safe_psql('postgres', "CREATE EXTENSION postgres_fdw");
 $node_subscriber->safe_psql('postgres', "CREATE TABLE tab_ins (a int, b int)");
@@ -45,9 +41,6 @@ $node_subscriber->safe_psql('postgres',
 	"CREATE USER MAPPING FOR PUBLIC SERVER tap_server"
 );
 
-$node_subscriber->safe_psql('postgres',
-	"CREATE FOREIGN TABLE f_tab_ins (a int, b int) SERVER tap_server OPTIONS(table_name 'tab_ins')"
-);
 $node_subscriber->safe_psql('postgres',
 	"CREATE SUBSCRIPTION tap_sub SERVER tap_server PUBLICATION tap_pub WITH (password_required=false)"
 );
@@ -56,7 +49,7 @@ $node_subscriber->safe_psql('postgres',
 $node_subscriber->wait_for_subscription_sync($node_publisher, 'tap_sub');
 
 my $result =
-  $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM (SELECT f.b = l.b as match FROM tab_ins l, f_tab_ins f WHERE l.a = f.a) WHERE match");
+  $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_ins");
 is($result, qq(1002), 'check that initial data was copied to subscriber');
 
 $node_publisher->safe_psql('postgres',
@@ -65,7 +58,7 @@ $node_publisher->safe_psql('postgres',
 $node_publisher->wait_for_catchup('tap_sub');
 
 $result =
-  $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM (SELECT f.b = l.b as match FROM tab_ins l, f_tab_ins f WHERE l.a = f.a) WHERE match");
+  $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_ins");
 is($result, qq(1050), 'check that inserted data was copied to subscriber');
 
 done_testing();
-- 
2.43.0

From 08001be72c54ce6f1bd4729546260b28821a46a1 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Sat, 14 Mar 2026 12:50:45 -0700
Subject: [PATCH v22 2/6] ALTER SUBSCRIPTION ... SERVER test.

Test ALTER SUBSCRIPTION ... SERVER and ALTER SUBSCRIPTION
... CONNECTION, including invalidation.

Discussion: https://postgr.es/m/CAExHW5vV5znEvecX=ra2-v7ubj9-m6qvddzub78m-txbyd1...@mail.gmail.com
Suggested-by: Ashutosh Bapat <[email protected]>
---
 contrib/postgres_fdw/t/010_subscription.pl | 38 ++++++++++++++++++++--
 1 file changed, 36 insertions(+), 2 deletions(-)

diff --git a/contrib/postgres_fdw/t/010_subscription.pl b/contrib/postgres_fdw/t/010_subscription.pl
index a04d64bb78c..50eac4c3bdb 100644
--- a/contrib/postgres_fdw/t/010_subscription.pl
+++ b/contrib/postgres_fdw/t/010_subscription.pl
@@ -49,7 +49,7 @@ $node_subscriber->safe_psql('postgres',
 $node_subscriber->wait_for_subscription_sync($node_publisher, 'tap_sub');
 
 my $result =
-  $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_ins");
+  $node_subscriber->safe_psql('postgres', "SELECT MAX(a) FROM tab_ins");
 is($result, qq(1002), 'check that initial data was copied to subscriber');
 
 $node_publisher->safe_psql('postgres',
@@ -58,7 +58,41 @@ $node_publisher->safe_psql('postgres',
 $node_publisher->wait_for_catchup('tap_sub');
 
 $result =
-  $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_ins");
+  $node_subscriber->safe_psql('postgres', "SELECT MAX(a) FROM tab_ins");
 is($result, qq(1050), 'check that inserted data was copied to subscriber');
 
+# change to CONNECTION and confirm invalidation
+my $log_offset = -s $node_subscriber->logfile;
+$node_subscriber->safe_psql('postgres',
+	"ALTER SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr'");
+$node_subscriber->wait_for_log(
+	qr/logical replication worker for subscription "tap_sub" will restart because of a parameter change/,
+	$log_offset);
+
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_ins SELECT a, a + 1 FROM generate_series(1051,1057) a");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT MAX(a) FROM tab_ins");
+is($result, qq(1057), 'check subscription after ALTER SUBSCRIPTION ... CONNECTION');
+
+# change back to SERVER and confirm invalidation
+$log_offset = -s $node_subscriber->logfile;
+$node_subscriber->safe_psql('postgres',
+	"ALTER SUBSCRIPTION tap_sub SERVER tap_server");
+$node_subscriber->wait_for_log(
+	qr/logical replication worker for subscription "tap_sub" will restart because of a parameter change/,
+	$log_offset);
+
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_ins SELECT a, a + 1 FROM generate_series(1058,1073) a");
+
+$node_publisher->wait_for_catchup('tap_sub');
+
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT MAX(a) FROM tab_ins");
+is($result, qq(1073), 'check subscription after ALTER SUBSCRIPTION ... SERVER');
+
 done_testing();
-- 
2.43.0

From 305de8a5dd4e960e372cddb1055fbd7d0a1717a3 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Thu, 12 Mar 2026 18:04:35 -0700
Subject: [PATCH v22 3/6] Temp context for maybe_reread_subscription().

Move temp context from ForeignServerConnectionString() to
maybe_reread_subscription(), so that it prevents more
invalidation-related leaks. Remove PG_TRY()/PG_FINALLY() from
ForeignServerConnectionString().

Suggested-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/xvdjrdqnpap3uq7owbaox3r7p5gf7sv62aaqf2ju3vb6yglatr%40kvvwhoudrlxq
---
 src/backend/catalog/pg_subscription.c    | 14 -----
 src/backend/foreign/foreign.c            | 66 +++++++-----------------
 src/backend/replication/logical/worker.c | 30 +++++++++--
 src/include/catalog/pg_subscription.h    |  1 -
 4 files changed, 43 insertions(+), 68 deletions(-)

diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 3673d4f0bc1..ca053c152cf 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -216,20 +216,6 @@ CountDBSubscriptions(Oid dbid)
 	return nsubs;
 }
 
-/*
- * Free memory allocated by subscription struct.
- */
-void
-FreeSubscription(Subscription *sub)
-{
-	pfree(sub->name);
-	pfree(sub->conninfo);
-	if (sub->slotname)
-		pfree(sub->slotname);
-	list_free_deep(sub->publications);
-	pfree(sub);
-}
-
 /*
  * Disable the given subscription.
  */
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 160cf6f51c9..f437b447282 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -219,62 +219,32 @@ GetForeignServerByName(const char *srvname, bool missing_ok)
 
 /*
  * Retrieve connection string from server's FDW.
+ *
+ * NB: leaks into CurrentMemoryContext.
  */
 char *
 ForeignServerConnectionString(Oid userid, Oid serverid)
 {
-	MemoryContext tempContext;
-	MemoryContext oldcxt;
-	text	   *volatile connection_text = NULL;
-	char	   *result = NULL;
-
-	/*
-	 * GetForeignServer, GetForeignDataWrapper, and the connection function
-	 * itself all leak memory into CurrentMemoryContext. Switch to a temporary
-	 * context for easy cleanup.
-	 */
-	tempContext = AllocSetContextCreate(CurrentMemoryContext,
-										"FDWConnectionContext",
-										ALLOCSET_SMALL_SIZES);
-
-	oldcxt = MemoryContextSwitchTo(tempContext);
-
-	PG_TRY();
-	{
-		ForeignServer *server;
-		ForeignDataWrapper *fdw;
-		Datum		connection_datum;
-
-		server = GetForeignServer(serverid);
-		fdw = GetForeignDataWrapper(server->fdwid);
-
-		if (!OidIsValid(fdw->fdwconnection))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("foreign data wrapper \"%s\" does not support subscription connections",
-							fdw->fdwname),
-					 errdetail("Foreign data wrapper must be defined with CONNECTION specified.")));
-
-
-		connection_datum = OidFunctionCall3(fdw->fdwconnection,
-											ObjectIdGetDatum(userid),
-											ObjectIdGetDatum(serverid),
-											PointerGetDatum(NULL));
+	ForeignServer *server;
+	ForeignDataWrapper *fdw;
+	Datum		connection_datum;
 
-		connection_text = DatumGetTextPP(connection_datum);
-	}
-	PG_FINALLY();
-	{
-		MemoryContextSwitchTo(oldcxt);
+	server = GetForeignServer(serverid);
+	fdw = GetForeignDataWrapper(server->fdwid);
 
-		if (connection_text)
-			result = text_to_cstring((text *) connection_text);
+	if (!OidIsValid(fdw->fdwconnection))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("foreign data wrapper \"%s\" does not support subscription connections",
+						fdw->fdwname),
+				 errdetail("Foreign data wrapper must be defined with CONNECTION specified.")));
 
-		MemoryContextDelete(tempContext);
-	}
-	PG_END_TRY();
+	connection_datum = OidFunctionCall3(fdw->fdwconnection,
+										ObjectIdGetDatum(userid),
+										ObjectIdGetDatum(serverid),
+										PointerGetDatum(NULL));
 
-	return result;
+	return text_to_cstring(DatumGetTextPP(connection_datum));
 }
 
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 033858752d9..4ea65a61fb4 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -479,6 +479,7 @@ static MemoryContext LogicalStreamingContext = NULL;
 WalReceiverConn *LogRepWorkerWalRcvConn = NULL;
 
 Subscription *MySubscription = NULL;
+static MemoryContext MySubscriptionCtx = NULL;
 static bool MySubscriptionValid = false;
 
 static List *on_commit_wakeup_workers_subids = NIL;
@@ -5042,6 +5043,7 @@ void
 maybe_reread_subscription(void)
 {
 	MemoryContext oldctx;
+	MemoryContext newctx;
 	Subscription *newsub;
 	bool		started_tx = false;
 
@@ -5056,8 +5058,15 @@ maybe_reread_subscription(void)
 		started_tx = true;
 	}
 
-	/* Ensure allocations in permanent context. */
-	oldctx = MemoryContextSwitchTo(ApplyContext);
+	newctx = AllocSetContextCreate(ApplyContext,
+								   "Subscription Context",
+								   ALLOCSET_SMALL_SIZES);
+
+	/*
+	 * GetSubscription() leaks a number of small allocations, so use a
+	 * subcontext for each call.
+	 */
+	oldctx = MemoryContextSwitchTo(newctx);
 
 	newsub = GetSubscription(MyLogicalRepWorker->subid, true, true);
 
@@ -5149,7 +5158,8 @@ maybe_reread_subscription(void)
 	}
 
 	/* Clean old subscription info and switch to new one. */
-	FreeSubscription(MySubscription);
+	MemoryContextDelete(MySubscriptionCtx);
+	MySubscriptionCtx = newctx;
 	MySubscription = newsub;
 
 	MemoryContextSwitchTo(oldctx);
@@ -5794,12 +5804,19 @@ InitializeLogRepWorker(void)
 	 */
 	SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
 
-	/* Load the subscription into persistent memory context. */
 	ApplyContext = AllocSetContextCreate(TopMemoryContext,
 										 "ApplyContext",
 										 ALLOCSET_DEFAULT_SIZES);
+
+	/*
+	 * GetSubscription() leaks a number of small allocations, so use a
+	 * subcontext for each call.
+	 */
+	MySubscriptionCtx = AllocSetContextCreate(ApplyContext,
+											  "Subscription Context",
+											  ALLOCSET_SMALL_SIZES);
+
 	StartTransactionCommand();
-	oldctx = MemoryContextSwitchTo(ApplyContext);
 
 	/*
 	 * Lock the subscription to prevent it from being concurrently dropped,
@@ -5808,7 +5825,10 @@ InitializeLogRepWorker(void)
 	 */
 	LockSharedObject(SubscriptionRelationId, MyLogicalRepWorker->subid, 0,
 					 AccessShareLock);
+
+	oldctx = MemoryContextSwitchTo(MySubscriptionCtx);
 	MySubscription = GetSubscription(MyLogicalRepWorker->subid, true, true);
+
 	if (!MySubscription)
 	{
 		ereport(LOG,
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index 0058d9387d7..2f6f7b57698 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -212,7 +212,6 @@ typedef struct Subscription
 
 extern Subscription *GetSubscription(Oid subid, bool missing_ok,
 									 bool aclcheck);
-extern void FreeSubscription(Subscription *sub);
 extern void DisableSubscription(Oid subid);
 
 extern int	CountDBSubscriptions(Oid dbid);
-- 
2.43.0

From 9cc1598f8a6686015a0c8167cab56888729395b0 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Fri, 13 Mar 2026 19:37:21 -0700
Subject: [PATCH v22 4/6] Refactor to remove ForeignServerName().

Callers either have a ForeignServer object or can readily construct
one. Also simplify ForeignServerConnectionString() by accepting a
ForeignServer rather than its OID.

Discussion: https://postgr.es/m/CAExHW5vV5znEvecX=ra2-v7ubj9-m6qvddzub78m-txbyd1...@mail.gmail.com
Suggested-by: Ashutosh Bapat <[email protected]>
---
 src/backend/catalog/pg_subscription.c   |  7 ++++--
 src/backend/commands/subscriptioncmds.c | 20 +++++++++-------
 src/backend/foreign/foreign.c           | 31 ++-----------------------
 src/include/foreign/foreign.h           |  4 ++--
 4 files changed, 20 insertions(+), 42 deletions(-)

diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index ca053c152cf..d9e220172e9 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -114,6 +114,9 @@ GetSubscription(Oid subid, bool missing_ok, bool aclcheck)
 	if (OidIsValid(subform->subserver))
 	{
 		AclResult	aclresult;
+		ForeignServer *server;
+
+		server = GetForeignServer(subform->subserver);
 
 		/* recheck ACL if requested */
 		if (aclcheck)
@@ -127,11 +130,11 @@ GetSubscription(Oid subid, bool missing_ok, bool aclcheck)
 						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 						 errmsg("subscription owner \"%s\" does not have permission on foreign server \"%s\"",
 								GetUserNameFromId(subform->subowner, false),
-								ForeignServerName(subform->subserver))));
+								server->servername)));
 		}
 
 		sub->conninfo = ForeignServerConnectionString(subform->subowner,
-													  subform->subserver);
+													  server);
 	}
 	else
 	{
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 724637cff5b..7375e214cb4 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -753,7 +753,7 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		GetUserMapping(owner, server->serverid);
 
 		serverid = server->serverid;
-		conninfo = ForeignServerConnectionString(owner, serverid);
+		conninfo = ForeignServerConnectionString(owner, server);
 	}
 	else
 	{
@@ -1841,13 +1841,13 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 							errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 							errmsg("subscription owner \"%s\" does not have permission on foreign server \"%s\"",
 								   GetUserNameFromId(form->subowner, false),
-								   ForeignServerName(new_server->serverid)));
+								   new_server->servername));
 
 				/* make sure a user mapping exists */
 				GetUserMapping(form->subowner, new_server->serverid);
 
 				conninfo = ForeignServerConnectionString(form->subowner,
-														 new_server->serverid);
+														 new_server);
 
 				/* Load the library providing us libpq calls. */
 				load_file("libpqwalreceiver", false);
@@ -2250,7 +2250,9 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
 	if (OidIsValid(form->subserver))
 	{
 		AclResult	aclresult;
+		ForeignServer *server;
 
+		server = GetForeignServer(form->subserver);
 		aclresult = object_aclcheck(ForeignServerRelationId, form->subserver,
 									form->subowner, ACL_USAGE);
 		if (aclresult != ACLCHECK_OK)
@@ -2263,12 +2265,12 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
 			 */
 			err = psprintf(_("subscription owner \"%s\" does not have permission on foreign server \"%s\""),
 						   GetUserNameFromId(form->subowner, false),
-						   ForeignServerName(form->subserver));
+						   server->servername);
 			conninfo = NULL;
 		}
 		else
 			conninfo = ForeignServerConnectionString(form->subowner,
-													 form->subserver);
+													 server);
 	}
 	else
 	{
@@ -2593,18 +2595,18 @@ AlterSubscriptionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 	 */
 	if (OidIsValid(form->subserver))
 	{
-		Oid			serverid = form->subserver;
+		ForeignServer *server = GetForeignServer(form->subserver);
 
-		aclresult = object_aclcheck(ForeignServerRelationId, serverid, newOwnerId, ACL_USAGE);
+		aclresult = object_aclcheck(ForeignServerRelationId, server->serverid, newOwnerId, ACL_USAGE);
 		if (aclresult != ACLCHECK_OK)
 			ereport(ERROR,
 					errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					errmsg("new subscription owner \"%s\" does not have permission on foreign server \"%s\"",
 						   GetUserNameFromId(newOwnerId, false),
-						   ForeignServerName(serverid)));
+						   server->servername));
 
 		/* make sure a user mapping exists */
-		GetUserMapping(newOwnerId, serverid);
+		GetUserMapping(newOwnerId, server->serverid);
 	}
 
 	form->subowner = newOwnerId;
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index f437b447282..5e9a1ac8514 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -177,31 +177,6 @@ GetForeignServerExtended(Oid serverid, bits16 flags)
 }
 
 
-/*
- * ForeignServerName - get name of foreign server.
- */
-char *
-ForeignServerName(Oid serverid)
-{
-	Form_pg_foreign_server serverform;
-	char	   *servername;
-	HeapTuple	tp;
-
-	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
-
-	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
-
-	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
-
-	servername = pstrdup(NameStr(serverform->srvname));
-
-	ReleaseSysCache(tp);
-
-	return servername;
-}
-
-
 /*
  * GetForeignServerByName - look up the foreign server definition by name.
  */
@@ -223,13 +198,11 @@ GetForeignServerByName(const char *srvname, bool missing_ok)
  * NB: leaks into CurrentMemoryContext.
  */
 char *
-ForeignServerConnectionString(Oid userid, Oid serverid)
+ForeignServerConnectionString(Oid userid, ForeignServer *server)
 {
-	ForeignServer *server;
 	ForeignDataWrapper *fdw;
 	Datum		connection_datum;
 
-	server = GetForeignServer(serverid);
 	fdw = GetForeignDataWrapper(server->fdwid);
 
 	if (!OidIsValid(fdw->fdwconnection))
@@ -241,7 +214,7 @@ ForeignServerConnectionString(Oid userid, Oid serverid)
 
 	connection_datum = OidFunctionCall3(fdw->fdwconnection,
 										ObjectIdGetDatum(userid),
-										ObjectIdGetDatum(serverid),
+										ObjectIdGetDatum(server->serverid),
 										PointerGetDatum(NULL));
 
 	return text_to_cstring(DatumGetTextPP(connection_datum));
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 65ed9a7f987..564c3cc1b7f 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -66,12 +66,12 @@ typedef struct ForeignTable
 
 
 extern ForeignServer *GetForeignServer(Oid serverid);
-extern char *ForeignServerName(Oid serverid);
 extern ForeignServer *GetForeignServerExtended(Oid serverid,
 											   bits16 flags);
 extern ForeignServer *GetForeignServerByName(const char *srvname,
 											 bool missing_ok);
-extern char *ForeignServerConnectionString(Oid userid, Oid serverid);
+extern char *ForeignServerConnectionString(Oid userid,
+										   ForeignServer *server);
 extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
 extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
 extern ForeignDataWrapper *GetForeignDataWrapperExtended(Oid fdwid,
-- 
2.43.0

From d5496c2cdb21cc083a7b844d5ef312d3107941b0 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Sat, 14 Mar 2026 15:07:27 -0700
Subject: [PATCH v22 5/6] Fix pg_dump for CREATE FOREIGN DATA WRAPPER ...
 CONNECTION.

---
 src/bin/pg_dump/pg_dump.c | 15 ++++++++++++++-
 src/bin/pg_dump/pg_dump.h |  1 +
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 137161aa5e0..17fa533086c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -10525,6 +10525,7 @@ getForeignDataWrappers(Archive *fout)
 	int			i_fdwowner;
 	int			i_fdwhandler;
 	int			i_fdwvalidator;
+	int			i_fdwconnection;
 	int			i_fdwacl;
 	int			i_acldefault;
 	int			i_fdwoptions;
@@ -10534,7 +10535,14 @@ getForeignDataWrappers(Archive *fout)
 	appendPQExpBufferStr(query, "SELECT tableoid, oid, fdwname, "
 						 "fdwowner, "
 						 "fdwhandler::pg_catalog.regproc, "
-						 "fdwvalidator::pg_catalog.regproc, "
+						 "fdwvalidator::pg_catalog.regproc, ");
+
+	if (fout->remoteVersion >= 190000)
+		appendPQExpBufferStr(query, "fdwconnection::pg_catalog.regproc, ");
+	else
+		appendPQExpBufferStr(query, "'-' AS fdwconnection, ");
+
+	appendPQExpBufferStr(query,
 						 "fdwacl, "
 						 "acldefault('F', fdwowner) AS acldefault, "
 						 "array_to_string(ARRAY("
@@ -10557,6 +10565,7 @@ getForeignDataWrappers(Archive *fout)
 	i_fdwowner = PQfnumber(res, "fdwowner");
 	i_fdwhandler = PQfnumber(res, "fdwhandler");
 	i_fdwvalidator = PQfnumber(res, "fdwvalidator");
+	i_fdwconnection = PQfnumber(res, "fdwconnection");
 	i_fdwacl = PQfnumber(res, "fdwacl");
 	i_acldefault = PQfnumber(res, "acldefault");
 	i_fdwoptions = PQfnumber(res, "fdwoptions");
@@ -10576,6 +10585,7 @@ getForeignDataWrappers(Archive *fout)
 		fdwinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_fdwowner));
 		fdwinfo[i].fdwhandler = pg_strdup(PQgetvalue(res, i, i_fdwhandler));
 		fdwinfo[i].fdwvalidator = pg_strdup(PQgetvalue(res, i, i_fdwvalidator));
+		fdwinfo[i].fdwconnection = pg_strdup(PQgetvalue(res, i, i_fdwconnection));
 		fdwinfo[i].fdwoptions = pg_strdup(PQgetvalue(res, i, i_fdwoptions));
 
 		/* Decide whether we want to dump it */
@@ -16179,6 +16189,9 @@ dumpForeignDataWrapper(Archive *fout, const FdwInfo *fdwinfo)
 	if (strcmp(fdwinfo->fdwvalidator, "-") != 0)
 		appendPQExpBuffer(q, " VALIDATOR %s", fdwinfo->fdwvalidator);
 
+	if (strcmp(fdwinfo->fdwconnection, "-") != 0)
+		appendPQExpBuffer(q, " CONNECTION %s", fdwinfo->fdwconnection);
+
 	if (strlen(fdwinfo->fdwoptions) > 0)
 		appendPQExpBuffer(q, " OPTIONS (\n    %s\n)", fdwinfo->fdwoptions);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 1c11a79083f..b150d736db1 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -604,6 +604,7 @@ typedef struct _fdwInfo
 	const char *rolname;
 	char	   *fdwhandler;
 	char	   *fdwvalidator;
+	char	   *fdwconnection;
 	char	   *fdwoptions;
 } FdwInfo;
 
-- 
2.43.0

From 2b28e4bc147bbf47bc6912986b4400d634f17131 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Sat, 14 Mar 2026 15:07:52 -0700
Subject: [PATCH v22 6/6] Add pg_dump tests related to CREATE SUBSCRIPTION ...
 SERVER.

Suggested-by: Ashutosh Bapat <[email protected]>
Discussion: https://postgr.es/m/CAExHW5vV5znEvecX=ra2-v7ubj9-m6qvddzub78m-txbyd1...@mail.gmail.com
---
 src/bin/pg_dump/t/002_pg_dump.pl | 49 ++++++++++++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 6d1d38128fc..11a944dd5f8 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2846,6 +2846,40 @@ my %tests = (
 		like => { %full_runs, section_pre_data => 1, },
 	},
 
+	'CREATE FUNCTION public.test_fdw_connection(oid, oid, internal)' => {
+		create_order => 37,
+		create_sql => "CREATE FUNCTION public.test_fdw_connection(oid, oid, internal) RETURNS text AS '\$libdir/regress', 'test_fdw_connection' LANGUAGE C;",
+		regexp => qr/^
+			\QCREATE FUNCTION public.test_fdw_connection(oid, oid, internal) \E
+			\QRETURNS text\E
+			\n\s+\QLANGUAGE c\E
+			\n\s+AS\ \'\$
+			\Qlibdir\/regress', 'test_fdw_connection';\E
+			/xm,
+		like => { %full_runs, section_pre_data => 1, },
+	},
+
+	'CREATE FOREIGN DATA WRAPPER test_fdw CONNECTION public.test_fdw_connection' => {
+		create_order => 38,
+		create_sql => 'CREATE FOREIGN DATA WRAPPER test_fdw CONNECTION public.test_fdw_connection;',
+		regexp => qr/CREATE FOREIGN DATA WRAPPER test_fdw CONNECTION public.test_fdw_connection;/m,
+		like => { %full_runs, section_pre_data => 1, },
+	},
+
+	'CREATE SERVER s2 FOREIGN DATA WRAPPER test_fdw' => {
+		create_order => 39,
+		create_sql => 'CREATE SERVER s2 FOREIGN DATA WRAPPER test_fdw;',
+		regexp => qr/CREATE SERVER s2 FOREIGN DATA WRAPPER test_fdw;/m,
+		like => { %full_runs, section_pre_data => 1, },
+	},
+
+	'CREATE USER MAPPING FOR public SERVER s2' => {
+		create_order => 40,
+		create_sql => 'CREATE USER MAPPING FOR public SERVER s2;',
+		regexp => qr/CREATE USER MAPPING FOR public SERVER s2;/m,
+		like => { %full_runs, section_pre_data => 1, },
+	},
+
 	'CREATE FOREIGN TABLE dump_test.foreign_table SERVER s1' => {
 		create_order => 88,
 		create_sql =>
@@ -3275,6 +3309,21 @@ my %tests = (
 		},
 	},
 
+	'CREATE SUBSCRIPTION sub4 SERVER s2' => {
+		create_order => 50,
+		create_sql => 'CREATE SUBSCRIPTION sub4
+						 SERVER s2 PUBLICATION pub1
+						 WITH (connect = false, slot_name = NONE, origin = any, streaming = on);',
+		regexp => qr/^
+			\QCREATE SUBSCRIPTION sub4 SERVER s2 PUBLICATION pub1 WITH (connect = false, slot_name = NONE, streaming = on);\E
+			/xm,
+		like => { %full_runs, section_post_data => 1, },
+		unlike => {
+			no_subscriptions => 1,
+			no_subscriptions_restore => 1,
+		},
+	},
+
 
 	# Regardless of whether the table or schema is excluded, publications must
 	# still be dumped, as excluded objects do not apply to publications. We
-- 
2.43.0

Reply via email to