summaryrefslogtreecommitdiffstats
path: root/src/backend/foreign
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 13:44:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 13:44:03 +0000
commit293913568e6a7a86fd1479e1cff8e2ecb58d6568 (patch)
treefc3b469a3ec5ab71b36ea97cc7aaddb838423a0c /src/backend/foreign
parentInitial commit. (diff)
downloadpostgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.tar.xz
postgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.zip
Adding upstream version 16.2.upstream/16.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/foreign')
-rw-r--r--src/backend/foreign/Makefile18
-rw-r--r--src/backend/foreign/foreign.c813
-rw-r--r--src/backend/foreign/meson.build5
3 files changed, 836 insertions, 0 deletions
diff --git a/src/backend/foreign/Makefile b/src/backend/foreign/Makefile
new file mode 100644
index 0000000..dfea028
--- /dev/null
+++ b/src/backend/foreign/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for foreign
+#
+# IDENTIFICATION
+# src/backend/foreign/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/foreign
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ foreign.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
new file mode 100644
index 0000000..ca3ad55
--- /dev/null
+++ b/src/backend/foreign/foreign.c
@@ -0,0 +1,813 @@
+/*-------------------------------------------------------------------------
+ *
+ * foreign.c
+ * support for foreign-data wrappers, servers and user mappings.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/foreign/foreign.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/reloptions.h"
+#include "catalog/pg_foreign_data_wrapper.h"
+#include "catalog/pg_foreign_server.h"
+#include "catalog/pg_foreign_table.h"
+#include "catalog/pg_user_mapping.h"
+#include "foreign/fdwapi.h"
+#include "foreign/foreign.h"
+#include "funcapi.h"
+#include "lib/stringinfo.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/varlena.h"
+
+
+/*
+ * GetForeignDataWrapper - look up the foreign-data wrapper by OID.
+ */
+ForeignDataWrapper *
+GetForeignDataWrapper(Oid fdwid)
+{
+ return GetForeignDataWrapperExtended(fdwid, 0);
+}
+
+
+/*
+ * GetForeignDataWrapperExtended - look up the foreign-data wrapper
+ * by OID. If flags uses FDW_MISSING_OK, return NULL if the object cannot
+ * be found instead of raising an error.
+ */
+ForeignDataWrapper *
+GetForeignDataWrapperExtended(Oid fdwid, bits16 flags)
+{
+ Form_pg_foreign_data_wrapper fdwform;
+ ForeignDataWrapper *fdw;
+ Datum datum;
+ HeapTuple tp;
+ bool isnull;
+
+ tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
+
+ if (!HeapTupleIsValid(tp))
+ {
+ if ((flags & FDW_MISSING_OK) == 0)
+ elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+ return NULL;
+ }
+
+ fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
+
+ fdw = (ForeignDataWrapper *) palloc(sizeof(ForeignDataWrapper));
+ fdw->fdwid = fdwid;
+ fdw->owner = fdwform->fdwowner;
+ fdw->fdwname = pstrdup(NameStr(fdwform->fdwname));
+ fdw->fdwhandler = fdwform->fdwhandler;
+ fdw->fdwvalidator = fdwform->fdwvalidator;
+
+ /* Extract the fdwoptions */
+ datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
+ tp,
+ Anum_pg_foreign_data_wrapper_fdwoptions,
+ &isnull);
+ if (isnull)
+ fdw->options = NIL;
+ else
+ fdw->options = untransformRelOptions(datum);
+
+ ReleaseSysCache(tp);
+
+ return fdw;
+}
+
+
+/*
+ * GetForeignDataWrapperByName - look up the foreign-data wrapper
+ * definition by name.
+ */
+ForeignDataWrapper *
+GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
+{
+ Oid fdwId = get_foreign_data_wrapper_oid(fdwname, missing_ok);
+
+ if (!OidIsValid(fdwId))
+ return NULL;
+
+ return GetForeignDataWrapper(fdwId);
+}
+
+
+/*
+ * GetForeignServer - look up the foreign server definition.
+ */
+ForeignServer *
+GetForeignServer(Oid serverid)
+{
+ return GetForeignServerExtended(serverid, 0);
+}
+
+
+/*
+ * GetForeignServerExtended - look up the foreign server definition. If
+ * flags uses FSV_MISSING_OK, return NULL if the object cannot be found
+ * instead of raising an error.
+ */
+ForeignServer *
+GetForeignServerExtended(Oid serverid, bits16 flags)
+{
+ Form_pg_foreign_server serverform;
+ ForeignServer *server;
+ HeapTuple tp;
+ Datum datum;
+ bool isnull;
+
+ tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
+
+ if (!HeapTupleIsValid(tp))
+ {
+ if ((flags & FSV_MISSING_OK) == 0)
+ elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+ return NULL;
+ }
+
+ serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
+
+ server = (ForeignServer *) palloc(sizeof(ForeignServer));
+ server->serverid = serverid;
+ server->servername = pstrdup(NameStr(serverform->srvname));
+ server->owner = serverform->srvowner;
+ server->fdwid = serverform->srvfdw;
+
+ /* Extract server type */
+ datum = SysCacheGetAttr(FOREIGNSERVEROID,
+ tp,
+ Anum_pg_foreign_server_srvtype,
+ &isnull);
+ server->servertype = isnull ? NULL : TextDatumGetCString(datum);
+
+ /* Extract server version */
+ datum = SysCacheGetAttr(FOREIGNSERVEROID,
+ tp,
+ Anum_pg_foreign_server_srvversion,
+ &isnull);
+ server->serverversion = isnull ? NULL : TextDatumGetCString(datum);
+
+ /* Extract the srvoptions */
+ datum = SysCacheGetAttr(FOREIGNSERVEROID,
+ tp,
+ Anum_pg_foreign_server_srvoptions,
+ &isnull);
+ if (isnull)
+ server->options = NIL;
+ else
+ server->options = untransformRelOptions(datum);
+
+ ReleaseSysCache(tp);
+
+ return server;
+}
+
+
+/*
+ * GetForeignServerByName - look up the foreign server definition by name.
+ */
+ForeignServer *
+GetForeignServerByName(const char *srvname, bool missing_ok)
+{
+ Oid serverid = get_foreign_server_oid(srvname, missing_ok);
+
+ if (!OidIsValid(serverid))
+ return NULL;
+
+ return GetForeignServer(serverid);
+}
+
+
+/*
+ * GetUserMapping - look up the user mapping.
+ *
+ * If no mapping is found for the supplied user, we also look for
+ * PUBLIC mappings (userid == InvalidOid).
+ */
+UserMapping *
+GetUserMapping(Oid userid, Oid serverid)
+{
+ Datum datum;
+ HeapTuple tp;
+ bool isnull;
+ UserMapping *um;
+
+ tp = SearchSysCache2(USERMAPPINGUSERSERVER,
+ ObjectIdGetDatum(userid),
+ ObjectIdGetDatum(serverid));
+
+ if (!HeapTupleIsValid(tp))
+ {
+ /* Not found for the specific user -- try PUBLIC */
+ tp = SearchSysCache2(USERMAPPINGUSERSERVER,
+ ObjectIdGetDatum(InvalidOid),
+ ObjectIdGetDatum(serverid));
+ }
+
+ if (!HeapTupleIsValid(tp))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("user mapping not found for \"%s\"",
+ MappingUserName(userid))));
+
+ um = (UserMapping *) palloc(sizeof(UserMapping));
+ um->umid = ((Form_pg_user_mapping) GETSTRUCT(tp))->oid;
+ um->userid = userid;
+ um->serverid = serverid;
+
+ /* Extract the umoptions */
+ datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
+ tp,
+ Anum_pg_user_mapping_umoptions,
+ &isnull);
+ if (isnull)
+ um->options = NIL;
+ else
+ um->options = untransformRelOptions(datum);
+
+ ReleaseSysCache(tp);
+
+ return um;
+}
+
+
+/*
+ * GetForeignTable - look up the foreign table definition by relation oid.
+ */
+ForeignTable *
+GetForeignTable(Oid relid)
+{
+ Form_pg_foreign_table tableform;
+ ForeignTable *ft;
+ HeapTuple tp;
+ Datum datum;
+ bool isnull;
+
+ tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for foreign table %u", relid);
+ tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
+
+ ft = (ForeignTable *) palloc(sizeof(ForeignTable));
+ ft->relid = relid;
+ ft->serverid = tableform->ftserver;
+
+ /* Extract the ftoptions */
+ datum = SysCacheGetAttr(FOREIGNTABLEREL,
+ tp,
+ Anum_pg_foreign_table_ftoptions,
+ &isnull);
+ if (isnull)
+ ft->options = NIL;
+ else
+ ft->options = untransformRelOptions(datum);
+
+ ReleaseSysCache(tp);
+
+ return ft;
+}
+
+
+/*
+ * GetForeignColumnOptions - Get attfdwoptions of given relation/attnum
+ * as list of DefElem.
+ */
+List *
+GetForeignColumnOptions(Oid relid, AttrNumber attnum)
+{
+ List *options;
+ HeapTuple tp;
+ Datum datum;
+ bool isnull;
+
+ tp = SearchSysCache2(ATTNUM,
+ ObjectIdGetDatum(relid),
+ Int16GetDatum(attnum));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+ attnum, relid);
+ datum = SysCacheGetAttr(ATTNUM,
+ tp,
+ Anum_pg_attribute_attfdwoptions,
+ &isnull);
+ if (isnull)
+ options = NIL;
+ else
+ options = untransformRelOptions(datum);
+
+ ReleaseSysCache(tp);
+
+ return options;
+}
+
+
+/*
+ * GetFdwRoutine - call the specified foreign-data wrapper handler routine
+ * to get its FdwRoutine struct.
+ */
+FdwRoutine *
+GetFdwRoutine(Oid fdwhandler)
+{
+ Datum datum;
+ FdwRoutine *routine;
+
+ datum = OidFunctionCall0(fdwhandler);
+ routine = (FdwRoutine *) DatumGetPointer(datum);
+
+ if (routine == NULL || !IsA(routine, FdwRoutine))
+ elog(ERROR, "foreign-data wrapper handler function %u did not return an FdwRoutine struct",
+ fdwhandler);
+
+ return routine;
+}
+
+
+/*
+ * GetForeignServerIdByRelId - look up the foreign server
+ * for the given foreign table, and return its OID.
+ */
+Oid
+GetForeignServerIdByRelId(Oid relid)
+{
+ HeapTuple tp;
+ Form_pg_foreign_table tableform;
+ Oid serverid;
+
+ tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for foreign table %u", relid);
+ tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
+ serverid = tableform->ftserver;
+ ReleaseSysCache(tp);
+
+ return serverid;
+}
+
+
+/*
+ * GetFdwRoutineByServerId - look up the handler of the foreign-data wrapper
+ * for the given foreign server, and retrieve its FdwRoutine struct.
+ */
+FdwRoutine *
+GetFdwRoutineByServerId(Oid serverid)
+{
+ HeapTuple tp;
+ Form_pg_foreign_data_wrapper fdwform;
+ Form_pg_foreign_server serverform;
+ Oid fdwid;
+ Oid fdwhandler;
+
+ /* Get foreign-data wrapper OID for the server. */
+ 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);
+ fdwid = serverform->srvfdw;
+ ReleaseSysCache(tp);
+
+ /* Get handler function OID for the FDW. */
+ tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+ fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
+ fdwhandler = fdwform->fdwhandler;
+
+ /* Complain if FDW has been set to NO HANDLER. */
+ if (!OidIsValid(fdwhandler))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("foreign-data wrapper \"%s\" has no handler",
+ NameStr(fdwform->fdwname))));
+
+ ReleaseSysCache(tp);
+
+ /* And finally, call the handler function. */
+ return GetFdwRoutine(fdwhandler);
+}
+
+
+/*
+ * GetFdwRoutineByRelId - look up the handler of the foreign-data wrapper
+ * for the given foreign table, and retrieve its FdwRoutine struct.
+ */
+FdwRoutine *
+GetFdwRoutineByRelId(Oid relid)
+{
+ Oid serverid;
+
+ /* Get server OID for the foreign table. */
+ serverid = GetForeignServerIdByRelId(relid);
+
+ /* Now retrieve server's FdwRoutine struct. */
+ return GetFdwRoutineByServerId(serverid);
+}
+
+/*
+ * GetFdwRoutineForRelation - look up the handler of the foreign-data wrapper
+ * for the given foreign table, and retrieve its FdwRoutine struct.
+ *
+ * This function is preferred over GetFdwRoutineByRelId because it caches
+ * the data in the relcache entry, saving a number of catalog lookups.
+ *
+ * If makecopy is true then the returned data is freshly palloc'd in the
+ * caller's memory context. Otherwise, it's a pointer to the relcache data,
+ * which will be lost in any relcache reset --- so don't rely on it long.
+ */
+FdwRoutine *
+GetFdwRoutineForRelation(Relation relation, bool makecopy)
+{
+ FdwRoutine *fdwroutine;
+ FdwRoutine *cfdwroutine;
+
+ if (relation->rd_fdwroutine == NULL)
+ {
+ /* Get the info by consulting the catalogs and the FDW code */
+ fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(relation));
+
+ /* Save the data for later reuse in CacheMemoryContext */
+ cfdwroutine = (FdwRoutine *) MemoryContextAlloc(CacheMemoryContext,
+ sizeof(FdwRoutine));
+ memcpy(cfdwroutine, fdwroutine, sizeof(FdwRoutine));
+ relation->rd_fdwroutine = cfdwroutine;
+
+ /* Give back the locally palloc'd copy regardless of makecopy */
+ return fdwroutine;
+ }
+
+ /* We have valid cached data --- does the caller want a copy? */
+ if (makecopy)
+ {
+ fdwroutine = (FdwRoutine *) palloc(sizeof(FdwRoutine));
+ memcpy(fdwroutine, relation->rd_fdwroutine, sizeof(FdwRoutine));
+ return fdwroutine;
+ }
+
+ /* Only a short-lived reference is needed, so just hand back cached copy */
+ return relation->rd_fdwroutine;
+}
+
+
+/*
+ * IsImportableForeignTable - filter table names for IMPORT FOREIGN SCHEMA
+ *
+ * Returns true if given table name should be imported according to the
+ * statement's import filter options.
+ */
+bool
+IsImportableForeignTable(const char *tablename,
+ ImportForeignSchemaStmt *stmt)
+{
+ ListCell *lc;
+
+ switch (stmt->list_type)
+ {
+ case FDW_IMPORT_SCHEMA_ALL:
+ return true;
+
+ case FDW_IMPORT_SCHEMA_LIMIT_TO:
+ foreach(lc, stmt->table_list)
+ {
+ RangeVar *rv = (RangeVar *) lfirst(lc);
+
+ if (strcmp(tablename, rv->relname) == 0)
+ return true;
+ }
+ return false;
+
+ case FDW_IMPORT_SCHEMA_EXCEPT:
+ foreach(lc, stmt->table_list)
+ {
+ RangeVar *rv = (RangeVar *) lfirst(lc);
+
+ if (strcmp(tablename, rv->relname) == 0)
+ return false;
+ }
+ return true;
+ }
+ return false; /* shouldn't get here */
+}
+
+
+/*
+ * pg_options_to_table - Convert options array to name/value table
+ *
+ * This is useful to provide details for information_schema and pg_dump.
+ */
+Datum
+pg_options_to_table(PG_FUNCTION_ARGS)
+{
+ Datum array = PG_GETARG_DATUM(0);
+ ListCell *cell;
+ List *options;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ options = untransformRelOptions(array);
+ rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ /* prepare the result set */
+ InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
+
+ foreach(cell, options)
+ {
+ DefElem *def = lfirst(cell);
+ Datum values[2];
+ bool nulls[2];
+
+ values[0] = CStringGetTextDatum(def->defname);
+ nulls[0] = false;
+ if (def->arg)
+ {
+ values[1] = CStringGetTextDatum(strVal(def->arg));
+ nulls[1] = false;
+ }
+ else
+ {
+ values[1] = (Datum) 0;
+ nulls[1] = true;
+ }
+ tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
+ values, nulls);
+ }
+
+ return (Datum) 0;
+}
+
+
+/*
+ * Describes the valid options for postgresql FDW, server, and user mapping.
+ */
+struct ConnectionOption
+{
+ const char *optname;
+ Oid optcontext; /* Oid of catalog in which option may appear */
+};
+
+/*
+ * Copied from fe-connect.c PQconninfoOptions.
+ *
+ * The list is small - don't bother with bsearch if it stays so.
+ */
+static const struct ConnectionOption libpq_conninfo_options[] = {
+ {"authtype", ForeignServerRelationId},
+ {"service", ForeignServerRelationId},
+ {"user", UserMappingRelationId},
+ {"password", UserMappingRelationId},
+ {"connect_timeout", ForeignServerRelationId},
+ {"dbname", ForeignServerRelationId},
+ {"host", ForeignServerRelationId},
+ {"hostaddr", ForeignServerRelationId},
+ {"port", ForeignServerRelationId},
+ {"tty", ForeignServerRelationId},
+ {"options", ForeignServerRelationId},
+ {"requiressl", ForeignServerRelationId},
+ {"sslmode", ForeignServerRelationId},
+ {"gsslib", ForeignServerRelationId},
+ {"gssdelegation", ForeignServerRelationId},
+ {NULL, InvalidOid}
+};
+
+
+/*
+ * Check if the provided option is one of libpq conninfo options.
+ * context is the Oid of the catalog the option came from, or 0 if we
+ * don't care.
+ */
+static bool
+is_conninfo_option(const char *option, Oid context)
+{
+ const struct ConnectionOption *opt;
+
+ for (opt = libpq_conninfo_options; opt->optname; opt++)
+ if (context == opt->optcontext && strcmp(opt->optname, option) == 0)
+ return true;
+ return false;
+}
+
+
+/*
+ * Validate the generic option given to SERVER or USER MAPPING.
+ * Raise an ERROR if the option or its value is considered invalid.
+ *
+ * Valid server options are all libpq conninfo options except
+ * user and password -- these may only appear in USER MAPPING options.
+ *
+ * Caution: this function is deprecated, and is now meant only for testing
+ * purposes, because the list of options it knows about doesn't necessarily
+ * square with those known to whichever libpq instance you might be using.
+ * Inquire of libpq itself, instead.
+ */
+Datum
+postgresql_fdw_validator(PG_FUNCTION_ARGS)
+{
+ List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
+ Oid catalog = PG_GETARG_OID(1);
+
+ ListCell *cell;
+
+ foreach(cell, options_list)
+ {
+ DefElem *def = lfirst(cell);
+
+ if (!is_conninfo_option(def->defname, catalog))
+ {
+ const struct ConnectionOption *opt;
+ const char *closest_match;
+ ClosestMatchState match_state;
+ bool has_valid_options = false;
+
+ /*
+ * Unknown option specified, complain about it. Provide a hint
+ * with a valid option that looks similar, if there is one.
+ */
+ initClosestMatch(&match_state, def->defname, 4);
+ for (opt = libpq_conninfo_options; opt->optname; opt++)
+ {
+ if (catalog == opt->optcontext)
+ {
+ has_valid_options = true;
+ updateClosestMatch(&match_state, opt->optname);
+ }
+ }
+
+ closest_match = getClosestMatch(&match_state);
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid option \"%s\"", def->defname),
+ has_valid_options ? closest_match ?
+ errhint("Perhaps you meant the option \"%s\".",
+ closest_match) : 0 :
+ errhint("There are no valid options in this context.")));
+
+ PG_RETURN_BOOL(false);
+ }
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+
+/*
+ * get_foreign_data_wrapper_oid - given a FDW name, look up the OID
+ *
+ * If missing_ok is false, throw an error if name not found. If true, just
+ * return InvalidOid.
+ */
+Oid
+get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok)
+{
+ Oid oid;
+
+ oid = GetSysCacheOid1(FOREIGNDATAWRAPPERNAME,
+ Anum_pg_foreign_data_wrapper_oid,
+ CStringGetDatum(fdwname));
+ if (!OidIsValid(oid) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("foreign-data wrapper \"%s\" does not exist",
+ fdwname)));
+ return oid;
+}
+
+
+/*
+ * get_foreign_server_oid - given a server name, look up the OID
+ *
+ * If missing_ok is false, throw an error if name not found. If true, just
+ * return InvalidOid.
+ */
+Oid
+get_foreign_server_oid(const char *servername, bool missing_ok)
+{
+ Oid oid;
+
+ oid = GetSysCacheOid1(FOREIGNSERVERNAME, Anum_pg_foreign_server_oid,
+ CStringGetDatum(servername));
+ if (!OidIsValid(oid) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("server \"%s\" does not exist", servername)));
+ return oid;
+}
+
+/*
+ * Get a copy of an existing local path for a given join relation.
+ *
+ * This function is usually helpful to obtain an alternate local path for EPQ
+ * checks.
+ *
+ * Right now, this function only supports unparameterized foreign joins, so we
+ * only search for unparameterized path in the given list of paths. Since we
+ * are searching for a path which can be used to construct an alternative local
+ * plan for a foreign join, we look for only MergeJoin, HashJoin or NestLoop
+ * paths.
+ *
+ * If the inner or outer subpath of the chosen path is a ForeignScan, we
+ * replace it with its outer subpath. For this reason, and also because the
+ * planner might free the original path later, the path returned by this
+ * function is a shallow copy of the original. There's no need to copy
+ * the substructure, so we don't.
+ *
+ * Since the plan created using this path will presumably only be used to
+ * execute EPQ checks, efficiency of the path is not a concern. But since the
+ * path list in RelOptInfo is anyway sorted by total cost we are likely to
+ * choose the most efficient path, which is all for the best.
+ */
+Path *
+GetExistingLocalJoinPath(RelOptInfo *joinrel)
+{
+ ListCell *lc;
+
+ Assert(IS_JOIN_REL(joinrel));
+
+ foreach(lc, joinrel->pathlist)
+ {
+ Path *path = (Path *) lfirst(lc);
+ JoinPath *joinpath = NULL;
+
+ /* Skip parameterized paths. */
+ if (path->param_info != NULL)
+ continue;
+
+ switch (path->pathtype)
+ {
+ case T_HashJoin:
+ {
+ HashPath *hash_path = makeNode(HashPath);
+
+ memcpy(hash_path, path, sizeof(HashPath));
+ joinpath = (JoinPath *) hash_path;
+ }
+ break;
+
+ case T_NestLoop:
+ {
+ NestPath *nest_path = makeNode(NestPath);
+
+ memcpy(nest_path, path, sizeof(NestPath));
+ joinpath = (JoinPath *) nest_path;
+ }
+ break;
+
+ case T_MergeJoin:
+ {
+ MergePath *merge_path = makeNode(MergePath);
+
+ memcpy(merge_path, path, sizeof(MergePath));
+ joinpath = (JoinPath *) merge_path;
+ }
+ break;
+
+ default:
+
+ /*
+ * Just skip anything else. We don't know if corresponding
+ * plan would build the output row from whole-row references
+ * of base relations and execute the EPQ checks.
+ */
+ break;
+ }
+
+ /* This path isn't good for us, check next. */
+ if (!joinpath)
+ continue;
+
+ /*
+ * If either inner or outer path is a ForeignPath corresponding to a
+ * pushed down join, replace it with the fdw_outerpath, so that we
+ * maintain path for EPQ checks built entirely of local join
+ * strategies.
+ */
+ if (IsA(joinpath->outerjoinpath, ForeignPath))
+ {
+ ForeignPath *foreign_path;
+
+ foreign_path = (ForeignPath *) joinpath->outerjoinpath;
+ if (IS_JOIN_REL(foreign_path->path.parent))
+ joinpath->outerjoinpath = foreign_path->fdw_outerpath;
+ }
+
+ if (IsA(joinpath->innerjoinpath, ForeignPath))
+ {
+ ForeignPath *foreign_path;
+
+ foreign_path = (ForeignPath *) joinpath->innerjoinpath;
+ if (IS_JOIN_REL(foreign_path->path.parent))
+ joinpath->innerjoinpath = foreign_path->fdw_outerpath;
+ }
+
+ return (Path *) joinpath;
+ }
+ return NULL;
+}
diff --git a/src/backend/foreign/meson.build b/src/backend/foreign/meson.build
new file mode 100644
index 0000000..6638a6a
--- /dev/null
+++ b/src/backend/foreign/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'foreign.c'
+)