]> granicus.if.org Git - postgresql/blobdiff - src/backend/commands/foreigncmds.c
Post-PG 10 beta1 pgindent run
[postgresql] / src / backend / commands / foreigncmds.c
index abbe731b14ce00e048d05fbd44775db48d3430a2..ba85952baaef52b07463038c1d462337fb9ea983 100644 (file)
@@ -3,30 +3,35 @@
  * 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.
  *
@@ -76,7 +93,7 @@ optionListToArray(List *options)
 
 
 /*
- * 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
@@ -85,9 +102,10 @@ optionListToArray(List *options)
  * 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,
@@ -119,7 +137,7 @@ transformGenericOptions(Oid catalogId,
 
                /*
                 * 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.
                 */
@@ -162,52 +180,48 @@ transformGenericOptions(Oid catalogId,
 
        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 */
@@ -215,9 +229,58 @@ AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId)
                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));
@@ -228,48 +291,59 @@ AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId)
                                 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)
@@ -277,10 +351,15 @@ AlterForeignServerOwner(const char *name, Oid 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);
@@ -295,40 +374,189 @@ AlterForeignServerOwner(const char *name, Oid 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;
@@ -336,9 +564,16 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt)
        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())
@@ -363,8 +598,6 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt)
        /*
         * Insert tuple into pg_foreign_data_wrapper.
         */
-       rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
-
        memset(values, 0, sizeof(values));
        memset(nulls, false, sizeof(nulls));
 
@@ -372,12 +605,13 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt)
                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;
 
@@ -393,20 +627,25 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt)
 
        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;
@@ -415,25 +654,40 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt)
 
        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())
@@ -451,23 +705,41 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
                                (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")));
@@ -477,16 +749,11 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
                /*
                 * 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)
        {
@@ -513,61 +780,53 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
        }
 
        /* 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;
 }
 
 
@@ -587,7 +846,7 @@ RemoveForeignDataWrapperById(Oid fdwId)
        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);
 
@@ -598,7 +857,7 @@ RemoveForeignDataWrapperById(Oid fdwId)
 /*
  * Create a foreign server
  */
-void
+ObjectAddress
 CreateForeignServer(CreateForeignServerStmt *stmt)
 {
        Relation        rel;
@@ -613,17 +872,32 @@ CreateForeignServer(CreateForeignServerStmt *stmt)
        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
@@ -638,8 +912,6 @@ CreateForeignServer(CreateForeignServerStmt *stmt)
        /*
         * Insert tuple into pg_foreign_server.
         */
-       rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
-
        memset(values, 0, sizeof(values));
        memset(nulls, false, sizeof(nulls));
 
@@ -678,13 +950,11 @@ CreateForeignServer(CreateForeignServerStmt *stmt)
 
        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;
@@ -696,14 +966,22 @@ CreateForeignServer(CreateForeignServerStmt *stmt)
 
        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;
@@ -713,6 +991,9 @@ AlterForeignServer(AlterForeignServerStmt *stmt)
        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));
@@ -779,56 +1060,20 @@ AlterForeignServer(AlterForeignServerStmt *stmt)
        }
 
        /* 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;
 }
 
 
@@ -848,7 +1093,7 @@ RemoveForeignServerById(Oid srvId)
        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);
 
@@ -886,7 +1131,7 @@ user_mapping_ddl_aclcheck(Oid umuserid, Oid serverid, const char *servername)
 /*
  * Create user mapping
  */
-void
+ObjectAddress
 CreateUserMapping(CreateUserMappingStmt *stmt)
 {
        Relation        rel;
@@ -900,8 +1145,14 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
        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);
@@ -914,20 +1165,33 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
        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));
 
@@ -947,9 +1211,7 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
 
        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);
 
@@ -964,17 +1226,27 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
        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;
@@ -985,8 +1257,16 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
        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,
@@ -995,8 +1275,8 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
        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);
 
@@ -1043,44 +1323,52 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
        }
 
        /* 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)
@@ -1090,7 +1378,7 @@ RemoveUserMapping(DropUserMappingStmt *stmt)
                                                        stmt->servername)));
                /* IF EXISTS, just note it */
                ereport(NOTICE, (errmsg("server does not exist, skipping")));
-               return;
+               return InvalidOid;
        }
 
        umId = GetSysCacheOid2(USERMAPPINGUSERSERVER,
@@ -1102,14 +1390,14 @@ RemoveUserMapping(DropUserMappingStmt *stmt)
                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);
@@ -1121,7 +1409,9 @@ RemoveUserMapping(DropUserMappingStmt *stmt)
        object.objectId = umId;
        object.objectSubId = 0;
 
-       performDeletion(&object, DROP_CASCADE);
+       performDeletion(&object, DROP_CASCADE, 0);
+
+       return umId;
 }
 
 
@@ -1141,9 +1431,230 @@ RemoveUserMappingById(Oid 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);
+}