Implement IMPORT FOREIGN SCHEMA.

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
This commit is contained in:
Tom Lane 2014-07-10 15:01:31 -04:00
parent 6a605cd6bd
commit 59efda3e50
29 changed files with 1239 additions and 15 deletions

View 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;

View 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;

View 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.
*

View 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 */

View 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;

View File

@ -3069,8 +3069,9 @@ ANALYZE measurement;
For additional information, see
<xref linkend="sql-createforeigndatawrapper">,
<xref linkend="sql-createserver">,
<xref linkend="sql-createusermapping">, and
<xref linkend="sql-createforeigntable">.
<xref linkend="sql-createusermapping">,
<xref linkend="sql-createforeigntable">, and
<xref linkend="sql-importforeignschema">.
</para>
</sect1>

View File

@ -603,6 +603,12 @@
<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>

View File

@ -696,6 +696,66 @@ AcquireSampleRowsFunc (Relation relation, int elevel,
</sect2>
<sect2 id="fdw-callbacks-import">
<title>FDW Routines For <command>IMPORT FOREIGN SCHEMA</></title>
<para>
<programlisting>
List *
ImportForeignSchema (ImportForeignSchemaStmt *stmt, Oid serverOid);
</programlisting>
Obtain a list of foreign table creation commands. This function is
called when executing <xref linkend="sql-importforeignschema">, and is
passed the parse tree for that statement, as well as the OID of the
foreign server to use. It should return a list of C strings, each of
which must contain a <xref linkend="sql-createforeigntable"> command.
These strings will be parsed and executed by the core server.
</para>
<para>
Within the <structname>ImportForeignSchemaStmt</> struct,
<structfield>remote_schema</> is the name of the remote schema from
which tables are to be imported.
<structfield>list_type</> identifies how to filter table names:
<literal>FDW_IMPORT_SCHEMA_ALL</> means that all tables in the remote
schema should be imported (in this case <structfield>table_list</> is
empty), <literal>FDW_IMPORT_SCHEMA_LIMIT_TO</> means to include only
tables listed in <structfield>table_list</>,
and <literal>FDW_IMPORT_SCHEMA_EXCEPT</> means to exclude the tables
listed in <structfield>table_list</>.
<structfield>options</> is a list of options used for the import process.
The meanings of the options are up to the FDW.
For example, an FDW could use an option to define whether the
<literal>NOT NULL</> attributes of columns should be imported.
These options need not have anything to do with those supported by the
FDW as database object options.
</para>
<para>
The FDW may ignore the <structfield>local_schema</> field of
the <structname>ImportForeignSchemaStmt</>, because the core server
will automatically insert that name into the parsed <command>CREATE
FOREIGN TABLE</> commands.
</para>
<para>
The FDW does not have to concern itself with implementing the filtering
specified by <structfield>list_type</> and <structfield>table_list</>,
either, as the core server will automatically skip any returned commands
for tables excluded according to those options. However, it's often
useful to avoid the work of creating commands for excluded tables in the
first place. The function <function>IsImportableForeignTable()</> may be
useful to test whether a given foreign-table name will pass the filter.
</para>
<para>
If the FDW does not support importing table definitions, the
<function>ImportForeignSchema</> pointer can be set to <literal>NULL</>.
</para>
</sect2>
</sect1>
<sect1 id="fdw-helpers">

View File

@ -49,7 +49,8 @@
</listitem>
<listitem>
<para>
Create a foreign table, using <xref linkend="sql-createforeigntable">,
Create a foreign table, using <xref linkend="sql-createforeigntable">
or <xref linkend="sql-importforeignschema">,
for each remote table you want to access. The columns of the foreign
table must match the referenced remote table. You can, however, use
table and/or column names different from the remote table's, if you
@ -99,7 +100,7 @@
<listitem>
<para>
<literal>user</literal> and <literal>password</literal> (specify these
for a user mapping, instead)
in a user mapping, instead)
</para>
</listitem>
<listitem>
@ -291,6 +292,72 @@
</variablelist>
</sect3>
<sect3>
<title>Importing Options</title>
<para>
<filename>postgres_fdw</> is able to import foreign table definitions
using <xref linkend="sql-importforeignschema">. This command creates
foreign table definitions on the local server that match tables or
views present on the remote server. If the remote tables to be imported
have columns of user-defined data types, the local server must have types
of the same names.
</para>
<para>
Importing behavior can be customized with the following options
(given in the <command>IMPORT FOREIGN SCHEMA</> command):
</para>
<variablelist>
<varlistentry>
<term><literal>import_collate</literal></term>
<listitem>
<para>
This option controls whether column <literal>COLLATE</> options
are included in the definitions of foreign tables imported
from a foreign server. The default is <literal>true</>. You might
need to turn this off if the remote server has a different set of
collation names than the local server does, which is likely to be the
case if it's running on a different operating system.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>import_default</literal></term>
<listitem>
<para>
This option controls whether column <literal>DEFAULT</> expressions
are included in the definitions of foreign tables imported
from a foreign server. The default is <literal>false</>. If you
enable this option, be wary of defaults that might get computed
differently on the local server than they would be on the remote
server; <function>nextval()</> is a common source of problems.
The <command>IMPORT</> will fail altogether if an imported default
expression uses a function or operator that does not exist locally.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>import_not_null</literal></term>
<listitem>
<para>
This option controls whether column <literal>NOT NULL</>
constraints are included in the definitions of foreign tables imported
from a foreign server. The default is <literal>true</>.
</para>
</listitem>
</varlistentry>
</variablelist>
<para>
Note that constraints other than <literal>NOT NULL</> will never be
imported from the remote tables, since <productname>PostgreSQL</>
does not support any other type of constraint on a foreign table.
Checking other types of constraints is always left to the remote server.
</para>
</sect3>
</sect2>
<sect2>
@ -422,7 +489,7 @@ CREATE USER MAPPING FOR local_user
<programlisting>
CREATE FOREIGN TABLE foreign_table (
id serial NOT NULL,
id integer NOT NULL,
data text
)
SERVER foreign_server
@ -434,6 +501,8 @@ CREATE FOREIGN TABLE foreign_table (
Column names must match as well, unless you attach <literal>column_name</>
options to the individual columns to show how they are named in the remote
table.
In many cases, use of <xref linkend="sql-importforeignschema"> is
preferable to constructing foreign table definitions manually.
</para>
</sect2>

View File

@ -131,6 +131,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY explain SYSTEM "explain.sgml">
<!ENTITY fetch SYSTEM "fetch.sgml">
<!ENTITY grant SYSTEM "grant.sgml">
<!ENTITY importForeignSchema SYSTEM "import_foreign_schema.sgml">
<!ENTITY insert SYSTEM "insert.sgml">
<!ENTITY listen SYSTEM "listen.sgml">
<!ENTITY load SYSTEM "load.sgml">

View File

@ -231,6 +231,7 @@ SERVER film_server;
<member><xref linkend="sql-dropforeigntable"></member>
<member><xref linkend="sql-createtable"></member>
<member><xref linkend="sql-createserver"></member>
<member><xref linkend="sql-importforeignschema"></member>
</simplelist>
</refsect1>
</refentry>

View File

@ -0,0 +1,168 @@
<!--
doc/src/sgml/ref/import_foreign_schema.sgml
PostgreSQL documentation
-->
<refentry id="SQL-IMPORTFOREIGNSCHEMA">
<indexterm zone="sql-importforeignschema">
<primary>IMPORT FOREIGN SCHEMA</primary>
</indexterm>
<refmeta>
<refentrytitle>IMPORT FOREIGN SCHEMA</refentrytitle>
<manvolnum>7</manvolnum>
<refmiscinfo>SQL - Language Statements</refmiscinfo>
</refmeta>
<refnamediv>
<refname>IMPORT FOREIGN SCHEMA</refname>
<refpurpose>import table definitions from a foreign server</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
IMPORT FOREIGN SCHEMA <replaceable class="PARAMETER">remote_schema</replaceable>
[ { LIMIT TO | EXCEPT } ( <replaceable class="PARAMETER">table_name</replaceable> [, ...] ) ]
FROM SERVER <replaceable class="PARAMETER">server_name</replaceable>
INTO <replaceable class="PARAMETER">local_schema</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
</synopsis>
</refsynopsisdiv>
<refsect1 id="SQL-IMPORTFOREIGNSCHEMA-description">
<title>Description</title>
<para>
<command>IMPORT FOREIGN SCHEMA</command> creates foreign tables that
represent tables existing on a foreign server. The new foreign tables
will be owned by the user issuing the command and are created with
the correct column definitions and options to match the remote tables.
</para>
<para>
By default, all tables and views existing in a particular schema on the
foreign server are imported. Optionally, the list of tables can be limited
to a specified subset, or specific tables can be excluded. The new foreign
tables are all created in the target schema, which must already exist.
</para>
<para>
To use <command>IMPORT FOREIGN SCHEMA</command>, the user must have
<literal>USAGE</literal> privilege on the foreign server, as well as
<literal>CREATE</literal> privilege on the target schema.
</para>
</refsect1>
<refsect1>
<title>Parameters</title>
<variablelist>
<varlistentry>
<term><replaceable class="PARAMETER">remote_schema</replaceable></term>
<listitem>
<para>
The remote schema to import from. The specific meaning of a remote schema
depends on the foreign data wrapper in use.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>LIMIT TO ( <replaceable class="PARAMETER">table_name</replaceable> [, ...] )</literal></term>
<listitem>
<para>
Import only foreign tables matching one of the given table names.
Other tables existing in the foreign schema will be ignored.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>EXCEPT ( <replaceable class="PARAMETER">table_name</replaceable> [, ...] )</literal></term>
<listitem>
<para>
Exclude specified foreign tables from the import. All tables
existing in the foreign schema will be imported except the
ones listed here.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
The foreign server to import from.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="PARAMETER">local_schema</replaceable></term>
<listitem>
<para>
The schema in which the imported foreign tables will be created.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ...] )</literal></term>
<listitem>
<para>
Options to be used during the import.
The allowed option names and values are specific to each foreign
data wrapper.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="SQL-IMPORTFOREIGNSCHEMA-examples">
<title>Examples</title>
<para>
Import table definitions from a remote schema <structname>foreign_films</>
on server <structname>film_server</>, creating the foreign tables in
local schema <structname>films</>:
<programlisting>
IMPORT FOREIGN SCHEMA foreign_films
FROM SERVER film_server INTO films;
</programlisting>
</para>
<para>
As above, but import only the two tables <structname>actors</> and
<literal>directors</> (if they exist):
<programlisting>
IMPORT FOREIGN SCHEMA foreign_films LIMIT TO (actors, directors)
FROM SERVER film_server INTO films;
</programlisting>
</para>
</refsect1>
<refsect1 id="SQL-IMPORTFOREIGNSCHEMA-compatibility">
<title>Compatibility</title>
<para>
The <command>IMPORT FOREIGN SCHEMA</command> command conforms to the
<acronym>SQL</acronym> standard, except that the <literal>OPTIONS</>
clause is a <productname>PostgreSQL</> extension.
</para>
</refsect1>
<refsect1>
<title>See Also</title>
<simplelist type="inline">
<member><xref linkend="sql-createforeigntable"></member>
<member><xref linkend="sql-createserver"></member>
</simplelist>
</refsect1>
</refentry>

