]> granicus.if.org Git - postgresql/commitdiff
Implement IMPORT FOREIGN SCHEMA.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 10 Jul 2014 19:01:31 +0000 (15:01 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 10 Jul 2014 19:01:43 +0000 (15:01 -0400)
This command provides an automated way to create foreign table definitions
that match remote tables, thereby reducing tedium and chances for error.
In this patch, we provide the necessary core-server infrastructure and
implement the feature fully in the postgres_fdw foreign-data wrapper.
Other wrappers will throw a "feature not supported" error until/unless
they are updated.

Ronan Dunklau and Michael Paquier, additional work by me

29 files changed:
contrib/postgres_fdw/deparse.c
contrib/postgres_fdw/expected/postgres_fdw.out
contrib/postgres_fdw/postgres_fdw.c
contrib/postgres_fdw/postgres_fdw.h
contrib/postgres_fdw/sql/postgres_fdw.sql
doc/src/sgml/ddl.sgml
doc/src/sgml/event-trigger.sgml
doc/src/sgml/fdwhandler.sgml
doc/src/sgml/postgres-fdw.sgml
doc/src/sgml/ref/allfiles.sgml
doc/src/sgml/ref/create_foreign_table.sgml
doc/src/sgml/ref/import_foreign_schema.sgml [new file with mode: 0644]
doc/src/sgml/reference.sgml
src/backend/commands/event_trigger.c
src/backend/commands/foreigncmds.c
src/backend/foreign/foreign.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/parser/gram.y
src/backend/tcop/utility.c
src/bin/psql/tab-complete.c
src/include/commands/defrem.h
src/include/foreign/fdwapi.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/parser/kwlist.h
src/test/regress/expected/foreign_data.out
src/test/regress/sql/foreign_data.sql

index 322138dd0cd6e9be5fa3617a31b77b3b454ffa95..2045774f24f074c39c7aebdfe45808968dae7087 100644 (file)
@@ -116,7 +116,6 @@ static void deparseReturningList(StringInfo buf, PlannerInfo *root,
 static void deparseColumnRef(StringInfo buf, int varno, int varattno,
                                 PlannerInfo *root);
 static void deparseRelation(StringInfo buf, Relation rel);
-static void deparseStringLiteral(StringInfo buf, const char *val);
 static void deparseExpr(Expr *expr, deparse_expr_cxt *context);
 static void deparseVar(Var *node, deparse_expr_cxt *context);
 static void deparseConst(Const *node, deparse_expr_cxt *context);
@@ -1160,7 +1159,7 @@ deparseRelation(StringInfo buf, Relation rel)
 /*
  * Append a SQL string literal representing "val" to buf.
  */
-static void
+void
 deparseStringLiteral(StringInfo buf, const char *val)
 {
        const char *valptr;
index 2e49ee317a2901af6107b5e4f8bd940c392b9fa4..7eead58cffbbdb09deb0d4a05eb6c039eb61e76a 100644 (file)
@@ -2834,3 +2834,233 @@ NOTICE:  NEW: (13,"test triggered !")
  (0,27)
 (1 row)
 
+-- ===================================================================
+-- test IMPORT FOREIGN SCHEMA
+-- ===================================================================
+CREATE SCHEMA import_source;
+CREATE TABLE import_source.t1 (c1 int, c2 varchar NOT NULL);
+CREATE TABLE import_source.t2 (c1 int default 42, c2 varchar NULL, c3 text collate "POSIX");
+CREATE TYPE typ1 AS (m1 int, m2 varchar);
+CREATE TABLE import_source.t3 (c1 timestamptz default now(), c2 typ1);
+CREATE TABLE import_source."x 4" (c1 float8, "C 2" text, c3 varchar(42));
+CREATE TABLE import_source."x 5" (c1 float8);
+ALTER TABLE import_source."x 5" DROP COLUMN c1;
+CREATE SCHEMA import_dest1;
+IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest1;
+\det+ import_dest1
+                                     List of foreign tables
+    Schema    | Table |  Server  |                   FDW Options                   | Description 
+--------------+-------+----------+-------------------------------------------------+-------------
+ import_dest1 | t1    | loopback | (schema_name 'import_source', table_name 't1')  | 
+ import_dest1 | t2    | loopback | (schema_name 'import_source', table_name 't2')  | 
+ import_dest1 | t3    | loopback | (schema_name 'import_source', table_name 't3')  | 
+ import_dest1 | x 4   | loopback | (schema_name 'import_source', table_name 'x 4') | 
+ import_dest1 | x 5   | loopback | (schema_name 'import_source', table_name 'x 5') | 
+(5 rows)
+
+\d import_dest1.*
+               Foreign table "import_dest1.t1"
+ Column |       Type        | Modifiers |    FDW Options     
+--------+-------------------+-----------+--------------------
+ c1     | integer           |           | (column_name 'c1')
+ c2     | character varying | not null  | (column_name 'c2')
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 't1')
+
+                 Foreign table "import_dest1.t2"
+ Column |       Type        |   Modifiers   |    FDW Options     
+--------+-------------------+---------------+--------------------
+ c1     | integer           |               | (column_name 'c1')
+ c2     | character varying |               | (column_name 'c2')
+ c3     | text              | collate POSIX | (column_name 'c3')
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 't2')
+
+                  Foreign table "import_dest1.t3"
+ Column |           Type           | Modifiers |    FDW Options     
+--------+--------------------------+-----------+--------------------
+ c1     | timestamp with time zone |           | (column_name 'c1')
+ c2     | typ1                     |           | (column_name 'c2')
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 't3')
+
+                 Foreign table "import_dest1.x 4"
+ Column |         Type          | Modifiers |     FDW Options     
+--------+-----------------------+-----------+---------------------
+ c1     | double precision      |           | (column_name 'c1')
+ C 2    | text                  |           | (column_name 'C 2')
+ c3     | character varying(42) |           | (column_name 'c3')
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 'x 4')
+
+    Foreign table "import_dest1.x 5"
+ Column | Type | Modifiers | FDW Options 
+--------+------+-----------+-------------
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 'x 5')
+
+-- Options
+CREATE SCHEMA import_dest2;
+IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest2
+  OPTIONS (import_default 'true');
+\det+ import_dest2
+                                     List of foreign tables
+    Schema    | Table |  Server  |                   FDW Options                   | Description 
+--------------+-------+----------+-------------------------------------------------+-------------
+ import_dest2 | t1    | loopback | (schema_name 'import_source', table_name 't1')  | 
+ import_dest2 | t2    | loopback | (schema_name 'import_source', table_name 't2')  | 
+ import_dest2 | t3    | loopback | (schema_name 'import_source', table_name 't3')  | 
+ import_dest2 | x 4   | loopback | (schema_name 'import_source', table_name 'x 4') | 
+ import_dest2 | x 5   | loopback | (schema_name 'import_source', table_name 'x 5') | 
+(5 rows)
+
+\d import_dest2.*
+               Foreign table "import_dest2.t1"
+ Column |       Type        | Modifiers |    FDW Options     
+--------+-------------------+-----------+--------------------
+ c1     | integer           |           | (column_name 'c1')
+ c2     | character varying | not null  | (column_name 'c2')
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 't1')
+
+                 Foreign table "import_dest2.t2"
+ Column |       Type        |   Modifiers   |    FDW Options     
+--------+-------------------+---------------+--------------------
+ c1     | integer           | default 42    | (column_name 'c1')
+ c2     | character varying |               | (column_name 'c2')
+ c3     | text              | collate POSIX | (column_name 'c3')
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 't2')
+
+                    Foreign table "import_dest2.t3"
+ Column |           Type           |   Modifiers   |    FDW Options     
+--------+--------------------------+---------------+--------------------
+ c1     | timestamp with time zone | default now() | (column_name 'c1')
+ c2     | typ1                     |               | (column_name 'c2')
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 't3')
+
+                 Foreign table "import_dest2.x 4"
+ Column |         Type          | Modifiers |     FDW Options     
+--------+-----------------------+-----------+---------------------
+ c1     | double precision      |           | (column_name 'c1')
+ C 2    | text                  |           | (column_name 'C 2')
+ c3     | character varying(42) |           | (column_name 'c3')
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 'x 4')
+
+    Foreign table "import_dest2.x 5"
+ Column | Type | Modifiers | FDW Options 
+--------+------+-----------+-------------
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 'x 5')
+
+CREATE SCHEMA import_dest3;
+IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest3
+  OPTIONS (import_collate 'false', import_not_null 'false');
+\det+ import_dest3
+                                     List of foreign tables
+    Schema    | Table |  Server  |                   FDW Options                   | Description 
+--------------+-------+----------+-------------------------------------------------+-------------
+ import_dest3 | t1    | loopback | (schema_name 'import_source', table_name 't1')  | 
+ import_dest3 | t2    | loopback | (schema_name 'import_source', table_name 't2')  | 
+ import_dest3 | t3    | loopback | (schema_name 'import_source', table_name 't3')  | 
+ import_dest3 | x 4   | loopback | (schema_name 'import_source', table_name 'x 4') | 
+ import_dest3 | x 5   | loopback | (schema_name 'import_source', table_name 'x 5') | 
+(5 rows)
+
+\d import_dest3.*
+               Foreign table "import_dest3.t1"
+ Column |       Type        | Modifiers |    FDW Options     
+--------+-------------------+-----------+--------------------
+ c1     | integer           |           | (column_name 'c1')
+ c2     | character varying |           | (column_name 'c2')
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 't1')
+
+               Foreign table "import_dest3.t2"
+ Column |       Type        | Modifiers |    FDW Options     
+--------+-------------------+-----------+--------------------
+ c1     | integer           |           | (column_name 'c1')
+ c2     | character varying |           | (column_name 'c2')
+ c3     | text              |           | (column_name 'c3')
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 't2')
+
+                  Foreign table "import_dest3.t3"
+ Column |           Type           | Modifiers |    FDW Options     
+--------+--------------------------+-----------+--------------------
+ c1     | timestamp with time zone |           | (column_name 'c1')
+ c2     | typ1                     |           | (column_name 'c2')
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 't3')
+
+                 Foreign table "import_dest3.x 4"
+ Column |         Type          | Modifiers |     FDW Options     
+--------+-----------------------+-----------+---------------------
+ c1     | double precision      |           | (column_name 'c1')
+ C 2    | text                  |           | (column_name 'C 2')
+ c3     | character varying(42) |           | (column_name 'c3')
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 'x 4')
+
+    Foreign table "import_dest3.x 5"
+ Column | Type | Modifiers | FDW Options 
+--------+------+-----------+-------------
+Server: loopback
+FDW Options: (schema_name 'import_source', table_name 'x 5')
+
+-- Check LIMIT TO and EXCEPT
+CREATE SCHEMA import_dest4;
+IMPORT FOREIGN SCHEMA import_source LIMIT TO (t1, nonesuch)
+  FROM SERVER loopback INTO import_dest4;
+\det+ import_dest4
+                                     List of foreign tables
+    Schema    | Table |  Server  |                  FDW Options                   | Description 
+--------------+-------+----------+------------------------------------------------+-------------
+ import_dest4 | t1    | loopback | (schema_name 'import_source', table_name 't1') | 
+(1 row)
+
+IMPORT FOREIGN SCHEMA import_source EXCEPT (t1, "x 4", nonesuch)
+  FROM SERVER loopback INTO import_dest4;
+\det+ import_dest4
+                                     List of foreign tables
+    Schema    | Table |  Server  |                   FDW Options                   | Description 
+--------------+-------+----------+-------------------------------------------------+-------------
+ import_dest4 | t1    | loopback | (schema_name 'import_source', table_name 't1')  | 
+ import_dest4 | t2    | loopback | (schema_name 'import_source', table_name 't2')  | 
+ import_dest4 | t3    | loopback | (schema_name 'import_source', table_name 't3')  | 
+ import_dest4 | x 5   | loopback | (schema_name 'import_source', table_name 'x 5') | 
+(4 rows)
+
+-- Assorted error cases
+IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest4;
+ERROR:  relation "t1" already exists
+CONTEXT:  importing foreign table "t1"
+IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO import_dest4;
+ERROR:  schema "nonesuch" is not present on foreign server "loopback"
+IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO notthere;
+ERROR:  schema "notthere" does not exist
+IMPORT FOREIGN SCHEMA nonesuch FROM SERVER nowhere INTO notthere;
+ERROR:  server "nowhere" does not exist
+-- Check case of a type present only on the remote server.
+-- We can fake this by dropping the type locally in our transaction.
+CREATE TYPE "Colors" AS ENUM ('red', 'green', 'blue');
+CREATE TABLE import_source.t5 (c1 int, c2 text collate "C", "Col" "Colors");
+CREATE SCHEMA import_dest5;
+BEGIN;
+DROP TYPE "Colors" CASCADE;
+NOTICE:  drop cascades to table import_source.t5 column Col
+IMPORT FOREIGN SCHEMA import_source LIMIT TO (t5)
+  FROM SERVER loopback INTO import_dest5;  -- ERROR
+ERROR:  type "public.Colors" does not exist
+LINE 4:   "Col" public."Colors" OPTIONS (column_name 'Col')
+                ^
+QUERY:  CREATE FOREIGN TABLE t5 (
+  c1 integer OPTIONS (column_name 'c1'),
+  c2 text OPTIONS (column_name 'c2') COLLATE pg_catalog."C",
+  "Col" public."Colors" OPTIONS (column_name 'Col')
+) SERVER loopback
+OPTIONS (schema_name 'import_source', table_name 't5');
+CONTEXT:  importing foreign table "t5"
+ROLLBACK;
index 56374905f5b26ed407699795ccd56619527d379a..19debfb5c9ef1d8c9d2b22d6b93c2ff91e74699e 100644 (file)
@@ -285,6 +285,8 @@ static void postgresExplainForeignModify(ModifyTableState *mtstate,
 static bool postgresAnalyzeForeignTable(Relation relation,
                                                        AcquireSampleRowsFunc *func,
                                                        BlockNumber *totalpages);
+static List *postgresImportForeignSchema(ImportForeignSchemaStmt *stmt,
+                                                       Oid serverOid);
 
 /*
  * Helper functions
@@ -362,6 +364,9 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
        /* Support functions for ANALYZE */
        routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
 
+       /* Support functions for IMPORT FOREIGN SCHEMA */
+       routine->ImportForeignSchema = postgresImportForeignSchema;
+
        PG_RETURN_POINTER(routine);
 }
 
