From a778d1fd8a6dca1737770043321f7ebc17cec811 Mon Sep 17 00:00:00 2001 From: Jeff Davis 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