From a778d1fd8a6dca1737770043321f7ebc17cec811 Mon Sep 17 00:00:00 2001
From: Jeff Davis <jeff@j-davis.com>
Date: Thu, 26 Feb 2026 10:42:08 -0800
Subject: [PATCH v17 2/2] dblink: support foreign data wrapper CONNECTION
 clause.

---
 contrib/dblink/Makefile                       |   2 +-
 contrib/dblink/dblink--1.2--1.3.sql           |  12 ++
 .../{dblink--1.2.sql => dblink--1.3.sql}      |  11 +-
 contrib/dblink/dblink.c                       | 163 +++++++++---------
 contrib/dblink/dblink.control                 |   2 +-
 contrib/dblink/meson.build                    |   3 +-
 6 files changed, 109 insertions(+), 84 deletions(-)
 create mode 100644 contrib/dblink/dblink--1.2--1.3.sql
 rename contrib/dblink/{dblink--1.2.sql => dblink--1.3.sql} (96%)

diff --git a/contrib/dblink/Makefile b/contrib/dblink/Makefile
index fde0b49ddbb..caa76c9cb27 100644
--- a/contrib/dblink/Makefile
+++ b/contrib/dblink/Makefile
@@ -8,7 +8,7 @@ PG_CPPFLAGS = -I$(libpq_srcdir)
 SHLIB_LINK_INTERNAL = $(libpq)
 
 EXTENSION = dblink
-DATA = dblink--1.2.sql dblink--1.1--1.2.sql dblink--1.0--1.1.sql
+DATA = dblink--1.3.sql dblink--1.2--1.3.sql dblink--1.1--1.2.sql dblink--1.0--1.1.sql
 PGFILEDESC = "dblink - connect to other PostgreSQL databases"
 
 REGRESS = dblink
diff --git a/contrib/dblink/dblink--1.2--1.3.sql b/contrib/dblink/dblink--1.2--1.3.sql
new file mode 100644
index 00000000000..77928a9e656
--- /dev/null
+++ b/contrib/dblink/dblink--1.2--1.3.sql
@@ -0,0 +1,12 @@
+/* contrib/dblink/dblink--1.2--1.3.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION dblink UPDATE TO '1.3'" to load this file. \quit
+
+-- takes internal parameter to prevent calling from SQL
+CREATE FUNCTION dblink_fdw_connection(oid, oid, internal)
+RETURNS text
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+ALTER FOREIGN DATA WRAPPER dblink_fdw CONNECTION dblink_fdw_connection;
diff --git a/contrib/dblink/dblink--1.2.sql b/contrib/dblink/dblink--1.3.sql
similarity index 96%
rename from contrib/dblink/dblink--1.2.sql
rename to contrib/dblink/dblink--1.3.sql
index 405eccb0ff9..22e4ea2061e 100644
--- a/contrib/dblink/dblink--1.2.sql
+++ b/contrib/dblink/dblink--1.3.sql
@@ -1,4 +1,4 @@
-/* contrib/dblink/dblink--1.2.sql */
+/* contrib/dblink/dblink--1.3.sql */
 
 -- complain if script is sourced in psql, rather than via CREATE EXTENSION
 \echo Use "CREATE EXTENSION dblink" to load this file. \quit
@@ -232,4 +232,11 @@ RETURNS void
 AS 'MODULE_PATHNAME', 'dblink_fdw_validator'
 LANGUAGE C STRICT PARALLEL SAFE;
 
