Implement IMPORT FOREIGN SCHEMA.
authorTom Lane
Thu, 10 Jul 2014 19:01:31 +0000 (15:01 -0400)
committerTom Lane
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
     ,
     ,
-    , and
-    .
+    ,
+    , and
+    .
    
  
 
index e5b9e6618530471bc956b7f47682198046ee72ba..3db8ef1a132d1df8664be27ed9691b994d03f952 100644 (file)
         X
         X
        
+       
+        IMPORT FOREIGN SCHEMA
+        X
+        X
+        -
+       
        
         SELECT INTO
         X
index 6b5c8b7e97b3a6f2091e20892af67ac41fdeb088..5fd8d6fbbe9fbdb2da055c37d67ba8f99cd34e61 100644 (file)
@@ -696,6 +696,66 @@ AcquireSampleRowsFunc (Relation relation, int elevel,
 
    
 
+   
+    FDW Routines For <command>IMPORT FOREIGN SCHEMA</>
+
+    
+
+List *
+ImportForeignSchema (ImportForeignSchemaStmt *stmt, Oid serverOid);
+
+
+     Obtain a list of foreign table creation commands.  This function is
+     called when executing , 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  command.
+     These strings will be parsed and executed by the core server.
+    
+
+    
+     Within the ImportForeignSchemaStmt struct,
+     remote_schema is the name of the remote schema from
+     which tables are to be imported.
+     list_type identifies how to filter table names:
+     FDW_IMPORT_SCHEMA_ALL means that all tables in the remote
+     schema should be imported (in this case table_list is
+     empty), FDW_IMPORT_SCHEMA_LIMIT_TO means to include only
+     tables listed in table_list,
+     and FDW_IMPORT_SCHEMA_EXCEPT means to exclude the tables
+     listed in table_list.
+     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
+     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.
+    
+
+    
+     The FDW may ignore the local_schema field of
+     the ImportForeignSchemaStmt, because the core server
+     will automatically insert that name into the parsed CREATE
+     FOREIGN TABLE commands.
+    
+
+    
+     The FDW does not have to concern itself with implementing the filtering
+     specified by list_type and 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 IsImportableForeignTable() may be
+     useful to test whether a given foreign-table name will pass the filter.
+    
+
+    
+     If the FDW does not support importing table definitions, the
+     ImportForeignSchema pointer can be set to NULL.
+    
+
+   
+
    
 
    
index e6f6e205815cae003f3554d1b8df0f5885d28deb..43adb61455d919eae10c3debae954e4111b95296 100644 (file)
@@ -49,7 +49,8 @@
    
    
     
-     Create a foreign table, using ,
+     Create a foreign table, using 
+     or ,
      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
      
       
        user and password (specify these
-       for a user mapping, instead)
+       in a user mapping, instead)
       
      
      
 
    
   
+
+  
+   Importing Options
+
+   
+    postgres_fdw is able to import foreign table definitions
+    using .  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.
+   
+
+   
+    Importing behavior can be customized with the following options
+    (given in the IMPORT FOREIGN SCHEMA command):
+   
+
+   
+    
+     import_collate
+     
+      
+       This option controls whether column COLLATE options
+       are included in the definitions of foreign tables imported
+       from a foreign server. The default is 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.
+      
+     
+    
+    
+     import_default
+     
+      
+       This option controls whether column DEFAULT expressions
+       are included in the definitions of foreign tables imported
+       from a foreign server. The default is 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; nextval() is a common source of problems.
+       The IMPORT will fail altogether if an imported default
+       expression uses a function or operator that does not exist locally.
+      
+     
+    
+    
+     import_not_null
+     
+      
+       This option controls whether column NOT NULL
+       constraints are included in the definitions of foreign tables imported
+       from a foreign server. The default is true.
+      
+     
+    
+   
+
+   
+    Note that constraints other than NOT NULL will never be
+    imported from the remote tables, since 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.
+   
+  
  
 
  
@@ -422,7 +489,7 @@ CREATE USER MAPPING FOR local_user
 
 
 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 column_name
    options to the individual columns to show how they are named in the remote
    table.
+   In many cases, use of  is
+   preferable to constructing foreign table definitions manually.
   
  
 
index 1b0962c253d8f7be11a87c1c8324c28d84e100c6..b685e16a0fa0a524cf45aebbf428619241c579b3 100644 (file)
@@ -131,6 +131,7 @@ Complete list of usable sgml source files in this directory.
 
 
 
+
 
 
 
index 4a8cf3889c29bddcca3aa6d6bad3a5aff2f0b8e1..46a20eff14f8f25220a50fa23f1e311fe40f6a65 100644 (file)
@@ -231,6 +231,7 @@ SERVER film_server;
    
    
    
+   
   
  
 
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 @@
+
+
+
+  IMPORT FOREIGN SCHEMA
+
+  IMPORT FOREIGN SCHEMA
+  7
+  SQL - Language Statements
+
+  IMPORT FOREIGN SCHEMA
+  import table definitions from a foreign server
+
+
+IMPORT FOREIGN SCHEMA remote_schema
+[ { LIMIT TO | EXCEPT } ( table_name [, ...] ) ]
+FROM SERVER server_name
+INTO local_schema
+[ OPTIONS ( option 'value' [, ... ] ) ]
+
+
+  Description
+
+  
+   IMPORT FOREIGN SCHEMA 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.
+  
+
+  
+   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.
+  
+
+  
+   To use IMPORT FOREIGN SCHEMA, the user must have
+   USAGE privilege on the foreign server, as well as
+   CREATE privilege on the target schema.
+  
+
+  Parameters
+
+  
+
+   
+    remote_schema
+    
+     
+      The remote schema to import from. The specific meaning of a remote schema
+      depends on the foreign data wrapper in use.
+     
+    
+   
+
+   
+    LIMIT TO ( table_name [, ...] )
+    
+     
+      Import only foreign tables matching one of the given table names.
+      Other tables existing in the foreign schema will be ignored.
+     
+    
+   
+
+   
+    EXCEPT ( table_name [, ...] )
+    
+     
+      Exclude specified foreign tables from the import.  All tables
+      existing in the foreign schema will be imported except the
+      ones listed here.
+     
+    
+   
+
+   
+    server_name
+    
+     
+      The foreign server to import from.
+     
+    
+   
+
+   
+    local_schema
+    
+     
+      The schema in which the imported foreign tables will be created.
+     
+    
+   
+
+   
+    OPTIONS ( option 'value' [, ...] )
+    
+     
+      Options to be used during the import.
+      The allowed option names and values are specific to each foreign
+      data wrapper.
+     
+    
+   
+  
+
+  Examples
+
+  
+   Import table definitions from a remote schema foreign_films
+   on server film_server, creating the foreign tables in
+   local schema films:
+
+
+IMPORT FOREIGN SCHEMA foreign_films
+    FROM SERVER film_server INTO films;
+
+   
+
+  
+   As above, but import only the two tables actors and
+   directors (if they exist):
+
+
+IMPORT FOREIGN SCHEMA foreign_films LIMIT TO (actors, directors)
+    FROM SERVER film_server INTO films;
+
+   
+
+
+  Compatibility
+
+  
+   The IMPORT FOREIGN SCHEMA command conforms to the
+   SQL standard, except that the OPTIONS
+   clause is a PostgreSQL extension.
+  
+
+
+  See Also
+
+  
+   
+   
+  
+
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    defacl_privilege_target
 %type  DefACLOption
 %type    DefACLOptionList
+%type    import_qualification_type
+%type  import_qualification
 
 %type    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;