@@ -2563,6 +2568,270 @@ analyze_row_processor(PGresult *res, int row, PgFdwAnalyzeState *astate)
        }
 }
 
+/*
+ * Import a foreign schema
+ */
+static List *
+postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
+{
+       List       *commands = NIL;
+       bool            import_collate = true;
+       bool            import_default = false;
+       bool            import_not_null = true;
+       ForeignServer *server;
+       UserMapping *mapping;
+       PGconn     *conn;
+       StringInfoData buf;
+       PGresult   *volatile res = NULL;
+       int                     numrows,
+                               i;
+       ListCell   *lc;
+
+       /* Parse statement options */
+       foreach(lc, stmt->options)
+       {
+               DefElem    *def = (DefElem *) lfirst(lc);
+
+               if (strcmp(def->defname, "import_collate") == 0)
+                       import_collate = defGetBoolean(def);
+               else if (strcmp(def->defname, "import_default") == 0)
+                       import_default = defGetBoolean(def);
+               else if (strcmp(def->defname, "import_not_null") == 0)
+                       import_not_null = defGetBoolean(def);
+               else
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
+                                        errmsg("invalid option \"%s\"", def->defname)));
+       }
+
+       /*
+        * Get connection to the foreign server.  Connection manager will
+        * establish new connection if necessary.
+        */
+       server = GetForeignServer(serverOid);
+       mapping = GetUserMapping(GetUserId(), server->serverid);
+       conn = GetConnection(server, mapping, false);
+
+       /* Don't attempt to import collation if remote server hasn't got it */
+       if (PQserverVersion(conn) < 90100)
+               import_collate = false;
+
+       /* Create workspace for strings */
+       initStringInfo(&buf);
+
+       /* In what follows, do not risk leaking any PGresults. */
+       PG_TRY();
+       {
+               /* Check that the schema really exists */
+               appendStringInfoString(&buf, "SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname = ");
+               deparseStringLiteral(&buf, stmt->remote_schema);
+
+               res = PQexec(conn, buf.data);
+               if (PQresultStatus(res) != PGRES_TUPLES_OK)
+                       pgfdw_report_error(ERROR, res, conn, false, buf.data);
+
+               if (PQntuples(res) != 1)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FDW_SCHEMA_NOT_FOUND),
+                         errmsg("schema \"%s\" is not present on foreign server \"%s\"",
+                                        stmt->remote_schema, server->servername)));
+
+               PQclear(res);
+               res = NULL;
+               resetStringInfo(&buf);
+
+               /*
+                * Fetch all table data from this schema, possibly restricted by
+                * EXCEPT or LIMIT TO.
+                *
+                * Note: because we run the connection with search_path restricted to
+                * pg_catalog, the format_type() and pg_get_expr() outputs will always
+                * include a schema name for types/functions in other schemas, which
+                * is what we want.
+                */
+               if (import_collate)
+                       appendStringInfoString(&buf,
+                                                                  "SELECT relname, "
+                                                                  "  attname, "
+                                                                  "  format_type(atttypid, atttypmod), "
+                                                                  "  attnotnull, "
+                                                                  "  pg_get_expr(adbin, adrelid), "
+                                                                  "  collname, "
+                                                                  "  collnsp.nspname "
+                                                                  "FROM pg_class c "
+                                                                  "  JOIN pg_namespace n ON "
+                                                                  "    relnamespace = n.oid "
+                                                                  "  LEFT JOIN pg_attribute a ON "
+                                                                  "    attrelid = c.oid AND attnum > 0 "
+                                                                  "      AND NOT attisdropped "
+                                                                  "  LEFT JOIN pg_attrdef ad ON "
+                                                                  "    adrelid = c.oid AND adnum = attnum "
+                                                                  "  LEFT JOIN pg_collation coll ON "
+                                                                  "    coll.oid = attcollation "
+                                                                  "  LEFT JOIN pg_namespace collnsp ON "
+                                                                  "    collnsp.oid = collnamespace ");
+               else
+                       appendStringInfoString(&buf,
+                                                                  "SELECT relname, "
+                                                                  "  attname, "
+                                                                  "  format_type(atttypid, atttypmod), "
+                                                                  "  attnotnull, "
+                                                                  "  pg_get_expr(adbin, adrelid), "
+                                                                  "  NULL, NULL "
+                                                                  "FROM pg_class c "
+                                                                  "  JOIN pg_namespace n ON "
+                                                                  "    relnamespace = n.oid "
+                                                                  "  LEFT JOIN pg_attribute a ON "
+                                                                  "    attrelid = c.oid AND attnum > 0 "
+                                                                  "      AND NOT attisdropped "
+                                                                  "  LEFT JOIN pg_attrdef ad ON "
+                                                                  "    adrelid = c.oid AND adnum = attnum ");
+
+               appendStringInfoString(&buf,
+                                                          "WHERE c.relkind IN ('r', 'v', 'f', 'm') "
+                                                          "  AND n.nspname = ");
+               deparseStringLiteral(&buf, stmt->remote_schema);
+
+               /* Apply restrictions for LIMIT TO and EXCEPT */
+               if (stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO ||
+                       stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT)
+               {
+                       bool            first_item = true;
+
+                       appendStringInfoString(&buf, " AND c.relname ");
+                       if (stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT)
+                               appendStringInfoString(&buf, "NOT ");
+                       appendStringInfoString(&buf, "IN (");
+
+                       /* Append list of table names within IN clause */
+                       foreach(lc, stmt->table_list)
+                       {
+                               RangeVar   *rv = (RangeVar *) lfirst(lc);
+
+                               if (first_item)
+                                       first_item = false;
+                               else
+                                       appendStringInfoString(&buf, ", ");
+                               deparseStringLiteral(&buf, rv->relname);
+                       }
+                       appendStringInfoString(&buf, ")");
+               }
+
+               /* Append ORDER BY at the end of query to ensure output ordering */
+               appendStringInfo(&buf, " ORDER BY c.relname, a.attnum");
+
+               /* Fetch the data */
+               res = PQexec(conn, buf.data);
+               if (PQresultStatus(res) != PGRES_TUPLES_OK)
+                       pgfdw_report_error(ERROR, res, conn, false, buf.data);
+
+               /* Process results */
+               numrows = PQntuples(res);
+               /* note: incrementation of i happens in inner loop's while() test */
+               for (i = 0; i < numrows;)
+               {
+                       char       *tablename = PQgetvalue(res, i, 0);
+                       bool            first_item = true;
+
+                       resetStringInfo(&buf);
+                       appendStringInfo(&buf, "CREATE FOREIGN TABLE %s (\n",
+                                                        quote_identifier(tablename));
+
+                       /* Scan all rows for this table */
+                       do
+                       {
+                               char       *attname;
+                               char       *typename;
+                               char       *attnotnull;
+                               char       *attdefault;
+                               char       *collname;
+                               char       *collnamespace;
+
+                               /* If table has no columns, we'll see nulls here */
+                               if (PQgetisnull(res, i, 1))
+                                       continue;
+
+                               attname = PQgetvalue(res, i, 1);
+                               typename = PQgetvalue(res, i, 2);
+                               attnotnull = PQgetvalue(res, i, 3);
+                               attdefault = PQgetisnull(res, i, 4) ? (char *) NULL :
+                                       PQgetvalue(res, i, 4);
+                               collname = PQgetisnull(res, i, 5) ? (char *) NULL :
+                                       PQgetvalue(res, i, 5);
+                               collnamespace = PQgetisnull(res, i, 6) ? (char *) NULL :
+                                       PQgetvalue(res, i, 6);
+
+                               if (first_item)
+                                       first_item = false;
+                               else
+                                       appendStringInfoString(&buf, ",\n");
+
+                               /* Print column name and type */
+                               appendStringInfo(&buf, "  %s %s",
+                                                                quote_identifier(attname),
+                                                                typename);
+
+                               /*
+                                * Add column_name option so that renaming the foreign table's
+                                * column doesn't break the association to the underlying
+                                * column.
+                                */
+                               appendStringInfoString(&buf, " OPTIONS (column_name ");
+                               deparseStringLiteral(&buf, attname);
+                               appendStringInfoString(&buf, ")");
+
+                               /* Add COLLATE if needed */
+                               if (import_collate && collname != NULL && collnamespace != NULL)
+                                       appendStringInfo(&buf, " COLLATE %s.%s",
+                                                                        quote_identifier(collnamespace),
+                                                                        quote_identifier(collname));
+
+                               /* Add DEFAULT if needed */
+                               if (import_default && attdefault != NULL)
+                                       appendStringInfo(&buf, " DEFAULT %s", attdefault);
+
+                               /* Add NOT NULL if needed */
+                               if (import_not_null && attnotnull[0] == 't')
+                                       appendStringInfoString(&buf, " NOT NULL");
+                       }
+                       while (++i < numrows &&
+                                  strcmp(PQgetvalue(res, i, 0), tablename) == 0);
+
+                       /*
+                        * Add server name and table-level options.  We specify remote
+                        * schema and table name as options (the latter to ensure that
+                        * renaming the foreign table doesn't break the association).
+                        */
+                       appendStringInfo(&buf, "\n) SERVER %s\nOPTIONS (",
+                                                        quote_identifier(server->servername));
+
+                       appendStringInfoString(&buf, "schema_name ");
+                       deparseStringLiteral(&buf, stmt->remote_schema);
+                       appendStringInfoString(&buf, ", table_name ");
+                       deparseStringLiteral(&buf, tablename);
+
+                       appendStringInfoString(&buf, ");");
+
+                       commands = lappend(commands, pstrdup(buf.data));
+               }
+
+               /* Clean up */
+               PQclear(res);
+               res = NULL;
+       }
+       PG_CATCH();
+       {
+               if (res)
+                       PQclear(res);
+               PG_RE_THROW();
+       }
+       PG_END_TRY();
+
+       ReleaseConnection(conn);
+
+       return commands;
+}
+
 /*
  * Create a tuple from the specified row of the PGresult.
  *
index 8aa8f1a1b58fcb41f21950e32eff01a76af3f59b..94eadae891650cde2747fbf6a2ff85d41e056484 100644 (file)
@@ -73,5 +73,6 @@ extern void deparseDeleteSql(StringInfo buf, PlannerInfo *root,
 extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel);
 extern void deparseAnalyzeSql(StringInfo buf, Relation rel,
                                  List **retrieved_attrs);
+extern void deparseStringLiteral(StringInfo buf, const char *val);
 
 #endif   /* POSTGRES_FDW_H */
