| /*------------------------------------------------------------------------- |
| * |
| * foreign.c |
| * support for foreign-data wrappers, servers and user mappings. |
| * |
| * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group |
| * |
| * IDENTIFICATION |
| * $PostgreSQL: pgsql/src/backend/foreign/foreign.c,v 1.5 2009/06/11 16:14:18 tgl Exp $ |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/reloptions.h" |
| #include "catalog/namespace.h" |
| #include "catalog/pg_foreign_data_wrapper.h" |
| #include "catalog/pg_foreign_server.h" |
| #include "catalog/pg_type.h" |
| #include "catalog/pg_user_mapping.h" |
| #include "executor/executor.h" |
| #include "foreign/foreign.h" |
| #include "funcapi.h" |
| #include "miscadmin.h" |
| #include "utils/acl.h" |
| #include "utils/array.h" |
| #include "utils/builtins.h" |
| #include "utils/lsyscache.h" |
| #include "utils/memutils.h" |
| #include "utils/syscache.h" |
| #include "utils/tuplestore.h" |
| |
| extern Datum pg_options_to_table(PG_FUNCTION_ARGS); |
| extern Datum gpdb_fdw_validator(PG_FUNCTION_ARGS); |
| |
| |
| |
| /* |
| * GetForeignDataWrapper - look up the foreign-data wrapper by OID. |
| */ |
| ForeignDataWrapper * |
| GetForeignDataWrapper(Oid fdwid) |
| { |
| Form_pg_foreign_data_wrapper fdwform; |
| ForeignDataWrapper *fdw; |
| Datum datum; |
| HeapTuple tp; |
| bool isnull; |
| |
| tp = SearchSysCache(FOREIGNDATAWRAPPEROID, |
| ObjectIdGetDatum(fdwid), |
| 0, 0, 0); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid); |
| |
| fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp); |
| |
| fdw = palloc(sizeof(ForeignDataWrapper)); |
| fdw->fdwid = fdwid; |
| fdw->owner = fdwform->fdwowner; |
| fdw->fdwname = pstrdup(NameStr(fdwform->fdwname)); |
| fdw->fdwvalidator = fdwform->fdwvalidator; |
| |
| /* Extract the options */ |
| datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID, |
| tp, |
| Anum_pg_foreign_data_wrapper_fdwoptions, |
| &isnull); |
| fdw->options = untransformRelOptions(datum); |
| |
| ReleaseSysCache(tp); |
| |
| return fdw; |
| } |
| |
| |
| /* |
| * GetForeignDataWrapperOidByName - look up the foreign-data wrapper |
| * OID by name. |
| */ |
| Oid |
| GetForeignDataWrapperOidByName(const char *fdwname, bool missing_ok) |
| { |
| Oid fdwId; |
| |
| fdwId = GetSysCacheOid(FOREIGNDATAWRAPPERNAME, |
| CStringGetDatum(fdwname), |
| 0, 0, 0); |
| |
| if (!OidIsValid(fdwId) && !missing_ok) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("foreign-data wrapper \"%s\" does not exist", fdwname), |
| errOmitLocation(true))); |
| |
| return fdwId; |
| } |
| |
| |
| /* |
| * GetForeignDataWrapperByName - look up the foreign-data wrapper |
| * definition by name. |
| */ |
| ForeignDataWrapper * |
| GetForeignDataWrapperByName(const char *fdwname, bool missing_ok) |
| { |
| Oid fdwId = GetForeignDataWrapperOidByName(fdwname, missing_ok); |
| |
| if (!OidIsValid(fdwId) && missing_ok) |
| return NULL; |
| |
| return GetForeignDataWrapper(fdwId); |
| } |
| |
| |
| /* |
| * GetForeignServer - look up the foreign server definition. |
| */ |
| ForeignServer * |
| GetForeignServer(Oid serverid) |
| { |
| Form_pg_foreign_server serverform; |
| ForeignServer *server; |
| HeapTuple tp; |
| Datum datum; |
| bool isnull; |
| |
| tp = SearchSysCache(FOREIGNSERVEROID, |
| ObjectIdGetDatum(serverid), |
| 0, 0, 0); |
| |
| if (!HeapTupleIsValid(tp)) |
| elog(ERROR, "cache lookup failed for foreign server %u", serverid); |
| |
| serverform = (Form_pg_foreign_server) GETSTRUCT(tp); |
| |
| server = 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 : pstrdup(TextDatumGetCString(datum)); |
| |
| /* Extract server version */ |
| datum = SysCacheGetAttr(FOREIGNSERVEROID, |
| tp, |
| Anum_pg_foreign_server_srvversion, |
| &isnull); |
| server->serverversion = isnull ? NULL : pstrdup(TextDatumGetCString(datum)); |
| |
| /* Extract the srvoptions */ |
| datum = SysCacheGetAttr(FOREIGNSERVEROID, |
| tp, |
| Anum_pg_foreign_server_srvoptions, |
| &isnull); |
| |
| /* untransformRelOptions does exactly what we want - avoid duplication */ |
| server->options = untransformRelOptions(datum); |
| |
| ReleaseSysCache(tp); |
| |
| return server; |
| } |
| |
| |
| /* |
| * GetForeignServerByName - look up the foreign server oid by name. |
| */ |
| Oid |
| GetForeignServerOidByName(const char *srvname, bool missing_ok) |
| { |
| Oid serverid; |
| |
| serverid = GetSysCacheOid(FOREIGNSERVERNAME, |
| CStringGetDatum(srvname), |
| 0, 0, 0); |
| |
| if (!OidIsValid(serverid) && !missing_ok) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("server \"%s\" does not exist", srvname), |
| errOmitLocation(true))); |
| |
| return serverid; |
| } |
| |
| |
| /* |
| * GetForeignServerByName - look up the foreign server definition by name. |
| */ |
| ForeignServer * |
| GetForeignServerByName(const char *srvname, bool missing_ok) |
| { |
| Oid serverid = GetForeignServerOidByName(srvname, missing_ok); |
| |
| if (!OidIsValid(serverid) && missing_ok) |
| 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) |
| { |
| Form_pg_user_mapping umform; |
| Datum datum; |
| HeapTuple tp; |
| bool isnull; |
| UserMapping *um; |
| |
| tp = SearchSysCache(USERMAPPINGUSERSERVER, |
| ObjectIdGetDatum(userid), |
| ObjectIdGetDatum(serverid), |
| 0, 0); |
| |
| if (!HeapTupleIsValid(tp)) |
| { |
| /* Not found for the specific user -- try PUBLIC */ |
| tp = SearchSysCache(USERMAPPINGUSERSERVER, |
| ObjectIdGetDatum(InvalidOid), |
| ObjectIdGetDatum(serverid), |
| 0, 0); |
| } |
| |
| if (!HeapTupleIsValid(tp)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("user mapping not found for \"%s\"", |
| MappingUserName(userid)), |
| errOmitLocation(true))); |
| |
| umform = (Form_pg_user_mapping) GETSTRUCT(tp); |
| |
| /* Extract the umoptions */ |
| datum = SysCacheGetAttr(USERMAPPINGUSERSERVER, |
| tp, |
| Anum_pg_user_mapping_umoptions, |
| &isnull); |
| |
| um = palloc(sizeof(UserMapping)); |
| um->userid = userid; |
| um->serverid = serverid; |
| um->options = untransformRelOptions(datum); |
| |
| ReleaseSysCache(tp); |
| |
| return um; |
| } |
| |
| |
| /* |
| * deflist_to_tuplestore - Helper function to convert DefElem list to |
| * tuplestore usable in SRF. |
| */ |
| static void |
| deflist_to_tuplestore(ReturnSetInfo *rsinfo, List *options) |
| { |
| ListCell *cell; |
| TupleDesc tupdesc; |
| Tuplestorestate *tupstore; |
| Datum values[2]; |
| bool nulls[2] = {0}; |
| MemoryContext per_query_ctx; |
| MemoryContext oldcontext; |
| |
| /* check to see if caller supports us returning a tuplestore */ |
| if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("set-valued function called in context that cannot accept a set"), |
| errOmitLocation(true))); |
| if (!(rsinfo->allowedModes & SFRM_Materialize) || |
| rsinfo->expectedDesc == NULL) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("materialize mode required, but it is not allowed in this context"), |
| errOmitLocation(true))); |
| |
| per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; |
| oldcontext = MemoryContextSwitchTo(per_query_ctx); |
| |
| /* |
| * Now prepare the result set. |
| */ |
| tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc); |
| tupstore = tuplestore_begin_heap(true, false, work_mem); |
| rsinfo->returnMode = SFRM_Materialize; |
| rsinfo->setResult = tupstore; |
| rsinfo->setDesc = tupdesc; |
| |
| foreach(cell, options) |
| { |
| DefElem *def = lfirst(cell); |
| HeapTuple tuple; |
| |
| values[0] = CStringGetTextDatum(def->defname); |
| values[1] = CStringGetTextDatum(((Value *) def->arg)->val.str); |
| tuple = heap_form_tuple(tupdesc, values, nulls); |
| tuplestore_puttuple(tupstore, tuple); |
| } |
| |
| /* clean up and return the tuplestore */ |
| tuplestore_donestoring(tupstore); |
| |
| MemoryContextSwitchTo(oldcontext); |
| } |
| |
| |
| /* |
| * Convert options array to name/value table. Useful for information |
| * schema and pg_dump. |
| */ |
| Datum |
| pg_options_to_table(PG_FUNCTION_ARGS) |
| { |
| Datum array = PG_GETARG_DATUM(0); |
| |
| deflist_to_tuplestore((ReturnSetInfo *) fcinfo->resultinfo, untransformRelOptions(array)); |
| |
| 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 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) |
| { |
| struct ConnectionOption *opt; |
| |
| for (opt = libpq_conninfo_options; opt->optname; opt++) |
| if ((context == opt->optcontext || context == InvalidOid) && 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. |
| */ |
| Datum |
| gpdb_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)) |
| { |
| 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 == InvalidOid || catalog == opt->optcontext) |
| appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", |
| opt->optname); |
| |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("invalid option \"%s\"", def->defname), |
| errhint("Valid options in this context are: %s", buf.data), |
| errOmitLocation(true))); |
| |
| PG_RETURN_BOOL(false); |
| } |
| } |
| |
| PG_RETURN_BOOL(true); |
| } |