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);
/*
* Append a SQL string literal representing "val" to buf.
*/
-static void
+void
deparseStringLiteral(StringInfo buf, const char *val)
{
const char *valptr;
(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;
static bool postgresAnalyzeForeignTable(Relation relation,
AcquireSampleRowsFunc *func,
BlockNumber *totalpages);
+static List *postgresImportForeignSchema(ImportForeignSchemaStmt *stmt,
+ Oid serverOid);
/*
* Helper functions
/* Support functions for ANALYZE */
routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
+ /* Support functions for IMPORT FOREIGN SCHEMA */
+ routine->ImportForeignSchema = postgresImportForeignSchema;
+
PG_RETURN_POINTER(routine);
}
}
}
+/*
+ * 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.
*
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 */
-- 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;
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>
<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>
</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">
</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>
<programlisting>
CREATE FOREIGN TABLE foreign_table (
- id serial NOT NULL,
+ id integer NOT NULL,
data text
)
SERVER foreign_server
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>
<!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">
<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>
--- /dev/null
+<!--
+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>
&explain;
&fetch;
&grant;
+ &importForeignSchema;
&insert;
&listen;
&load;
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;
/*
#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
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);
+}
}
+/*
+ * 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.
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)
{
case T_CreateForeignTableStmt:
retval = _copyCreateForeignTableStmt(from);
break;
+ case T_ImportForeignSchemaStmt:
+ retval = _copyImportForeignSchemaStmt(from);
+ break;
case T_CreateTrigStmt:
retval = _copyCreateTrigStmt(from);
break;
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)
{
case T_CreateForeignTableStmt:
retval = _equalCreateForeignTableStmt(a, b);
break;
+ case T_ImportForeignSchemaStmt:
+ retval = _equalImportForeignSchemaStmt(a, b);
+ break;
case T_CreateTrigStmt:
retval = _equalCreateTrigStmt(a, b);
break;
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)
{
case T_CreateForeignTableStmt:
_outCreateForeignTableStmt(str, obj);
break;
+ case T_ImportForeignSchemaStmt:
+ _outImportForeignSchemaStmt(str, obj);
+ break;
case T_IndexStmt:
_outIndexStmt(str, obj);
break;
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
ResTarget *target;
struct PrivTarget *privtarget;
AccessPriv *accesspriv;
+ struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
}
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
%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
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
| FetchStmt
| GrantStmt
| GrantRoleStmt
+ | ImportForeignSchemaStmt
| IndexStmt
| InsertStmt
| ListenStmt
}
;
+/*****************************************************************************
+ *
+ * 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:
| IMMEDIATE
| IMMUTABLE
| IMPLICIT_P
+ | IMPORT_P
| INCLUDING
| INCREMENT
| INDEX
case T_AlterTableSpaceOptionsStmt:
case T_AlterTableSpaceMoveStmt:
case T_CreateForeignTableStmt:
+ case T_ImportForeignSchemaStmt:
case T_SecLabelStmt:
PreventCommandIfReadOnly(CreateCommandTag(parsetree));
break;
RemoveUserMapping((DropUserMappingStmt *) parsetree);
break;
+ case T_ImportForeignSchemaStmt:
+ ImportForeignSchema((ImportForeignSchemaStmt *) parsetree);
+ break;
+
case T_CompositeTypeStmt: /* CREATE TYPE (composite) */
{
CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
tag = "CREATE FOREIGN TABLE";
break;
+ case T_ImportForeignSchemaStmt:
+ tag = "IMPORT FOREIGN SCHEMA";
+ break;
+
case T_DropStmt:
switch (((DropStmt *) parsetree)->removeType)
{
case T_CreateUserMappingStmt:
case T_AlterUserMappingStmt:
case T_DropUserMappingStmt:
+ case T_ImportForeignSchemaStmt:
lev = LOGSTMT_DDL;
break;
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",
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)
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,
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
/* Support functions for ANALYZE */
AnalyzeForeignTable_function AnalyzeForeignTable;
+
+ /* Support functions for IMPORT FOREIGN SCHEMA */
+ ImportForeignSchema_function ImportForeignSchema;
} 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 */
T_AlterTableSpaceMoveStmt,
T_SecLabelStmt,
T_CreateForeignTableStmt,
+ T_ImportForeignSchemaStmt,
T_CreateExtensionStmt,
T_AlterExtensionStmt,
T_AlterExtensionContentsStmt,
} AlterForeignServerStmt;
/* ----------------------
- * Create FOREIGN TABLE Statements
+ * Create FOREIGN TABLE Statement
* ----------------------
*/
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
* ----------------------
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)
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
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;