index 6187839453c0aa6cf30e561773e1e84d3b4d4e29..9f54359be5842f025f5e429905503219177a97b2 100644 (file)
@@ -609,3 +609,60 @@ UPDATE rem1 SET f2 = 'testo';
 
 -- Test returning a system attribute
 INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
+
+-- ===================================================================
+-- test IMPORT FOREIGN SCHEMA
+-- ===================================================================
+
+CREATE SCHEMA import_source;
+CREATE TABLE import_source.t1 (c1 int, c2 varchar NOT NULL);
+CREATE TABLE import_source.t2 (c1 int default 42, c2 varchar NULL, c3 text collate "POSIX");
+CREATE TYPE typ1 AS (m1 int, m2 varchar);
+CREATE TABLE import_source.t3 (c1 timestamptz default now(), c2 typ1);
+CREATE TABLE import_source."x 4" (c1 float8, "C 2" text, c3 varchar(42));
+CREATE TABLE import_source."x 5" (c1 float8);
+ALTER TABLE import_source."x 5" DROP COLUMN c1;
+
+CREATE SCHEMA import_dest1;
+IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest1;
+\det+ import_dest1
+\d import_dest1.*
+
+-- Options
+CREATE SCHEMA import_dest2;
+IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest2
+  OPTIONS (import_default 'true');
+\det+ import_dest2
+\d import_dest2.*
+CREATE SCHEMA import_dest3;
+IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest3
+  OPTIONS (import_collate 'false', import_not_null 'false');
+\det+ import_dest3
+\d import_dest3.*
+
+-- Check LIMIT TO and EXCEPT
+CREATE SCHEMA import_dest4;
+IMPORT FOREIGN SCHEMA import_source LIMIT TO (t1, nonesuch)
+  FROM SERVER loopback INTO import_dest4;
+\det+ import_dest4
+IMPORT FOREIGN SCHEMA import_source EXCEPT (t1, "x 4", nonesuch)
+  FROM SERVER loopback INTO import_dest4;
+\det+ import_dest4
+
+-- Assorted error cases
+IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest4;
+IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO import_dest4;
+IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO notthere;
+IMPORT FOREIGN SCHEMA nonesuch FROM SERVER nowhere INTO notthere;
+
+-- Check case of a type present only on the remote server.
+-- We can fake this by dropping the type locally in our transaction.
+CREATE TYPE "Colors" AS ENUM ('red', 'green', 'blue');
+CREATE TABLE import_source.t5 (c1 int, c2 text collate "C", "Col" "Colors");
+
+CREATE SCHEMA import_dest5;
+BEGIN;
+DROP TYPE "Colors" CASCADE;
+IMPORT FOREIGN SCHEMA import_source LIMIT TO (t5)
+  FROM SERVER loopback INTO import_dest5;  -- ERROR
+ROLLBACK;
index 8ace8bd3a2537849cdaac52b620baa8ed97a9095..3b7fff4846c85abc731e670c9f24a9ab9e3ee883 100644 (file)
@@ -3069,8 +3069,9 @@ ANALYZE measurement;
     For additional information, see
     <xref linkend="sql-createforeigndatawrapper">,
     <xref linkend="sql-createserver">,
