diff options
Diffstat (limited to 'src/backend/foreign')
-rw-r--r-- | src/backend/foreign/Makefile | 18 | ||||
-rw-r--r-- | src/backend/foreign/foreign.c | 804 |
2 files changed, 822 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..e60394c --- /dev/null +++ b/src/backend/foreign/foreign.c @@ -0,0 +1,804 @@ +/*------------------------------------------------------------------------- + * + * foreign.c + * support for foreign-data wrappers, servers and user mappings. + * + * Portions Copyright (c) 1996-2022, 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" + + +/* + * 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}, + {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; + StringInfoData buf; + + /* + * Unknown option specified, complain about it. Provide a hint + * with list of valid options for the object. + */ + initStringInfo(&buf); + for (opt = libpq_conninfo_options; opt->optname; opt++) + if (catalog == opt->optcontext) + appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", + opt->optname); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid option \"%s\"", def->defname), + buf.len > 0 + ? errhint("Valid options in this context are: %s", + buf.data) + : 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; +} |