View File

@ -159,6 +159,7 @@
&explain;
&fetch;
&grant;
&importForeignSchema;
&insert;
&listen;
&load;

View 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;
/*

View 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"
@ -27,9 +27,11 @@
#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"
@ -37,6 +39,16 @@
#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);
}

View 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.

View 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;

View 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;

View 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;

View File

@ -111,6 +111,13 @@ typedef struct PrivTarget
List *objs;
} PrivTarget;
/* Private struct for the result of import_qualification production */
typedef struct ImportQual
{
ImportForeignSchemaType type;
List *table_names;
} ImportQual;
/* ConstraintAttributeSpec yields an integer bitmask of these flags: */
#define CAS_NOT_DEFERRABLE 0x01
#define CAS_DEFERRABLE 0x02
@ -212,6 +219,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
ResTarget *target;
struct PrivTarget *privtarget;
AccessPriv *accesspriv;
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
}
@ -238,8 +246,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
LockStmt NotifyStmt ExplainableStmt PreparableStmt
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
@ -322,6 +330,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> defacl_privilege_target
%type <defelt> DefACLOption
%type <list> DefACLOptionList
%type <ival> import_qualification_type
%type <importqual> import_qualification
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
@ -556,7 +566,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@ -802,6 +812,7 @@ stmt :
| FetchStmt
| GrantStmt
| GrantRoleStmt
| ImportForeignSchemaStmt
| IndexStmt
| InsertStmt
| ListenStmt
@ -4272,6 +4283,52 @@ AlterForeignTableStmt:
}
;
/*****************************************************************************
*
* QUERY:
* IMPORT FOREIGN SCHEMA remote_schema
* [ { LIMIT TO | EXCEPT } ( table_list ) ]
* FROM SERVER server_name INTO local_schema [ OPTIONS (...) ]
*
****************************************************************************/
ImportForeignSchemaStmt:
IMPORT_P FOREIGN SCHEMA name import_qualification
FROM SERVER name INTO name create_generic_options
{
ImportForeignSchemaStmt *n = makeNode(ImportForeignSchemaStmt);
n->server_name = $8;
n->remote_schema = $4;
n->local_schema = $10;
n->list_type = $5->type;
n->table_list = $5->table_names;
n->options = $11;
$$ = (Node *) n;
}
;
import_qualification_type:
LIMIT TO { $$ = FDW_IMPORT_SCHEMA_LIMIT_TO; }
| EXCEPT { $$ = FDW_IMPORT_SCHEMA_EXCEPT; }
;
import_qualification:
import_qualification_type '(' relation_expr_list ')'
{
ImportQual *n = (ImportQual *) palloc(sizeof(ImportQual));
n->type = $1;
n->table_names = $3;
$$ = n;
}
| /*EMPTY*/
{
ImportQual *n = (ImportQual *) palloc(sizeof(ImportQual));
n->type = FDW_IMPORT_SCHEMA_ALL;
n->table_names = NIL;
$$ = n;
}
;
/*****************************************************************************
*
* QUERY:
@ -12909,6 +12966,7 @@ unreserved_keyword:
| IMMEDIATE
| IMMUTABLE
| IMPLICIT_P
| IMPORT_P
| INCLUDING
| INCREMENT
| INDEX

View 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;

View 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)

View 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,

View 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 */

View File

@ -357,6 +357,7 @@ typedef enum NodeTag
T_AlterTableSpaceMoveStmt,
T_SecLabelStmt,
T_CreateForeignTableStmt,
T_ImportForeignSchemaStmt,
T_CreateExtensionStmt,
T_AlterExtensionStmt,
T_AlterExtensionContentsStmt,

View 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
* ----------------------

View 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)

View 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

View 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;