-    <xref linkend="sql-createusermapping">, and
-    <xref linkend="sql-createforeigntable">.
+    <xref linkend="sql-createusermapping">,
+    <xref linkend="sql-createforeigntable">, and
+    <xref linkend="sql-importforeignschema">.
    </para>
  </sect1>
 
index e5b9e6618530471bc956b7f47682198046ee72ba..3db8ef1a132d1df8664be27ed9691b994d03f952 100644 (file)
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
        </row>
+       <row>
+        <entry align="left"><literal>IMPORT FOREIGN SCHEMA</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+       </row>
        <row>
         <entry align="left"><literal>SELECT INTO</literal></entry>
         <entry align="center"><literal>X</literal></entry>
index 6b5c8b7e97b3a6f2091e20892af67ac41fdeb088..5fd8d6fbbe9fbdb2da055c37d67ba8f99cd34e61 100644 (file)
@@ -696,6 +696,66 @@ AcquireSampleRowsFunc (Relation relation, int elevel,
 
    </sect2>
 
+   <sect2 id="fdw-callbacks-import">
+    <title>FDW Routines For <command>IMPORT FOREIGN SCHEMA</></title>
+
+    <para>
+<programlisting>
+List *
+ImportForeignSchema (ImportForeignSchemaStmt *stmt, Oid serverOid);
+</programlisting>
+
+     Obtain a list of foreign table creation commands.  This function is
+     called when executing <xref linkend="sql-importforeignschema">, and is
+     passed the parse tree for that statement, as well as the OID of the
+     foreign server to use.  It should return a list of C strings, each of
+     which must contain a <xref linkend="sql-createforeigntable"> command.
+     These strings will be parsed and executed by the core server.
+    </para>
+
+    <para>
+     Within the <structname>ImportForeignSchemaStmt</> struct,
+     <structfield>remote_schema</> is the name of the remote schema from
+     which tables are to be imported.
+     <structfield>list_type</> identifies how to filter table names:
+     <literal>FDW_IMPORT_SCHEMA_ALL</> means that all tables in the remote
+     schema should be imported (in this case <structfield>table_list</> is
+     empty), <literal>FDW_IMPORT_SCHEMA_LIMIT_TO</> means to include only
+     tables listed in <structfield>table_list</>,
+     and <literal>FDW_IMPORT_SCHEMA_EXCEPT</> means to exclude the tables
+     listed in <structfield>table_list</>.
+     <structfield>options</> is a list of options used for the import process.
+     The meanings of the options are up to the FDW.
+     For example, an FDW could use an option to define whether the
+     <literal>NOT NULL</> attributes of columns should be imported.
+     These options need not have anything to do with those supported by the
+     FDW as database object options.
+    </para>
+
+    <para>
+     The FDW may ignore the <structfield>local_schema</> field of
+     the <structname>ImportForeignSchemaStmt</>, because the core server
+     will automatically insert that name into the parsed <command>CREATE
+     FOREIGN TABLE</> commands.
+    </para>
+
+    <para>
+     The FDW does not have to concern itself with implementing the filtering
+     specified by <structfield>list_type</> and <structfield>table_list</>,
+     either, as the core server will automatically skip any returned commands
+     for tables excluded according to those options.  However, it's often
+     useful to avoid the work of creating commands for excluded tables in the
+     first place.  The function <function>IsImportableForeignTable()</> may be
+     useful to test whether a given foreign-table name will pass the filter.
+    </para>
+
+    <para>
+     If the FDW does not support importing table definitions, the
+     <function>ImportForeignSchema</> pointer can be set to <literal>NULL</>.
+    </para>
+
+   </sect2>
+
    </sect1>
 
    <sect1 id="fdw-helpers">
index e6f6e205815cae003f3554d1b8df0f5885d28deb..43adb61455d919eae10c3debae954e4111b95296 100644 (file)
@@ -49,7 +49,8 @@
    </listitem>
    <listitem>
     <para>
-     Create a foreign table, using <xref linkend="sql-createforeigntable">,
+     Create a foreign table, using <xref linkend="sql-createforeigntable">
+     or <xref linkend="sql-importforeignschema">,
      for each remote table you want to access.  The columns of the foreign
      table must match the referenced remote table.  You can, however, use
      table and/or column names different from the remote table's, if you
      <listitem>
       <para>
        <literal>user</literal> and <literal>password</literal> (specify these
-       for a user mapping, instead)
+       in a user mapping, instead)
       </para>
      </listitem>
      <listitem>
 
    </variablelist>
   </sect3>
