* foreigncmds.c
* foreign-data wrapper/server creation/manipulation commands
*
- * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/foreigncmds.c,v 1.11 2010/02/14 18:42:14 rhaas Exp $
+ * src/backend/commands/foreigncmds.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
+#include "access/htup_details.h"
#include "access/reloptions.h"
-#include "catalog/catalog.h"
+#include "access/xact.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
+#include "catalog/pg_foreign_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
+#include "foreign/fdwapi.h"
#include "foreign/foreign.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
+#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
+typedef struct
+{
+ char *tablename;
+ char *cmd;
+} import_error_callback_arg;
+
+/* Internal functions */
+static void import_error_callback(void *arg);
+
+
/*
* Convert a DefElem list to the text array format that is used in
- * pg_foreign_data_wrapper, pg_foreign_server, and pg_user_mapping.
+ * pg_foreign_data_wrapper, pg_foreign_server, pg_user_mapping, and
+ * pg_foreign_table.
+ *
* Returns the array in the form of a Datum, or PointerGetDatum(NULL)
* if the list is empty.
*
/*
- * Transform a list of DefElem into text array format. This is substantially
+ * Transform a list of DefElem into text array format. This is substantially
* the same thing as optionListToArray(), except we recognize SET/ADD/DROP
* actions for modifying an existing list of options, which is passed in
* Datum form as oldOptions. Also, if fdwvalidator isn't InvalidOid
* Returns the array in the form of a Datum, or PointerGetDatum(NULL)
* if the list is empty.
*
- * This is used by CREATE/ALTER of FOREIGN DATA WRAPPER/SERVER/USER MAPPING.
+ * This is used by CREATE/ALTER of FOREIGN DATA WRAPPER/SERVER/USER MAPPING/
+ * FOREIGN TABLE.
*/
-static Datum
+Datum
transformGenericOptions(Oid catalogId,
Datum oldOptions,
List *options,
/*
* It is possible to perform multiple SET/DROP actions on the same
- * option. The standard permits this, as long as the options to be
+ * option. The standard permits this, as long as the options to be
* added are unique. Note that an unspecified action is taken to be
* ADD.
*/
result = optionListToArray(resultOptions);
- if (fdwvalidator)
- OidFunctionCall2(fdwvalidator, result, ObjectIdGetDatum(catalogId));
-
- return result;
-}
-
-
-/*
- * Convert the user mapping user name to OID
- */
-static Oid
-GetUserOidFromMapping(const char *username, bool missing_ok)
-{
- if (!username)
- /* PUBLIC user mapping */
- return InvalidOid;
+ if (OidIsValid(fdwvalidator))
+ {
+ Datum valarg = result;
- if (strcmp(username, "current_user") == 0)
- /* map to the owner */
- return GetUserId();
+ /*
+ * Pass a null options list as an empty array, so that validators
+ * don't have to be declared non-strict to handle the case.
+ */
+ if (DatumGetPointer(valarg) == NULL)
+ valarg = PointerGetDatum(construct_empty_array(TEXTOID));
+ OidFunctionCall2(fdwvalidator, valarg, ObjectIdGetDatum(catalogId));
+ }
- /* map to provided user */
- return missing_ok ? get_roleid(username) : get_roleid_checked(username);
+ return result;
}
/*
- * Change foreign-data wrapper owner.
+ * Internal workhorse for changing a data wrapper's owner.
*
* Allow this only for superusers; also the new owner must be a
* superuser.
*/
-void
-AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId)
+static void
+AlterForeignDataWrapperOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
{
- HeapTuple tup;
- Relation rel;
- Oid fdwId;
Form_pg_foreign_data_wrapper form;
+ Datum repl_val[Natts_pg_foreign_data_wrapper];
+ bool repl_null[Natts_pg_foreign_data_wrapper];
+ bool repl_repl[Natts_pg_foreign_data_wrapper];
+ Acl *newAcl;
+ Datum aclDatum;
+ bool isNull;
+
+ form = (Form_pg_foreign_data_wrapper) GETSTRUCT(tup);
/* Must be a superuser to change a FDW owner */
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of foreign-data wrapper \"%s\"",
- name),
+ NameStr(form->fdwname)),
errhint("Must be superuser to change owner of a foreign-data wrapper.")));
/* New owner must also be a superuser */
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to change owner of foreign-data wrapper \"%s\"",
- name),
+ NameStr(form->fdwname)),
errhint("The owner of a foreign-data wrapper must be a superuser.")));
+ if (form->fdwowner != newOwnerId)
+ {
+ memset(repl_null, false, sizeof(repl_null));
+ memset(repl_repl, false, sizeof(repl_repl));
+
+ repl_repl[Anum_pg_foreign_data_wrapper_fdwowner - 1] = true;
+ repl_val[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(newOwnerId);
+
+ aclDatum = heap_getattr(tup,
+ Anum_pg_foreign_data_wrapper_fdwacl,
+ RelationGetDescr(rel),
+ &isNull);
+ /* Null ACLs do not require changes */
+ if (!isNull)
+ {
+ newAcl = aclnewowner(DatumGetAclP(aclDatum),
+ form->fdwowner, newOwnerId);
+ repl_repl[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true;
+ repl_val[Anum_pg_foreign_data_wrapper_fdwacl - 1] = PointerGetDatum(newAcl);
+ }
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null,
+ repl_repl);
+
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ /* Update owner dependency reference */
+ changeDependencyOnOwner(ForeignDataWrapperRelationId,
+ HeapTupleGetOid(tup),
+ newOwnerId);
+ }
+
+ InvokeObjectPostAlterHook(ForeignDataWrapperRelationId,
+ HeapTupleGetOid(tup), 0);
+}
+
+/*
+ * Change foreign-data wrapper owner -- by name
+ *
+ * Note restrictions in the "_internal" function, above.
+ */
+ObjectAddress
+AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId)
+{
+ Oid fdwId;
+ HeapTuple tup;
+ Relation rel;
+ ObjectAddress address;
+
rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
tup = SearchSysCacheCopy1(FOREIGNDATAWRAPPERNAME, CStringGetDatum(name));
errmsg("foreign-data wrapper \"%s\" does not exist", name)));
fdwId = HeapTupleGetOid(tup);
- form = (Form_pg_foreign_data_wrapper) GETSTRUCT(tup);
- if (form->fdwowner != newOwnerId)
- {
- form->fdwowner = newOwnerId;
+ AlterForeignDataWrapperOwner_internal(rel, tup, newOwnerId);
- simple_heap_update(rel, &tup->t_self, tup);
- CatalogUpdateIndexes(rel, tup);
-
- /* Update owner dependency reference */
- changeDependencyOnOwner(ForeignDataWrapperRelationId,
- fdwId,
- newOwnerId);
- }
+ ObjectAddressSet(address, ForeignDataWrapperRelationId, fdwId);
- heap_close(rel, NoLock);
heap_freetuple(tup);
-}
+ heap_close(rel, RowExclusiveLock);
+
+ return address;
+}
/*
- * Change foreign server owner
+ * Change foreign-data wrapper owner -- by OID
+ *
+ * Note restrictions in the "_internal" function, above.
*/
void
-AlterForeignServerOwner(const char *name, Oid newOwnerId)
+AlterForeignDataWrapperOwner_oid(Oid fwdId, Oid newOwnerId)
{
HeapTuple tup;
Relation rel;
- Oid srvId;
- AclResult aclresult;
- Form_pg_foreign_server form;
- rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
+ rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
- tup = SearchSysCacheCopy1(FOREIGNSERVERNAME, CStringGetDatum(name));
+ tup = SearchSysCacheCopy1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fwdId));
if (!HeapTupleIsValid(tup))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("server \"%s\" does not exist", name)));
+ errmsg("foreign-data wrapper with OID %u does not exist", fwdId)));
+
+ AlterForeignDataWrapperOwner_internal(rel, tup, newOwnerId);
+
+ heap_freetuple(tup);
+
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Internal workhorse for changing a foreign server's owner
+ */
+static void
+AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
+{
+ Form_pg_foreign_server form;
+ Datum repl_val[Natts_pg_foreign_server];
+ bool repl_null[Natts_pg_foreign_server];
+ bool repl_repl[Natts_pg_foreign_server];
+ Acl *newAcl;
+ Datum aclDatum;
+ bool isNull;
- srvId = HeapTupleGetOid(tup);
form = (Form_pg_foreign_server) GETSTRUCT(tup);
if (form->srvowner != newOwnerId)
/* Superusers can always do it */
if (!superuser())
{
+ Oid srvId;
+ AclResult aclresult;
+
+ srvId = HeapTupleGetOid(tup);
+
/* Must be owner */
if (!pg_foreign_server_ownercheck(srvId, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER,
- name);
+ NameStr(form->srvname));
/* Must be able to become new owner */
check_is_member_of_role(GetUserId(), newOwnerId);
}
}
- form->srvowner = newOwnerId;
+ memset(repl_null, false, sizeof(repl_null));
+ memset(repl_repl, false, sizeof(repl_repl));
- simple_heap_update(rel, &tup->t_self, tup);
- CatalogUpdateIndexes(rel, tup);
+ repl_repl[Anum_pg_foreign_server_srvowner - 1] = true;
+ repl_val[Anum_pg_foreign_server_srvowner - 1] = ObjectIdGetDatum(newOwnerId);
+
+ aclDatum = heap_getattr(tup,
+ Anum_pg_foreign_server_srvacl,
+ RelationGetDescr(rel),
+ &isNull);
+ /* Null ACLs do not require changes */
+ if (!isNull)
+ {
+ newAcl = aclnewowner(DatumGetAclP(aclDatum),
+ form->srvowner, newOwnerId);
+ repl_repl[Anum_pg_foreign_server_srvacl - 1] = true;
+ repl_val[Anum_pg_foreign_server_srvacl - 1] = PointerGetDatum(newAcl);
+ }
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(rel), repl_val, repl_null,
+ repl_repl);
+
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
/* Update owner dependency reference */
changeDependencyOnOwner(ForeignServerRelationId, HeapTupleGetOid(tup),
newOwnerId);
}
- heap_close(rel, NoLock);
+ InvokeObjectPostAlterHook(ForeignServerRelationId,
+ HeapTupleGetOid(tup), 0);
+}
+
+/*
+ * Change foreign server owner -- by name
+ */
+ObjectAddress
+AlterForeignServerOwner(const char *name, Oid newOwnerId)
+{
+ Oid servOid;
+ HeapTuple tup;
+ Relation rel;
+ ObjectAddress address;
+
+ rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
+
+ tup = SearchSysCacheCopy1(FOREIGNSERVERNAME, CStringGetDatum(name));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("server \"%s\" does not exist", name)));
+
+ servOid = HeapTupleGetOid(tup);
+
+ AlterForeignServerOwner_internal(rel, tup, newOwnerId);
+
+ ObjectAddressSet(address, ForeignServerRelationId, servOid);
+
+ heap_freetuple(tup);
+
+ heap_close(rel, RowExclusiveLock);
+
+ return address;
+}
+
+/*
+ * Change foreign server owner -- by OID
+ */
+void
+AlterForeignServerOwner_oid(Oid srvId, Oid newOwnerId)
+{
+ HeapTuple tup;
+ Relation rel;
+
+ rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
+
+ tup = SearchSysCacheCopy1(FOREIGNSERVEROID, ObjectIdGetDatum(srvId));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("foreign server with OID %u does not exist", srvId)));
+
+ AlterForeignServerOwner_internal(rel, tup, newOwnerId);
+
heap_freetuple(tup);
+
+ heap_close(rel, RowExclusiveLock);
}
+/*
+ * Convert a handler function name passed from the parser to an Oid.
+ */
+static Oid
+lookup_fdw_handler_func(DefElem *handler)
+{
+ Oid handlerOid;
+ Oid funcargtypes[1]; /* dummy */
+
+ if (handler == NULL || handler->arg == NULL)
+ return InvalidOid;
+
+ /* handlers have no arguments */
+ handlerOid = LookupFuncName((List *) handler->arg, 0, funcargtypes, false);
+
+ /* check that handler has correct return type */
+ if (get_func_rettype(handlerOid) != FDW_HANDLEROID)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("function %s must return type %s",
+ NameListToString((List *) handler->arg), "fdw_handler")));
+
+ return handlerOid;
+}
/*
* Convert a validator function name passed from the parser to an Oid.
*/
static Oid
-lookup_fdw_validator_func(List *validator)
+lookup_fdw_validator_func(DefElem *validator)
{
Oid funcargtypes[2];
+ if (validator == NULL || validator->arg == NULL)
+ return InvalidOid;
+
+ /* validators take text[], oid */
funcargtypes[0] = TEXTARRAYOID;
funcargtypes[1] = OIDOID;
- return LookupFuncName(validator, 2, funcargtypes, false);
- /* return value is ignored, so we don't check the type */
+
+ return LookupFuncName((List *) validator->arg, 2, funcargtypes, false);
+ /* validator's return value is ignored, so we don't check the type */
}
+/*
+ * Process function options of CREATE/ALTER FDW
+ */
+static void
+parse_func_options(List *func_options,
+ bool *handler_given, Oid *fdwhandler,
+ bool *validator_given, Oid *fdwvalidator)
+{
+ ListCell *cell;
+
+ *handler_given = false;
+ *validator_given = false;
+ /* return InvalidOid if not given */
+ *fdwhandler = InvalidOid;
+ *fdwvalidator = InvalidOid;
+
+ foreach(cell, func_options)
+ {
+ DefElem *def = (DefElem *) lfirst(cell);
+
+ if (strcmp(def->defname, "handler") == 0)
+ {
+ if (*handler_given)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ *handler_given = true;
+ *fdwhandler = lookup_fdw_handler_func(def);
+ }
+ else if (strcmp(def->defname, "validator") == 0)
+ {
+ if (*validator_given)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ *validator_given = true;
+ *fdwvalidator = lookup_fdw_validator_func(def);
+ }
+ else
+ elog(ERROR, "option \"%s\" not recognized",
+ def->defname);
+ }
+}
/*
* Create a foreign-data wrapper
*/
-void
+ObjectAddress
CreateForeignDataWrapper(CreateFdwStmt *stmt)
{
Relation rel;
bool nulls[Natts_pg_foreign_data_wrapper];
HeapTuple tuple;
Oid fdwId;
+ bool handler_given;
+ bool validator_given;
+ Oid fdwhandler;
Oid fdwvalidator;
Datum fdwoptions;
Oid ownerId;
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
/* Must be super user */
if (!superuser())
/*
* Insert tuple into pg_foreign_data_wrapper.
*/
- rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
-
memset(values, 0, sizeof(values));
memset(nulls, false, sizeof(nulls));
DirectFunctionCall1(namein, CStringGetDatum(stmt->fdwname));
values[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(ownerId);
- if (stmt->validator)
- fdwvalidator = lookup_fdw_validator_func(stmt->validator);
- else
- fdwvalidator = InvalidOid;
+ /* Lookup handler and validator functions, if given */
+ parse_func_options(stmt->func_options,
+ &handler_given, &fdwhandler,
+ &validator_given, &fdwvalidator);
- values[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = fdwvalidator;
+ values[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = ObjectIdGetDatum(fdwhandler);
+ values[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = ObjectIdGetDatum(fdwvalidator);
nulls[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true;
tuple = heap_form_tuple(rel->rd_att, values, nulls);
- fdwId = simple_heap_insert(rel, tuple);
- CatalogUpdateIndexes(rel, tuple);
+ fdwId = CatalogTupleInsert(rel, tuple);
heap_freetuple(tuple);
- if (fdwvalidator)
- {
- ObjectAddress myself;
- ObjectAddress referenced;
+ /* record dependencies */
+ myself.classId = ForeignDataWrapperRelationId;
+ myself.objectId = fdwId;
+ myself.objectSubId = 0;
- myself.classId = ForeignDataWrapperRelationId;
- myself.objectId = fdwId;
- myself.objectSubId = 0;
+ if (OidIsValid(fdwhandler))
+ {
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = fdwhandler;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+ if (OidIsValid(fdwvalidator))
+ {
referenced.classId = ProcedureRelationId;
referenced.objectId = fdwvalidator;
referenced.objectSubId = 0;
recordDependencyOnOwner(ForeignDataWrapperRelationId, fdwId, ownerId);
- heap_close(rel, NoLock);
+ /* dependency on extension */
+ recordDependencyOnCurrentExtension(&myself, false);
+
+ /* Post creation hook for new foreign data wrapper */
+ InvokeObjectPostCreateHook(ForeignDataWrapperRelationId, fdwId, 0);
+
+ heap_close(rel, RowExclusiveLock);
+
+ return myself;
}
/*
* Alter foreign-data wrapper
*/
-void
+ObjectAddress
AlterForeignDataWrapper(AlterFdwStmt *stmt)
{
Relation rel;
HeapTuple tp;
+ Form_pg_foreign_data_wrapper fdwForm;
Datum repl_val[Natts_pg_foreign_data_wrapper];
bool repl_null[Natts_pg_foreign_data_wrapper];
bool repl_repl[Natts_pg_foreign_data_wrapper];
Oid fdwId;
bool isnull;
Datum datum;
+ bool handler_given;
+ bool validator_given;
+ Oid fdwhandler;
Oid fdwvalidator;
+ ObjectAddress myself;
+
+ rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
/* Must be super user */
if (!superuser())
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("foreign-data wrapper \"%s\" does not exist", stmt->fdwname)));
+ fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
fdwId = HeapTupleGetOid(tp);
memset(repl_val, 0, sizeof(repl_val));
memset(repl_null, false, sizeof(repl_null));
memset(repl_repl, false, sizeof(repl_repl));
- if (stmt->change_validator)
+ parse_func_options(stmt->func_options,
+ &handler_given, &fdwhandler,
+ &validator_given, &fdwvalidator);
+
+ if (handler_given)
+ {
+ repl_val[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = ObjectIdGetDatum(fdwhandler);
+ repl_repl[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = true;
+
+ /*
+ * It could be that the behavior of accessing foreign table changes
+ * with the new handler. Warn about this.
+ */
+ ereport(WARNING,
+ (errmsg("changing the foreign-data wrapper handler can change behavior of existing foreign tables")));
+ }
+
+ if (validator_given)
{
- fdwvalidator = stmt->validator ? lookup_fdw_validator_func(stmt->validator) : InvalidOid;
repl_val[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = ObjectIdGetDatum(fdwvalidator);
repl_repl[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = true;
/*
- * It could be that the options for the FDW, SERVER and USER MAPPING
- * are no longer valid with the new validator. Warn about this.
+ * It could be that existing options for the FDW or dependent SERVER,
+ * USER MAPPING or FOREIGN TABLE objects are no longer valid according
+ * to the new validator. Warn about this.
*/
- if (stmt->validator)
+ if (OidIsValid(fdwvalidator))
ereport(WARNING,
(errmsg("changing the foreign-data wrapper validator can cause "
"the options for dependent objects to become invalid")));
/*
* Validator is not changed, but we need it for validating options.
*/
- datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
- tp,
- Anum_pg_foreign_data_wrapper_fdwvalidator,
- &isnull);
- Assert(!isnull);
- fdwvalidator = DatumGetObjectId(datum);
+ fdwvalidator = fdwForm->fdwvalidator;
}
/*
- * Options specified, validate and update.
+ * If options specified, validate and update.
*/
if (stmt->options)
{
}
/* Everything looks good - update the tuple */
-
- rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
-
tp = heap_modify_tuple(tp, RelationGetDescr(rel),
repl_val, repl_null, repl_repl);
- simple_heap_update(rel, &tp->t_self, tp);
- CatalogUpdateIndexes(rel, tp);
+ CatalogTupleUpdate(rel, &tp->t_self, tp);
- heap_close(rel, RowExclusiveLock);
heap_freetuple(tp);
-}
+ ObjectAddressSet(myself, ForeignDataWrapperRelationId, fdwId);
-/*
- * Drop foreign-data wrapper
- */
-void
-RemoveForeignDataWrapper(DropFdwStmt *stmt)
-{
- Oid fdwId;
- ObjectAddress object;
+ /* Update function dependencies if we changed them */
+ if (handler_given || validator_given)
+ {
+ ObjectAddress referenced;
- fdwId = GetForeignDataWrapperOidByName(stmt->fdwname, true);
+ /*
+ * Flush all existing dependency records of this FDW on functions; we
+ * assume there can be none other than the ones we are fixing.
+ */
+ deleteDependencyRecordsForClass(ForeignDataWrapperRelationId,
+ fdwId,
+ ProcedureRelationId,
+ DEPENDENCY_NORMAL);
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to drop foreign-data wrapper \"%s\"",
- stmt->fdwname),
- errhint("Must be superuser to drop a foreign-data wrapper.")));
+ /* And build new ones. */
- if (!OidIsValid(fdwId))
- {
- if (!stmt->missing_ok)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("foreign-data wrapper \"%s\" does not exist",
- stmt->fdwname)));
+ if (OidIsValid(fdwhandler))
+ {
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = fdwhandler;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
- /* IF EXISTS specified, just note it */
- ereport(NOTICE,
- (errmsg("foreign-data wrapper \"%s\" does not exist, skipping",
- stmt->fdwname)));
- return;
+ if (OidIsValid(fdwvalidator))
+ {
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = fdwvalidator;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
}
- /*
- * Do the deletion
- */
- object.classId = ForeignDataWrapperRelationId;
- object.objectId = fdwId;
- object.objectSubId = 0;
+ InvokeObjectPostAlterHook(ForeignDataWrapperRelationId, fdwId, 0);
- performDeletion(&object, stmt->behavior);
+ heap_close(rel, RowExclusiveLock);
+
+ return myself;
}
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwId);
- simple_heap_delete(rel, &tp->t_self);
+ CatalogTupleDelete(rel, &tp->t_self);
ReleaseSysCache(tp);
/*
* Create a foreign server
*/
-void
+ObjectAddress
CreateForeignServer(CreateForeignServerStmt *stmt)
{
Relation rel;
ObjectAddress referenced;
ForeignDataWrapper *fdw;
+ rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
+
/* For now the owner cannot be specified on create. Use effective user ID. */
ownerId = GetUserId();
/*
- * Check that there is no other foreign server by this name.
+ * Check that there is no other foreign server by this name. Do nothing if
+ * IF NOT EXISTS was enforced.
*/
if (GetForeignServerByName(stmt->servername, true) != NULL)
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_OBJECT),
- errmsg("server \"%s\" already exists",
- stmt->servername)));
+ {
+ if (stmt->if_not_exists)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("server \"%s\" already exists, skipping",
+ stmt->servername)));
+ heap_close(rel, RowExclusiveLock);
+ return InvalidObjectAddress;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("server \"%s\" already exists",
+ stmt->servername)));
+ }
/*
* Check that the FDW exists and that we have USAGE on it. Also get the
/*
* Insert tuple into pg_foreign_server.
*/
- rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
-
memset(values, 0, sizeof(values));
memset(nulls, false, sizeof(nulls));
tuple = heap_form_tuple(rel->rd_att, values, nulls);
- srvId = simple_heap_insert(rel, tuple);
-
- CatalogUpdateIndexes(rel, tuple);
+ srvId = CatalogTupleInsert(rel, tuple);
heap_freetuple(tuple);
- /* Add dependency on FDW and owner */
+ /* record dependencies */
myself.classId = ForeignServerRelationId;
myself.objectId = srvId;
myself.objectSubId = 0;
recordDependencyOnOwner(ForeignServerRelationId, srvId, ownerId);
- heap_close(rel, NoLock);
+ /* dependency on extension */
+ recordDependencyOnCurrentExtension(&myself, false);
+
+ /* Post creation hook for new foreign server */
+ InvokeObjectPostCreateHook(ForeignServerRelationId, srvId, 0);
+
+ heap_close(rel, RowExclusiveLock);
+
+ return myself;
}
/*
* Alter foreign server
*/
-void
+ObjectAddress
AlterForeignServer(AlterForeignServerStmt *stmt)
{
Relation rel;
bool repl_repl[Natts_pg_foreign_server];
Oid srvId;
Form_pg_foreign_server srvForm;
+ ObjectAddress address;
+
+ rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
tp = SearchSysCacheCopy1(FOREIGNSERVERNAME,
CStringGetDatum(stmt->servername));
}
/* Everything looks good - update the tuple */
-
- rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
-
tp = heap_modify_tuple(tp, RelationGetDescr(rel),
repl_val, repl_null, repl_repl);
- simple_heap_update(rel, &tp->t_self, tp);
- CatalogUpdateIndexes(rel, tp);
-
- heap_close(rel, RowExclusiveLock);
- heap_freetuple(tp);
-}
-
-
-/*
- * Drop foreign server
- */
-void
-RemoveForeignServer(DropForeignServerStmt *stmt)
-{
- Oid srvId;
- ObjectAddress object;
+ CatalogTupleUpdate(rel, &tp->t_self, tp);
- srvId = GetForeignServerOidByName(stmt->servername, true);
+ InvokeObjectPostAlterHook(ForeignServerRelationId, srvId, 0);
- if (!OidIsValid(srvId))
- {
- /* Server not found, complain or notice */
- if (!stmt->missing_ok)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("server \"%s\" does not exist", stmt->servername)));
+ ObjectAddressSet(address, ForeignServerRelationId, srvId);
- /* IF EXISTS specified, just note it */
- ereport(NOTICE,
- (errmsg("server \"%s\" does not exist, skipping",
- stmt->servername)));
- return;
- }
-
- /* Only allow DROP if the server is owned by the user. */
- if (!pg_foreign_server_ownercheck(srvId, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER,
- stmt->servername);
+ heap_freetuple(tp);
- object.classId = ForeignServerRelationId;
- object.objectId = srvId;
- object.objectSubId = 0;
+ heap_close(rel, RowExclusiveLock);
- performDeletion(&object, stmt->behavior);
+ return address;
}
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for foreign server %u", srvId);
- simple_heap_delete(rel, &tp->t_self);
+ CatalogTupleDelete(rel, &tp->t_self);
ReleaseSysCache(tp);
/*
* Create user mapping
*/
-void
+ObjectAddress
CreateUserMapping(CreateUserMappingStmt *stmt)
{
Relation rel;
ObjectAddress referenced;
ForeignServer *srv;
ForeignDataWrapper *fdw;
+ RoleSpec *role = (RoleSpec *) stmt->user;
+
+ rel = heap_open(UserMappingRelationId, RowExclusiveLock);
- useId = GetUserOidFromMapping(stmt->username, false);
+ if (role->roletype == ROLESPEC_PUBLIC)
+ useId = ACL_ID_PUBLIC;
+ else
+ useId = get_rolespec_oid(stmt->user, false);
/* Check that the server exists. */
srv = GetForeignServerByName(stmt->servername, false);
umId = GetSysCacheOid2(USERMAPPINGUSERSERVER,
ObjectIdGetDatum(useId),
ObjectIdGetDatum(srv->serverid));
+
if (OidIsValid(umId))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_OBJECT),
- errmsg("user mapping \"%s\" already exists for server %s",
- MappingUserName(useId),
- stmt->servername)));
+ {
+ if (stmt->if_not_exists)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("user mapping for \"%s\" already exists for server %s, skipping",
+ MappingUserName(useId),
+ stmt->servername)));
+
+ heap_close(rel, RowExclusiveLock);
+ return InvalidObjectAddress;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("user mapping for \"%s\" already exists for server %s",
+ MappingUserName(useId),
+ stmt->servername)));
+ }
fdw = GetForeignDataWrapper(srv->fdwid);
/*
* Insert tuple into pg_user_mapping.
*/
- rel = heap_open(UserMappingRelationId, RowExclusiveLock);
-
memset(values, 0, sizeof(values));
memset(nulls, false, sizeof(nulls));
tuple = heap_form_tuple(rel->rd_att, values, nulls);
- umId = simple_heap_insert(rel, tuple);
-
- CatalogUpdateIndexes(rel, tuple);
+ umId = CatalogTupleInsert(rel, tuple);
heap_freetuple(tuple);
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
if (OidIsValid(useId))
+ {
/* Record the mapped user dependency */
recordDependencyOnOwner(UserMappingRelationId, umId, useId);
+ }
+
+ /* dependency on extension */
+ recordDependencyOnCurrentExtension(&myself, false);
+
+ /* Post creation hook for new user mapping */
+ InvokeObjectPostCreateHook(UserMappingRelationId, umId, 0);
+
+ heap_close(rel, RowExclusiveLock);
- heap_close(rel, NoLock);
+ return myself;
}
/*
* Alter user mapping
*/
-void
+ObjectAddress
AlterUserMapping(AlterUserMappingStmt *stmt)
{
Relation rel;
Oid useId;
Oid umId;
ForeignServer *srv;
+ ObjectAddress address;
+ RoleSpec *role = (RoleSpec *) stmt->user;
+
+ rel = heap_open(UserMappingRelationId, RowExclusiveLock);
+
+ if (role->roletype == ROLESPEC_PUBLIC)
+ useId = ACL_ID_PUBLIC;
+ else
+ useId = get_rolespec_oid(stmt->user, false);
- useId = GetUserOidFromMapping(stmt->username, false);
srv = GetForeignServerByName(stmt->servername, false);
umId = GetSysCacheOid2(USERMAPPINGUSERSERVER,
if (!OidIsValid(umId))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("user mapping \"%s\" does not exist for the server",
- MappingUserName(useId))));
+ errmsg("user mapping for \"%s\" does not exist for the server",
+ MappingUserName(useId))));
user_mapping_ddl_aclcheck(useId, srv->serverid, stmt->servername);
}
/* Everything looks good - update the tuple */
-
- rel = heap_open(UserMappingRelationId, RowExclusiveLock);
-
tp = heap_modify_tuple(tp, RelationGetDescr(rel),
repl_val, repl_null, repl_repl);
- simple_heap_update(rel, &tp->t_self, tp);
- CatalogUpdateIndexes(rel, tp);
+ CatalogTupleUpdate(rel, &tp->t_self, tp);
+
+ ObjectAddressSet(address, UserMappingRelationId, umId);
- heap_close(rel, RowExclusiveLock);
heap_freetuple(tp);
+
+ heap_close(rel, RowExclusiveLock);
+
+ return address;
}
/*
* Drop user mapping
*/
-void
+Oid
RemoveUserMapping(DropUserMappingStmt *stmt)
{
ObjectAddress object;
Oid useId;
Oid umId;
ForeignServer *srv;
+ RoleSpec *role = (RoleSpec *) stmt->user;
- useId = GetUserOidFromMapping(stmt->username, stmt->missing_ok);
- srv = GetForeignServerByName(stmt->servername, true);
-
- if (stmt->username && !OidIsValid(useId))
+ if (role->roletype == ROLESPEC_PUBLIC)
+ useId = ACL_ID_PUBLIC;
+ else
{
- /*
- * IF EXISTS specified, role not found and not public. Notice this and
- * leave.
- */
- elog(NOTICE, "role \"%s\" does not exist, skipping", stmt->username);
- return;
+ useId = get_rolespec_oid(stmt->user, stmt->missing_ok);
+ if (!OidIsValid(useId))
+ {
+ /*
+ * IF EXISTS specified, role not found and not public. Notice this
+ * and leave.
+ */
+ elog(NOTICE, "role \"%s\" does not exist, skipping",
+ role->rolename);
+ return InvalidOid;
+ }
}
+ srv = GetForeignServerByName(stmt->servername, true);
+
if (!srv)
{
if (!stmt->missing_ok)
stmt->servername)));
/* IF EXISTS, just note it */
ereport(NOTICE, (errmsg("server does not exist, skipping")));
- return;
+ return InvalidOid;
}
umId = GetSysCacheOid2(USERMAPPINGUSERSERVER,
if (!stmt->missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("user mapping \"%s\" does not exist for the server",
- MappingUserName(useId))));
+ errmsg("user mapping for \"%s\" does not exist for the server",
+ MappingUserName(useId))));
/* IF EXISTS specified, just note it */
ereport(NOTICE,
- (errmsg("user mapping \"%s\" does not exist for the server, skipping",
- MappingUserName(useId))));
- return;
+ (errmsg("user mapping for \"%s\" does not exist for the server, skipping",
+ MappingUserName(useId))));
+ return InvalidOid;
}
user_mapping_ddl_aclcheck(useId, srv->serverid, srv->servername);
object.objectId = umId;
object.objectSubId = 0;
- performDeletion(&object, DROP_CASCADE);
+ performDeletion(&object, DROP_CASCADE, 0);
+
+ return umId;
}
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for user mapping %u", umId);
- simple_heap_delete(rel, &tp->t_self);
+ CatalogTupleDelete(rel, &tp->t_self);
ReleaseSysCache(tp);
heap_close(rel, RowExclusiveLock);
}
+
+/*
+ * Create a foreign table
+ * call after DefineRelation().
+ */
+void
+CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
+{
+ Relation ftrel;
+ Datum ftoptions;
+ Datum values[Natts_pg_foreign_table];
+ bool nulls[Natts_pg_foreign_table];
+ HeapTuple tuple;
+ AclResult aclresult;
+ ObjectAddress myself;
+ ObjectAddress referenced;
+ Oid ownerId;
+ ForeignDataWrapper *fdw;
+ ForeignServer *server;
+
+ /*
+ * Advance command counter to ensure the pg_attribute tuple is visible;
+ * the tuple might be updated to add constraints in previous step.
+ */
+ CommandCounterIncrement();
+
+ ftrel = heap_open(ForeignTableRelationId, RowExclusiveLock);
+
+ /*
+ * For now the owner cannot be specified on create. Use effective user ID.
+ */
+ ownerId = GetUserId();
+
+ /*
+ * Check that the foreign server exists and that we have USAGE on it. Also
+ * get the actual FDW for option validation etc.
+ */
+ server = GetForeignServerByName(stmt->servername, false);
+ aclresult = pg_foreign_server_aclcheck(server->serverid, ownerId, ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, server->servername);
+
+ fdw = GetForeignDataWrapper(server->fdwid);
+
+ /*
+ * Insert tuple into pg_foreign_table.
+ */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ values[Anum_pg_foreign_table_ftrelid - 1] = ObjectIdGetDatum(relid);
+ values[Anum_pg_foreign_table_ftserver - 1] = ObjectIdGetDatum(server->serverid);
+ /* Add table generic options */
+ ftoptions = transformGenericOptions(ForeignTableRelationId,
+ PointerGetDatum(NULL),
+ stmt->options,
+ fdw->fdwvalidator);
+
+ if (PointerIsValid(DatumGetPointer(ftoptions)))
+ values[Anum_pg_foreign_table_ftoptions - 1] = ftoptions;
+ else
+ nulls[Anum_pg_foreign_table_ftoptions - 1] = true;
+
+ tuple = heap_form_tuple(ftrel->rd_att, values, nulls);
+
+ CatalogTupleInsert(ftrel, tuple);
+
+ heap_freetuple(tuple);
+
+ /* Add pg_class dependency on the server */
+ myself.classId = RelationRelationId;
+ myself.objectId = relid;
+ myself.objectSubId = 0;
+
+ referenced.classId = ForeignServerRelationId;
+ referenced.objectId = server->serverid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ heap_close(ftrel, RowExclusiveLock);
+}
+
+/*
+ * Import a foreign schema
+ */
+void
+ImportForeignSchema(ImportForeignSchemaStmt *stmt)
+{
+ ForeignServer *server;
+ ForeignDataWrapper *fdw;
+ FdwRoutine *fdw_routine;
+ AclResult aclresult;
+ List *cmd_list;
+ ListCell *lc;
+
+ /* Check that the foreign server exists and that we have USAGE on it */
+ server = GetForeignServerByName(stmt->server_name, false);
+ aclresult = pg_foreign_server_aclcheck(server->serverid, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, server->servername);
+
+ /* Check that the schema exists and we have CREATE permissions on it */
+ (void) LookupCreationNamespace(stmt->local_schema);
+
+ /* Get the FDW and check it supports IMPORT */
+ fdw = GetForeignDataWrapper(server->fdwid);
+ if (!OidIsValid(fdw->fdwhandler))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("foreign-data wrapper \"%s\" has no handler",
+ fdw->fdwname)));
+ fdw_routine = GetFdwRoutine(fdw->fdwhandler);
+ if (fdw_routine->ImportForeignSchema == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FDW_NO_SCHEMAS),
+ errmsg("foreign-data wrapper \"%s\" does not support IMPORT FOREIGN SCHEMA",
+ fdw->fdwname)));
+
+ /* Call FDW to get a list of commands */
+ cmd_list = fdw_routine->ImportForeignSchema(stmt, server->serverid);
+
+ /* Parse and execute each command */
+ foreach(lc, cmd_list)
+ {
+ char *cmd = (char *) lfirst(lc);
+ import_error_callback_arg callback_arg;
+ ErrorContextCallback sqlerrcontext;
+ List *raw_parsetree_list;
+ ListCell *lc2;
+
+ /*
+ * Setup error traceback support for ereport(). This is so that any
+ * error in the generated SQL will be displayed nicely.
+ */
+ callback_arg.tablename = NULL; /* not known yet */
+ callback_arg.cmd = cmd;
+ sqlerrcontext.callback = import_error_callback;
+ sqlerrcontext.arg = (void *) &callback_arg;
+ sqlerrcontext.previous = error_context_stack;
+ error_context_stack = &sqlerrcontext;
+
+ /*
+ * Parse the SQL string into a list of raw parse trees.
+ */
+ raw_parsetree_list = pg_parse_query(cmd);
+
+ /*
+ * Process each parse tree (we allow the FDW to put more than one
+ * command per string, though this isn't really advised).
+ */
+ foreach(lc2, raw_parsetree_list)
+ {
+ RawStmt *rs = lfirst_node(RawStmt, lc2);
+ CreateForeignTableStmt *cstmt = (CreateForeignTableStmt *) rs->stmt;
+ PlannedStmt *pstmt;
+
+ /*
+ * Because we only allow CreateForeignTableStmt, we can skip parse
+ * analysis, rewrite, and planning steps here.
+ */
+ if (!IsA(cstmt, CreateForeignTableStmt))
+ elog(ERROR,
+ "foreign-data wrapper \"%s\" returned incorrect statement type %d",
+ fdw->fdwname, (int) nodeTag(cstmt));
+
+ /* Ignore commands for tables excluded by filter options */
+ if (!IsImportableForeignTable(cstmt->base.relation->relname, stmt))
+ continue;
+
+ /* Enable reporting of current table's name on error */
+ callback_arg.tablename = cstmt->base.relation->relname;
+
+ /* Ensure creation schema is the one given in IMPORT statement */
+ cstmt->base.relation->schemaname = pstrdup(stmt->local_schema);
+
+ /* No planning needed, just make a wrapper PlannedStmt */
+ pstmt = makeNode(PlannedStmt);
+ pstmt->commandType = CMD_UTILITY;
+ pstmt->canSetTag = false;
+ pstmt->utilityStmt = (Node *) cstmt;
+ pstmt->stmt_location = rs->stmt_location;
+ pstmt->stmt_len = rs->stmt_len;
+
+ /* Execute statement */
+ ProcessUtility(pstmt,
+ cmd,
+ PROCESS_UTILITY_SUBCOMMAND, NULL, NULL,
+ None_Receiver, NULL);
+
+ /* Be sure to advance the command counter between subcommands */
+ CommandCounterIncrement();
+
+ callback_arg.tablename = NULL;
+ }
+
+ error_context_stack = sqlerrcontext.previous;
+ }
+}
+
+/*
+ * error context callback to let us supply the failing SQL statement's text
+ */
+static void
+import_error_callback(void *arg)
+{
+ import_error_callback_arg *callback_arg = (import_error_callback_arg *) arg;
+ int syntaxerrposition;
+
+ /* If it's a syntax error, convert to internal syntax error report */
+ syntaxerrposition = geterrposition();
+ if (syntaxerrposition > 0)
+ {
+ errposition(0);
+ internalerrposition(syntaxerrposition);
+ internalerrquery(callback_arg->cmd);
+ }
+
+ if (callback_arg->tablename)
+ errcontext("importing foreign table \"%s\"",
+ callback_arg->tablename);
+}