-CREATE FOREIGN DATA WRAPPER dblink_fdw VALIDATOR dblink_fdw_validator;
+-- takes internal parameter to prevent calling from SQL
+CREATE FUNCTION dblink_fdw_connection(oid, oid, internal)
+RETURNS text
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FOREIGN DATA WRAPPER dblink_fdw VALIDATOR dblink_fdw_validator
+  CONNECTION dblink_fdw_connection;
diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 2498d80c8e7..3a4b307ff64 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -1993,6 +1993,87 @@ dblink_fdw_validator(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * Implement FDW CONNECTION clause.
+ */
+PG_FUNCTION_INFO_V1(dblink_fdw_connection);
+Datum
+dblink_fdw_connection(PG_FUNCTION_ARGS)
+{
+	Oid			userid = PG_GETARG_OID(0);
+	Oid			serverid = PG_GETARG_OID(1);
+	ForeignServer *foreign_server = GetForeignServer(serverid);
+	UserMapping *user_mapping = GetUserMapping(userid, serverid);
+	ForeignDataWrapper *fdw = GetForeignDataWrapper(foreign_server->fdwid);
+	AclResult	aclresult;
+	ListCell   *cell;
+	StringInfoData buf;
+
+	static const PQconninfoOption *options = NULL;
+
+	initStringInfo(&buf);
+
+	/*
+	 * Get list of valid libpq options.
+	 *
+	 * To avoid unnecessary work, we get the list once and use it throughout
+	 * the lifetime of this backend process.  We don't need to care about
+	 * memory context issues, because PQconndefaults allocates with malloc.
+	 */
+	if (!options)
+	{
+		options = PQconndefaults();
+		if (!options)			/* assume reason for failure is OOM */
+			ereport(ERROR,
+					(errcode(ERRCODE_FDW_OUT_OF_MEMORY),
+					 errmsg("out of memory"),
+					 errdetail("Could not get libpq's default connection options.")));
+	}
+
+	/* Check permissions, user must have usage on the server. */
+	aclresult = object_aclcheck(ForeignServerRelationId, serverid, userid, ACL_USAGE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, foreign_server->servername);
+
+	/*
+	 * First append hardcoded options needed for SCRAM pass-through, so if the
+	 * user overwrites these options we can ereport on dblink_connstr_check
+	 * and dblink_security_check.
+	 */
+	if (MyProcPort != NULL && MyProcPort->has_scram_keys && UseScramPassthrough(foreign_server, user_mapping))
+		appendSCRAMKeysInfo(&buf);
+
+	foreach(cell, fdw->options)
+	{
+		DefElem    *def = lfirst(cell);
+
+		if (is_valid_dblink_option(options, def->defname, ForeignDataWrapperRelationId))
+			appendStringInfo(&buf, "%s='%s' ", def->defname,
+							 escape_param_str(strVal(def->arg)));
+	}
+
+	foreach(cell, foreign_server->options)
+	{
+		DefElem    *def = lfirst(cell);
+
+		if (is_valid_dblink_option(options, def->defname, ForeignServerRelationId))
+			appendStringInfo(&buf, "%s='%s' ", def->defname,
+							 escape_param_str(strVal(def->arg)));
+	}
+
+	foreach(cell, user_mapping->options)
+	{
+
+		DefElem    *def = lfirst(cell);
+
+		if (is_valid_dblink_option(options, def->defname, UserMappingRelationId))
+			appendStringInfo(&buf, "%s='%s' ", def->defname,
+							 escape_param_str(strVal(def->arg)));
+	}
+
+	PG_RETURN_TEXT_P(cstring_to_text(buf.data));
+}
+
 
 /*************************************************************
  * internal functions
@@ -2855,93 +2936,17 @@ static char *
 get_connect_string(const char *servername)
 {
 	ForeignServer *foreign_server = NULL;
-	UserMapping *user_mapping;
-	ListCell   *cell;
-	StringInfoData buf;
-	ForeignDataWrapper *fdw;
-	AclResult	aclresult;
 	char	   *srvname;
 
-	static const PQconninfoOption *options = NULL;
-
-	initStringInfo(&buf);
-
-	/*
-	 * Get list of valid libpq options.
-	 *
-	 * To avoid unnecessary work, we get the list once and use it throughout
-	 * the lifetime of this backend process.  We don't need to care about
-	 * memory context issues, because PQconndefaults allocates with malloc.
-	 */
-	if (!options)
-	{
-		options = PQconndefaults();
-		if (!options)			/* assume reason for failure is OOM */
-			ereport(ERROR,
-					(errcode(ERRCODE_FDW_OUT_OF_MEMORY),
-					 errmsg("out of memory"),
-					 errdetail("Could not get libpq's default connection options.")));
-	}
-
 	/* first gather the server connstr options */
 	srvname = pstrdup(servername);
 	truncate_identifier(srvname, strlen(srvname), false);
 	foreign_server = GetForeignServerByName(srvname, true);
 
-	if (foreign_server)
-	{
-		Oid			serverid = foreign_server->serverid;
-		Oid			fdwid = foreign_server->fdwid;
-		Oid			userid = GetUserId();
-
-		user_mapping = GetUserMapping(userid, serverid);
-		fdw = GetForeignDataWrapper(fdwid);
-
-		/* Check permissions, user must have usage on the server. */
-		aclresult = object_aclcheck(ForeignServerRelationId, serverid, userid, ACL_USAGE);
-		if (aclresult != ACLCHECK_OK)
-			aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, foreign_server->servername);
-
-		/*
-		 * First append hardcoded options needed for SCRAM pass-through, so if
-		 * the user overwrites these options we can ereport on
-		 * dblink_connstr_check and dblink_security_check.
-		 */
-		if (MyProcPort != NULL && MyProcPort->has_scram_keys && UseScramPassthrough(foreign_server, user_mapping))
-			appendSCRAMKeysInfo(&buf);
-
-		foreach(cell, fdw->options)
-		{
-			DefElem    *def = lfirst(cell);
-
-			if (is_valid_dblink_option(options, def->defname, ForeignDataWrapperRelationId))
-				appendStringInfo(&buf, "%s='%s' ", def->defname,
-								 escape_param_str(strVal(def->arg)));
-		}
-
-		foreach(cell, foreign_server->options)
-		{
-			DefElem    *def = lfirst(cell);
-
-			if (is_valid_dblink_option(options, def->defname, ForeignServerRelationId))
-				appendStringInfo(&buf, "%s='%s' ", def->defname,
-								 escape_param_str(strVal(def->arg)));
-		}
-
-		foreach(cell, user_mapping->options)
-		{
-
-			DefElem    *def = lfirst(cell);
-
-			if (is_valid_dblink_option(options, def->defname, UserMappingRelationId))
-				appendStringInfo(&buf, "%s='%s' ", def->defname,
-								 escape_param_str(strVal(def->arg)));
-		}
-
-		return buf.data;
-	}
-	else
+	if (!foreign_server)
 		return NULL;
+
+	return ForeignServerConnectionString(GetUserId(), foreign_server->serverid);
 }
 
 /*
diff --git a/contrib/dblink/dblink.control b/contrib/dblink/dblink.control
index bdd17d28a4b..816d19f4483 100644
--- a/contrib/dblink/dblink.control
+++ b/contrib/dblink/dblink.control
@@ -1,5 +1,5 @@
 # dblink extension
 comment = 'connect to other PostgreSQL databases from within a database'
-default_version = '1.2'
+default_version = '1.3'
 module_pathname = '$libdir/dblink'
 relocatable = true
diff --git a/contrib/dblink/meson.build b/contrib/dblink/meson.build
index e2489f41229..fc91b4a918d 100644
--- a/contrib/dblink/meson.build
+++ b/contrib/dblink/meson.build
@@ -22,7 +22,8 @@ install_data(
   'dblink.control',
   'dblink--1.0--1.1.sql',
   'dblink--1.1--1.2.sql',
-  'dblink--1.2.sql',
+  'dblink--1.2--1.3.sql',
+  'dblink--1.3.sql',
   kwargs: contrib_data_args,
 )
 
-- 
2.43.0