+
+  <sect3>
+   <title>Importing Options</title>
+
+   <para>
+    <filename>postgres_fdw</> is able to import foreign table definitions
+    using <xref linkend="sql-importforeignschema">.  This command creates
+    foreign table definitions on the local server that match tables or
+    views present on the remote server.  If the remote tables to be imported
+    have columns of user-defined data types, the local server must have types
+    of the same names.
+   </para>
+
+   <para>
+    Importing behavior can be customized with the following options
+    (given in the <command>IMPORT FOREIGN SCHEMA</> command):
+   </para>
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>import_collate</literal></term>
+     <listitem>
+      <para>
+       This option controls whether column <literal>COLLATE</> options
+       are included in the definitions of foreign tables imported
+       from a foreign server. The default is <literal>true</>.  You might
+       need to turn this off if the remote server has a different set of
+       collation names than the local server does, which is likely to be the
+       case if it's running on a different operating system.
+      </para>
+     </listitem>
+    </varlistentry>
+    <varlistentry>
+     <term><literal>import_default</literal></term>
+     <listitem>
+      <para>
+       This option controls whether column <literal>DEFAULT</> expressions
+       are included in the definitions of foreign tables imported
+       from a foreign server. The default is <literal>false</>.  If you
+       enable this option, be wary of defaults that might get computed
+       differently on the local server than they would be on the remote
+       server; <function>nextval()</> is a common source of problems.
+       The <command>IMPORT</> will fail altogether if an imported default
+       expression uses a function or operator that does not exist locally.
+      </para>
+     </listitem>
+    </varlistentry>
+    <varlistentry>
+     <term><literal>import_not_null</literal></term>
+     <listitem>
+      <para>
+       This option controls whether column <literal>NOT NULL</>
+       constraints are included in the definitions of foreign tables imported
+       from a foreign server. The default is <literal>true</>.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+
+   <para>
+    Note that constraints other than <literal>NOT NULL</> will never be
+    imported from the remote tables, since <productname>PostgreSQL</>
+    does not support any other type of constraint on a foreign table.
+    Checking other types of constraints is always left to the remote server.
+   </para>
+  </sect3>
  </sect2>
 
  <sect2>
@@ -422,7 +489,7 @@ CREATE USER MAPPING FOR local_user
 
 <programlisting>
 CREATE FOREIGN TABLE foreign_table (
-        id serial NOT NULL,
+        id integer NOT NULL,
         data text
 )
         SERVER foreign_server
@@ -434,6 +501,8 @@ CREATE FOREIGN TABLE foreign_table (
    Column names must match as well, unless you attach <literal>column_name</>
    options to the individual columns to show how they are named in the remote
    table.
+   In many cases, use of <xref linkend="sql-importforeignschema"> is
+   preferable to constructing foreign table definitions manually.
   </para>
  </sect2>
 
index 1b0962c253d8f7be11a87c1c8324c28d84e100c6..b685e16a0fa0a524cf45aebbf428619241c579b3 100644 (file)
@@ -131,6 +131,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY explain            SYSTEM "explain.sgml">
 <!ENTITY fetch              SYSTEM "fetch.sgml">
 <!ENTITY grant              SYSTEM "grant.sgml">
+<!ENTITY importForeignSchema SYSTEM "import_foreign_schema.sgml">
 <!ENTITY insert             SYSTEM "insert.sgml">
 <!ENTITY listen             SYSTEM "listen.sgml">
 <!ENTITY load               SYSTEM "load.sgml">
index 4a8cf3889c29bddcca3aa6d6bad3a5aff2f0b8e1..46a20eff14f8f25220a50fa23f1e311fe40f6a65 100644 (file)
@@ -231,6 +231,7 @@ SERVER film_server;
    <member><xref linkend="sql-dropforeigntable"></member>
    <member><xref linkend="sql-createtable"></member>
    <member><xref linkend="sql-createserver"></member>
+   <member><xref linkend="sql-importforeignschema"></member>
   </simplelist>
  </refsect1>
 </refentry>
diff --git a/doc/src/sgml/ref/import_foreign_schema.sgml b/doc/src/sgml/ref/import_foreign_schema.sgml
new file mode 100644 (file)
index 0000000..bdcc265
--- /dev/null
@@ -0,0 +1,168 @@
+<!--
+doc/src/sgml/ref/import_foreign_schema.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-IMPORTFOREIGNSCHEMA">
+ <indexterm zone="sql-importforeignschema">
+  <primary>IMPORT FOREIGN SCHEMA</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>IMPORT FOREIGN SCHEMA</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>IMPORT FOREIGN SCHEMA</refname>
+  <refpurpose>import table definitions from a foreign server</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+IMPORT FOREIGN SCHEMA <replaceable class="PARAMETER">remote_schema</replaceable>
+[ { LIMIT TO | EXCEPT } ( <replaceable class="PARAMETER">table_name</replaceable> [, ...] ) ]
+FROM SERVER <replaceable class="PARAMETER">server_name</replaceable>
+INTO <replaceable class="PARAMETER">local_schema</replaceable>
+[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id="SQL-IMPORTFOREIGNSCHEMA-description">
+  <title>Description</title>
+
+  <para>
+   <command>IMPORT FOREIGN SCHEMA</command> creates foreign tables that
+   represent tables existing on a foreign server.  The new foreign tables
+   will be owned by the user issuing the command and are created with
+   the correct column definitions and options to match the remote tables.
+  </para>
+
+  <para>
+   By default, all tables and views existing in a particular schema on the
+   foreign server are imported.  Optionally, the list of tables can be limited
+   to a specified subset, or specific tables can be excluded.  The new foreign
+   tables are all created in the target schema, which must already exist.
+  </para>
+
+  <para>
+   To use <command>IMPORT FOREIGN SCHEMA</command>, the user must have
+   <literal>USAGE</literal> privilege on the foreign server, as well as
+   <literal>CREATE</literal> privilege on the target schema.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+
+   <varlistentry>
+    <term><replaceable class="PARAMETER">remote_schema</replaceable></term>
+    <listitem>
+     <para>
+      The remote schema to import from. The specific meaning of a remote schema
+      depends on the foreign data wrapper in use.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>LIMIT TO ( <replaceable class="PARAMETER">table_name</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      Import only foreign tables matching one of the given table names.
+      Other tables existing in the foreign schema will be ignored.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>EXCEPT ( <replaceable class="PARAMETER">table_name</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      Exclude specified foreign tables from the import.  All tables
+      existing in the foreign schema will be imported except the
+      ones listed here.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="PARAMETER">server_name</replaceable></term>
+    <listitem>
+     <para>
+      The foreign server to import from.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="PARAMETER">local_schema</replaceable></term>
+    <listitem>
+     <para>
+      The schema in which the imported foreign tables will be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ...] )</literal></term>
+    <listitem>
+     <para>
+      Options to be used during the import.
+      The allowed option names and values are specific to each foreign
+      data wrapper.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1 id="SQL-IMPORTFOREIGNSCHEMA-examples">
+  <title>Examples</title>
+
+  <para>
+   Import table definitions from a remote schema <structname>foreign_films</>
+   on server <structname>film_server</>, creating the foreign tables in
+   local schema <structname>films</>:
+
+<programlisting>
+IMPORT FOREIGN SCHEMA foreign_films
+    FROM SERVER film_server INTO films;
+</programlisting>
+   </para>
+
+  <para>
+   As above, but import only the two tables <structname>actors</> and
+   <literal>directors</> (if they exist):
+
+<programlisting>
+IMPORT FOREIGN SCHEMA foreign_films LIMIT TO (actors, directors)
+    FROM SERVER film_server INTO films;
+</programlisting>
+   </para>
+
+ </refsect1>
+
+ <refsect1 id="SQL-IMPORTFOREIGNSCHEMA-compatibility">
+  <title>Compatibility</title>
+
+  <para>
+   The <command>IMPORT FOREIGN SCHEMA</command> command conforms to the
+   <acronym>SQL</acronym> standard, except that the <literal>OPTIONS</>
+   clause is a <productname>PostgreSQL</> extension.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createforeigntable"></member>
+   <member><xref linkend="sql-createserver"></member>
+  </simplelist>
+ </refsect1>
+</refentry>
index a6575f52ac08ef6db43fc4d73c5eb61db0f2992d..6ec126381c3b5ffbe484759ed702ce145f60c009 100644 (file)
    &explain;
    &fetch;
    &grant;
+   &importForeignSchema;
    &insert;
    &listen;
    &load;
index 110fe004a464fcbbb5001d31a26b1c85e586e602..754264eb3eea0b9ebbae51d3aeae943451703ffd 100644 (file)
@@ -250,7 +250,8 @@ check_ddl_tag(const char *tag)
                pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW") == 0 ||
                pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 ||
                pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0 ||
-               pg_strcasecmp(tag, "DROP OWNED") == 0)
+               pg_strcasecmp(tag, "DROP OWNED") == 0 ||
+               pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0)
                return EVENT_TRIGGER_COMMAND_TAG_OK;
 
        /*
index 8ab9c439db25487932d793895a2b12ce8b798b44..ab4ed6c78ece0a3e6e377ce62b102a63108f946f 100644 (file)
@@ -15,8 +15,8 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
-#include "access/xact.h"
 #include "access/reloptions.h"
+#include "access/xact.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.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, pg_user_mapping, and
@@ -1427,3 +1439,133 @@ CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
 
        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)
+               {
+                       CreateForeignTableStmt *cstmt = lfirst(lc2);
+
+                       /*
+                        * 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);
+
+                       /* Execute statement */
+                       ProcessUtility((Node *) cstmt,
+                                                  cmd,
+                                                  PROCESS_UTILITY_SUBCOMMAND, 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);
+}
index 6d548b7d08b7be04a34b969a69a47ed1f79ccfe4..4f5f6ae362b565fb48f1eb1991b88c4aaf40b033 100644 (file)
@@ -399,6 +399,47 @@ GetFdwRoutineForRelation(Relation relation, bool makecopy)
 }
 
 
+/*
+ * IsImportableForeignTable - filter table names for IMPORT FOREIGN SCHEMA
+ *
+ * Returns TRUE if given table name should be imported according to the
+ * statement's import filter options.
+ */
+bool
+IsImportableForeignTable(const char *tablename,
+                                                ImportForeignSchemaStmt *stmt)
+{
+       ListCell   *lc;
+
+       switch (stmt->list_type)
+       {
+               case FDW_IMPORT_SCHEMA_ALL:
+                       return true;
+
+               case FDW_IMPORT_SCHEMA_LIMIT_TO:
+                       foreach(lc, stmt->table_list)
+                       {
+                               RangeVar   *rv = (RangeVar *) lfirst(lc);
+
+                               if (strcmp(tablename, rv->relname) == 0)
+                                       return true;
+                       }
+                       return false;
+
+               case FDW_IMPORT_SCHEMA_EXCEPT:
+                       foreach(lc, stmt->table_list)
+                       {
+                               RangeVar   *rv = (RangeVar *) lfirst(lc);
+
+                               if (strcmp(tablename, rv->relname) == 0)
+                                       return false;
+                       }
+                       return true;
+       }
+       return false;                           /* shouldn't get here */
+}
+
+
 /*
  * deflist_to_tuplestore - Helper function to convert DefElem list to
  * tuplestore usable in SRF.
index 8d3d5a7c7347a6c87ea42fe9064a4aa2c39ccd9d..30885789304f10affba9f1dedc19393a5698ceeb 100644 (file)
@@ -3567,6 +3567,21 @@ _copyCreateForeignTableStmt(const CreateForeignTableStmt *from)
        return newnode;
 }
 
+static ImportForeignSchemaStmt *
+_copyImportForeignSchemaStmt(const ImportForeignSchemaStmt *from)
+{
+       ImportForeignSchemaStmt *newnode = makeNode(ImportForeignSchemaStmt);
+
+       COPY_STRING_FIELD(server_name);
+       COPY_STRING_FIELD(remote_schema);
+       COPY_STRING_FIELD(local_schema);
+       COPY_SCALAR_FIELD(list_type);
+       COPY_NODE_FIELD(table_list);
+       COPY_NODE_FIELD(options);
+
+       return newnode;
+}
+
 static CreateTrigStmt *
 _copyCreateTrigStmt(const CreateTrigStmt *from)
 {
@@ -4477,6 +4492,9 @@ copyObject(const void *from)
                case T_CreateForeignTableStmt:
                        retval = _copyCreateForeignTableStmt(from);
                        break;
+               case T_ImportForeignSchemaStmt:
+                       retval = _copyImportForeignSchemaStmt(from);
+                       break;
                case T_CreateTrigStmt:
                        retval = _copyCreateTrigStmt(from);
                        break;
index e7b49f680cf01a1a8f6ca2a4aa4f26cc41bece72..1b07db69d732a797c26be12ae80c9d1cb58e5d82 100644 (file)
@@ -1768,6 +1768,19 @@ _equalCreateForeignTableStmt(const CreateForeignTableStmt *a, const CreateForeig
        return true;
 }
 
+static bool
+_equalImportForeignSchemaStmt(const ImportForeignSchemaStmt *a, const ImportForeignSchemaStmt *b)
+{
+       COMPARE_STRING_FIELD(server_name);
+       COMPARE_STRING_FIELD(remote_schema);
+       COMPARE_STRING_FIELD(local_schema);
+       COMPARE_SCALAR_FIELD(list_type);
+       COMPARE_NODE_FIELD(table_list);
+       COMPARE_NODE_FIELD(options);
+
+       return true;
+}
+
 static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
@@ -2943,6 +2956,9 @@ equal(const void *a, const void *b)
                case T_CreateForeignTableStmt:
                        retval = _equalCreateForeignTableStmt(a, b);
                        break;
+               case T_ImportForeignSchemaStmt:
+                       retval = _equalImportForeignSchemaStmt(a, b);
+                       break;
                case T_CreateTrigStmt:
                        retval = _equalCreateTrigStmt(a, b);
                        break;
index c182212e62f76d0ad87c89235bea66cde9a389a9..9573a9bb0e6d9347b7fdfc3ef2c92fa9919ecd1c 100644 (file)
@@ -2011,6 +2011,19 @@ _outCreateForeignTableStmt(StringInfo str, const CreateForeignTableStmt *node)
        WRITE_NODE_FIELD(options);
 }
 
+static void
+_outImportForeignSchemaStmt(StringInfo str, const ImportForeignSchemaStmt *node)
+{
+       WRITE_NODE_TYPE("IMPORTFOREIGNSCHEMASTMT");
+
+       WRITE_STRING_FIELD(server_name);
+       WRITE_STRING_FIELD(remote_schema);
+       WRITE_STRING_FIELD(local_schema);
+       WRITE_ENUM_FIELD(list_type, ImportForeignSchemaType);
+       WRITE_NODE_FIELD(table_list);
+       WRITE_NODE_FIELD(options);
+}
+
 static void
 _outIndexStmt(StringInfo str, const IndexStmt *node)
 {
@@ -3119,6 +3132,9 @@ _outNode(StringInfo str, const void *obj)
                        case T_CreateForeignTableStmt:
                                _outCreateForeignTableStmt(str, obj);
                                break;
+                       case T_ImportForeignSchemaStmt:
+                               _outImportForeignSchemaStmt(str, obj);
+                               break;
                        case T_IndexStmt:
                                _outIndexStmt(str, obj);
                                break;
index ba7d091dc793c079481a9a0fab05a110c8e98ce7..a113809ca6376214ff20a61b3b7053d6010172d3 100644 (file)
@@ -111,6 +111,13 @@ typedef struct PrivTarget
        List       *objs;
 } PrivTarget;
 
+/* Private struct for the result of import_qualification production */
+typedef struct ImportQual
+{
+       ImportForeignSchemaType type;
+       List       *table_names;
+} ImportQual;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE                     0x01
 #define CAS_DEFERRABLE                         0x02
@@ -212,6 +219,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
        ResTarget                       *target;
        struct PrivTarget       *privtarget;
        AccessPriv                      *accesspriv;
+       struct ImportQual       *importqual;
        InsertStmt                      *istmt;
        VariableSetStmt         *vsetstmt;
 }
@@ -238,8 +246,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
                DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
                DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
                DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
-               GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
-               LockStmt NotifyStmt ExplainableStmt PreparableStmt
+               GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
+               ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
                CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
                RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
                RuleActionStmt RuleActionStmtOrEmpty RuleStmt
@@ -322,6 +330,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>   defacl_privilege_target
 %type <defelt> DefACLOption
 %type <list>   DefACLOptionList
+%type <ival>   import_qualification_type
+%type <importqual> import_qualification
 
 %type <list>   stmtblock stmtmulti
                                OptTableElementList TableElementList OptInherit definition
@@ -556,7 +566,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
        HANDLER HAVING HEADER_P HOLD HOUR_P
 
-       IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P
+       IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
        INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
        INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
        INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -802,6 +812,7 @@ stmt :
                        | FetchStmt
                        | GrantStmt
                        | GrantRoleStmt
+                       | ImportForeignSchemaStmt
                        | IndexStmt
                        | InsertStmt
                        | ListenStmt
@@ -4272,6 +4283,52 @@ AlterForeignTableStmt:
                                }
                ;
 
+/*****************************************************************************
+ *
+ *             QUERY:
+ *                             IMPORT FOREIGN SCHEMA remote_schema
+ *                             [ { LIMIT TO | EXCEPT } ( table_list ) ]
+ *                             FROM SERVER server_name INTO local_schema [ OPTIONS (...) ]
+ *
+ ****************************************************************************/
+
+ImportForeignSchemaStmt:
+               IMPORT_P FOREIGN SCHEMA name import_qualification
+                 FROM SERVER name INTO name create_generic_options
+                       {
+                               ImportForeignSchemaStmt *n = makeNode(ImportForeignSchemaStmt);
+                               n->server_name = $8;
+                               n->remote_schema = $4;
+                               n->local_schema = $10;
+                               n->list_type = $5->type;
+                               n->table_list = $5->table_names;
+                               n->options = $11;
+                               $$ = (Node *) n;
+                       }
+               ;
+
+import_qualification_type:
+               LIMIT TO                                { $$ = FDW_IMPORT_SCHEMA_LIMIT_TO; }
+               | EXCEPT                                { $$ = FDW_IMPORT_SCHEMA_EXCEPT; }
+               ;
+
+import_qualification:
+               import_qualification_type '(' relation_expr_list ')'
+                       {
+                               ImportQual *n = (ImportQual *) palloc(sizeof(ImportQual));
+                               n->type = $1;
+                               n->table_names = $3;
+                               $$ = n;
+                       }
+               | /*EMPTY*/
+                       {
+                               ImportQual *n = (ImportQual *) palloc(sizeof(ImportQual));
+                               n->type = FDW_IMPORT_SCHEMA_ALL;
+                               n->table_names = NIL;
+                               $$ = n;
+                       }
+               ;
+
 /*****************************************************************************
  *
  *             QUERY:
@@ -12909,6 +12966,7 @@ unreserved_keyword:
                        | IMMEDIATE
                        | IMMUTABLE
                        | IMPLICIT_P
+                       | IMPORT_P
                        | INCLUDING
                        | INCREMENT
                        | INDEX
index 3423898c1125cf64e292f0c1939e023cf408c2b5..07e0b987212fab60de65d7be81a4290afdad9be8 100644 (file)
@@ -202,6 +202,7 @@ check_xact_readonly(Node *parsetree)
                case T_AlterTableSpaceOptionsStmt:
                case T_AlterTableSpaceMoveStmt:
                case T_CreateForeignTableStmt:
+               case T_ImportForeignSchemaStmt:
                case T_SecLabelStmt:
                        PreventCommandIfReadOnly(CreateCommandTag(parsetree));
                        break;
@@ -1196,6 +1197,10 @@ ProcessUtilitySlow(Node *parsetree,
                                RemoveUserMapping((DropUserMappingStmt *) parsetree);
                                break;
 
+                       case T_ImportForeignSchemaStmt:
+                               ImportForeignSchema((ImportForeignSchemaStmt *) parsetree);
+                               break;
+
                        case T_CompositeTypeStmt:       /* CREATE TYPE (composite) */
                                {
                                        CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
@@ -1853,6 +1858,10 @@ CreateCommandTag(Node *parsetree)
                        tag = "CREATE FOREIGN TABLE";
                        break;
 
+               case T_ImportForeignSchemaStmt:
+                       tag = "IMPORT FOREIGN SCHEMA";
+                       break;
+
                case T_DropStmt:
                        switch (((DropStmt *) parsetree)->removeType)
                        {
@@ -2518,6 +2527,7 @@ GetCommandLogLevel(Node *parsetree)
                case T_CreateUserMappingStmt:
                case T_AlterUserMappingStmt:
                case T_DropUserMappingStmt:
+               case T_ImportForeignSchemaStmt:
                        lev = LOGSTMT_DDL;
                        break;
 
index bab03572352d1e5ba9b2b78a298de95065d929c4..6c2d431842c28dcc202e74047d70f6f79372a12d 100644 (file)
@@ -876,8 +876,9 @@ psql_completion(const char *text, int start, int end)
        static const char *const sql_commands[] = {
                "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
                "COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
-               "DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
-               "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
+               "DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
+               "FETCH", "GRANT", "IMPORT", "INSERT", "LISTEN", "LOAD", "LOCK",
+               "MOVE", "NOTIFY", "PREPARE",
                "REASSIGN", "REFRESH", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
                "SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
                "TABLE", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", "VALUES", "WITH",
@@ -2947,6 +2948,13 @@ psql_completion(const char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "GROUP") == 0)
                COMPLETE_WITH_CONST("BY");
 
+/* IMPORT FOREIGN SCHEMA */
+       else if (pg_strcasecmp(prev_wd, "IMPORT") == 0)
+               COMPLETE_WITH_CONST("FOREIGN SCHEMA");
+       else if (pg_strcasecmp(prev2_wd, "IMPORT") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+               COMPLETE_WITH_CONST("SCHEMA");
+
 /* INSERT */
        /* Complete INSERT with "INTO" */
        else if (pg_strcasecmp(prev_wd, "INSERT") == 0)
index 5ec9374aad0df368af4ba39610972e3da4cb8c06..0ebdbc1c186f29a0fb29b4ebeaf3626ebe672956 100644 (file)
@@ -124,6 +124,7 @@ extern Oid  AlterUserMapping(AlterUserMappingStmt *stmt);
 extern Oid     RemoveUserMapping(DropUserMappingStmt *stmt);
 extern void RemoveUserMappingById(Oid umId);
 extern void CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid);
+extern void ImportForeignSchema(ImportForeignSchemaStmt *stmt);
 extern Datum transformGenericOptions(Oid catalogId,
                                                Datum oldOptions,
                                                List *options,
index 1b735dacc6d5559bdad20b5a51b48f8a719a3635..dc0a7fc7235142b993baef71634fdd895d742ca6 100644 (file)
@@ -100,6 +100,9 @@ typedef bool (*AnalyzeForeignTable_function) (Relation relation,
                                                                                                 AcquireSampleRowsFunc *func,
                                                                                                        BlockNumber *totalpages);
 
+typedef List *(*ImportForeignSchema_function) (ImportForeignSchemaStmt *stmt,
+                                                                                                                  Oid serverOid);
+
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
  * function.  It provides pointers to the callback functions needed by the
@@ -144,6 +147,9 @@ typedef struct FdwRoutine
 
        /* Support functions for ANALYZE */
        AnalyzeForeignTable_function AnalyzeForeignTable;
+
+       /* Support functions for IMPORT FOREIGN SCHEMA */
+       ImportForeignSchema_function ImportForeignSchema;
 } FdwRoutine;
 
 
@@ -151,5 +157,7 @@ typedef struct FdwRoutine
 extern FdwRoutine *GetFdwRoutine(Oid fdwhandler);
 extern FdwRoutine *GetFdwRoutineByRelId(Oid relid);
 extern FdwRoutine *GetFdwRoutineForRelation(Relation relation, bool makecopy);
+extern bool IsImportableForeignTable(const char *tablename,
+                                                ImportForeignSchemaStmt *stmt);
 
 #endif   /* FDWAPI_H */
index 7b0088fdb502d182825e84abaaceca88f6f6f19e..067c7685f0d2986c48f26d496cb089e881eaa085 100644 (file)
@@ -357,6 +357,7 @@ typedef enum NodeTag
        T_AlterTableSpaceMoveStmt,
        T_SecLabelStmt,
        T_CreateForeignTableStmt,
+       T_ImportForeignSchemaStmt,
        T_CreateExtensionStmt,
        T_AlterExtensionStmt,
        T_AlterExtensionContentsStmt,
index ff126ebca4718263fd292f17473ca903b463068d..8364bef79f59c85fe02ea5330ca3a8a2af682dbb 100644 (file)
@@ -1791,7 +1791,7 @@ typedef struct AlterForeignServerStmt
 } AlterForeignServerStmt;
 
 /* ----------------------
- *             Create FOREIGN TABLE Statements
+ *             Create FOREIGN TABLE Statement
  * ----------------------
  */
 
@@ -1831,6 +1831,29 @@ typedef struct DropUserMappingStmt
        bool            missing_ok;             /* ignore missing mappings */
 } DropUserMappingStmt;
 
+/* ----------------------
+ *             Import Foreign Schema Statement
+ * ----------------------
+ */
+
+typedef enum ImportForeignSchemaType
+{
+       FDW_IMPORT_SCHEMA_ALL,          /* all relations wanted */
+       FDW_IMPORT_SCHEMA_LIMIT_TO, /* include only listed tables in import */
+       FDW_IMPORT_SCHEMA_EXCEPT        /* exclude listed tables from import */
+} ImportForeignSchemaType;
+
+typedef struct ImportForeignSchemaStmt
+{
+       NodeTag         type;
+       char       *server_name;        /* FDW server name */
+       char       *remote_schema;      /* remote schema name to query */
+       char       *local_schema;       /* local schema to create objects in */
+       ImportForeignSchemaType list_type;      /* type of table list */
+       List       *table_list;         /* List of RangeVar */
+       List       *options;            /* list of options to pass to FDW */
+} ImportForeignSchemaStmt;
+
 /* ----------------------
  *             Create TRIGGER Statement
  * ----------------------
index 04e98109635fa08a742557e59f285b25e796a1ca..b52e50757c8c4e51569294f01e507958fe9446d4 100644 (file)
@@ -184,6 +184,7 @@ PG_KEYWORD("ilike", ILIKE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("immediate", IMMEDIATE, UNRESERVED_KEYWORD)
 PG_KEYWORD("immutable", IMMUTABLE, UNRESERVED_KEYWORD)
 PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
index ff203b201fd43e5b5955c8f7ba41cc3275275db0..e4dedb0c3ba2ca091da60a105d00f8185eeb27c3 100644 (file)
@@ -1193,6 +1193,16 @@ DROP TRIGGER trigtest_before_row ON foreign_schema.foreign_table_1;
 DROP TRIGGER trigtest_after_stmt ON foreign_schema.foreign_table_1;
 DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
 DROP FUNCTION dummy_trigger();
+-- IMPORT FOREIGN SCHEMA
+IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
+ERROR:  foreign-data wrapper "foo" has no handler
+IMPORT FOREIGN SCHEMA s1 LIMIT TO (t1) FROM SERVER s9 INTO public; --ERROR
+ERROR:  foreign-data wrapper "foo" has no handler
+IMPORT FOREIGN SCHEMA s1 EXCEPT (t1) FROM SERVER s9 INTO public; -- ERROR
+ERROR:  foreign-data wrapper "foo" has no handler
+IMPORT FOREIGN SCHEMA s1 EXCEPT (t1, t2) FROM SERVER s9 INTO public
+OPTIONS (option1 'value1', option2 'value2'); -- ERROR
+ERROR:  foreign-data wrapper "foo" has no handler
 -- DROP FOREIGN TABLE
 DROP FOREIGN TABLE no_table;                                    -- ERROR
 ERROR:  foreign table "no_table" does not exist
index 0f0869ee268c3afb54defec8818ecb8941fabd34..de9dbc8f386c3f55ff37c19be54dd7586a1d7cd2 100644 (file)
@@ -514,6 +514,13 @@ DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
 
 DROP FUNCTION dummy_trigger();
 
+-- IMPORT FOREIGN SCHEMA
+IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
+IMPORT FOREIGN SCHEMA s1 LIMIT TO (t1) FROM SERVER s9 INTO public; --ERROR
+IMPORT FOREIGN SCHEMA s1 EXCEPT (t1) FROM SERVER s9 INTO public; -- ERROR
+IMPORT FOREIGN SCHEMA s1 EXCEPT (t1, t2) FROM SERVER s9 INTO public
+OPTIONS (option1 'value1', option2 'value2'); -- ERROR
+
 -- DROP FOREIGN TABLE
 DROP FOREIGN TABLE no_table;                                    -- ERROR
 DROP FOREIGN TABLE IF EXISTS no_table;