diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index f341a72..822a31f 100644
*** a/contrib/pageinspect/rawpage.c
--- b/contrib/pageinspect/rawpage.c
*************** get_raw_page_internal(text *relname, For
*** 119,124 ****
--- 119,129 ----
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot get raw page from composite type \"%s\"",
RelationGetRelationName(rel))));
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot get raw page from foreign table \"%s\"",
+ RelationGetRelationName(rel))));
/*
* Reject attempts to read non-local temporary relations; we would be
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 3a5d9c2..e5ddd87 100644
*** a/contrib/pgstattuple/pgstattuple.c
--- b/contrib/pgstattuple/pgstattuple.c
*************** pgstat_relation(Relation rel, FunctionCa
*** 242,247 ****
--- 242,250 ----
case RELKIND_COMPOSITE_TYPE:
err = "composite type";
break;
+ case RELKIND_FOREIGN_TABLE:
+ err = "foreign table";
+ break;
default:
err = "unknown";
break;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 54a6dcc..50ee399 100644
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 149,154 ****
--- 149,159 ----
+ pg_foreign_table
+ additional foreign table information
+
+
+
pg_index
additional index information
***************
*** 1645,1652 ****
r> = ordinary table, i> = index,
S> = sequence, v> = view, c> =
! composite type, t> = TOAST
! table
--- 1650,1657 ----
r> = ordinary table, i> = index,
S> = sequence, v> = view, c> =
! composite type, t> = TOAST table,
! f> = foiregn table
***************
*** 2807,2812 ****
--- 2812,2828 ----
+ fdwhandler
+ oid
+ pg_proc .oid
+
+ References a handler function that is responsible for
+ supplying foreign-data wrapper routines.
+ Zero if no handler is provided.
+
+
+
+
fdwacl
aclitem[]
***************
*** 2921,2926 ****
--- 2937,2997 ----
+
+ pg_foreign_table
+
+
+ pg_foreign_table
+
+
+
+ The catalog pg_foreign_table contains part
+ of the information about foreign tables.
+ The rest is mostly in pg_class .
+
+
+
+ pg_foreign_table> Columns
+
+
+
+
+ Name
+ Type
+ References
+ Description
+
+
+
+
+
+ ftrelid
+ oid
+ pg_class .oid
+ The OID of the pg_class> entry for this foreign table
+
+
+
+ ftserver
+ oid
+ pg_foreign_server .oid
+ The OID of the foreign server for this foreign table
+
+
+
+ srvoptions
+ text[]
+
+
+ Foreign table specific options, as keyword=value> strings.
+
+
+
+
+
+
+
+
pg_index
diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml
index 9d30949..43305ad 100644
*** a/doc/src/sgml/information_schema.sgml
--- b/doc/src/sgml/information_schema.sgml
*************** ORDER BY c.ordinal_position;
*** 2384,2389 ****
--- 2384,2578 ----
+
+ foreign_table_options
+
+
+ The view foreign_table_options contains all the
+ options defined for foreign tables in the current database. Only
+ those foreign tables are shown that the current user has access to
+ (by way of being the owner or having some privilege).
+
+
+
+ foreign_table_options Columns
+
+
+
+
+ Name
+ Data Type
+ Description
+
+
+
+
+
+ foreign_table_catalog
+ sql_identifier
+ Name of the database that contains the foreign table (always the current database)
+
+
+
+ foreign_table_schema
+ sql_identifier
+ Name of the schema that contains the foreign table
+
+
+
+ foreign_table_name
+ sql_identifier
+ Name of the foreign table
+
+
+
+ foreign_server_catalog
+ sql_identifier
+ Name of the database that the foreign server is defined in (always the current database)
+
+
+
+ foreign_server_name
+ sql_identifier
+ Name of the foreign server
+
+
+
+ option_name
+ sql_identifier
+ Name of an option
+
+
+
+ option_value
+ character_data
+ Value of the option
+
+
+
+
+
+
+
+ column_options
+
+
+ The view column_options contains all the generic
+ options defined for colunms in the current database.
+ Only those columns are shown that the current user has
+ access to (by way of being the owner or having some privilege).
+
+
+
+ column_options Columns
+
+
+
+
+ Name
+ Data Type
+ Description
+
+
+
+
+
+ table_catalog
+ sql_identifier
+ Name of the database that contains the table (always the current database)
+
+
+
+ table_schema
+ sql_identifier
+ Name of the schema that contains the table
+
+
+
+ table_name
+ sql_identifier
+ Name of the table
+
+
+
+ column_name
+ sql_identifier
+ Name of the column
+
+
+
+ option_name
+ sql_identifier
+ Name of an option
+
+
+
+ option_value
+ character_data
+ Value of the option
+
+
+
+
+
+
+
+ foreign_tables
+
+
+ The view foreign_tables contains all foreign
+ tables defined in the current database. Only those foreign
+ tables are shown that the current user has access to (by way of
+ being the owner or having some privilege).
+
+
+
+ foreign_tables Columns
+
+
+
+
+ Name
+ Data Type
+ Description
+
+
+
+
+
+ foreign_table_catalog
+ sql_identifier
+ Name of the database that the foreign table is defined in (always the current database)
+
+
+
+ foreign_table_schema
+ sql_identifier
+ Name of the schema that contains the foreign table
+
+
+
+ foreign_table_name
+ sql_identifier
+ Name of the foreign table
+
+
+
+ foreign_server_catalog
+ sql_identifier
+ Name of the database that the foreign server is defined in (always the current database)
+
+
+
+ foreign_server_name
+ sql_identifier
+ Name of the foreign server
+
+
+
+
+
+
key_column_usage
*************** ORDER BY c.ordinal_position;
*** 4730,4737 ****
Type of the table: BASE TABLE for a
persistent base table (the normal table type),
! VIEW for a view, or LOCAL
! TEMPORARY for a temporary table
--- 4919,4927 ----
Type of the table: BASE TABLE for a
persistent base table (the normal table type),
! VIEW for a view, FOREIGN TABLE
! for a foreign table, or
! LOCAL TEMPORARY for a temporary table
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index a352a43..f40fa9d 100644
*** a/doc/src/sgml/ref/allfiles.sgml
--- b/doc/src/sgml/ref/allfiles.sgml
*************** Complete list of usable sgml source file
*** 12,17 ****
--- 12,18 ----
+
*************** Complete list of usable sgml source file
*** 50,55 ****
--- 51,57 ----
+
*************** Complete list of usable sgml source file
*** 85,90 ****
--- 87,93 ----
+
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index e1aa293..612645d 100644
*** a/doc/src/sgml/ref/alter_default_privileges.sgml
--- b/doc/src/sgml/ref/alter_default_privileges.sgml
*************** REVOKE [ GRANT OPTION FOR ]
*** 71,78 ****
ALTER DEFAULT PRIVILEGES> allows you to set the privileges
that will be applied to objects created in the future. (It does not
affect privileges assigned to already-existing objects.) Currently,
! only the privileges for tables (including views), sequences, and
! functions can be altered.
--- 71,78 ----
ALTER DEFAULT PRIVILEGES> allows you to set the privileges
that will be applied to objects created in the future. (It does not
affect privileges assigned to already-existing objects.) Currently,
! only the privileges for tables (including views and foreign tables),
! sequences, and functions can be altered.
diff --git a/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml b/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml
index 4e9e8a2..ead2c2e 100644
*** a/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml
--- b/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml
*************** PostgreSQL documentation
*** 23,28 ****
--- 23,29 ----
ALTER FOREIGN DATA WRAPPER name
[ VALIDATOR valfunction | NO VALIDATOR ]
+ [ HANDLER handler | NO HANDLER ]
[ OPTIONS ( [ ADD | SET | DROP ] option ['value '] [, ... ]) ]
ALTER FOREIGN DATA WRAPPER name OWNER TO new_owner
*************** ALTER FOREIGN DATA WRAPPER
+ HANDLER handler
+
+
+ Specifies a new foreign-data wrapper handler function.
+
+
+
+
+
+ NO HANDLER
+
+
+ This is used to specify that the foreign-data wrapper should no
+ longer have a handler function.
+
+
+ Note that foreign tables which uses a foreign-data wrapper with no
+ handler can't be used in a SELECT statement.
+
+
+
+
+
OPTIONS ( [ ADD | SET | DROP ] option ['value '] [, ... ] )
*************** ALTER FOREIGN DATA WRAPPER dbi VALIDATOR
*** 127,134 ****
ALTER FOREIGN DATA WRAPPER conforms to ISO/IEC
9075-9 (SQL/MED). The standard does not specify the
! VALIDATOR and OWNER TO> variants of the
! command.
--- 151,158 ----
ALTER FOREIGN DATA WRAPPER conforms to ISO/IEC
9075-9 (SQL/MED). The standard does not specify the
! VALIDATOR , HANDLER> and OWNER TO>
! variants of the command.
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index ...ebb3d40 .
*** a/doc/src/sgml/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 0 ****
--- 1,536 ----
+
+
+
+
+ ALTER FOREIGN TABLE
+ 7
+ SQL - Language Statements
+
+
+
+ ALTER FOREIGN TABLE
+ change the definition of a foreign table
+
+
+
+ ALTER FOREIGN TABLE
+
+
+
+
+ ALTER FOREIGN TABLE name [ * ]
+ action [, ... ]
+ ALTER FOREIGN TABLE name [ * ]
+ RENAME [ COLUMN ] column TO new_column
+ ALTER FOREIGN TABLE name
+ RENAME TO new_name
+ ALTER FOREIGN TABLE name
+ SET SCHEMA new_schema
+
+ where action is one of:
+
+ ADD [ COLUMN ] column type [ column_constraint [ ... ] ]
+ DROP [ COLUMN ] [ IF EXISTS ] column [ RESTRICT | CASCADE ]
+ ALTER [ COLUMN ] column [ SET DATA ] TYPE type
+ ALTER [ COLUMN ] column SET DEFAULT expression
+ ALTER [ COLUMN ] column DROP DEFAULT
+ ALTER [ COLUMN ] column { SET | DROP } NOT NULL
+ ALTER [ COLUMN ] column OPTIONS ( [ ADD | SET | DROP ] option ['value '] [, ... ])
+ ADD table_constraint
+ DROP CONSTRAINT [ IF EXISTS ] constraint_name [ RESTRICT | CASCADE ]
+ DISABLE RULE rewrite_rule_name
+ ENABLE RULE rewrite_rule_name
+ ENABLE REPLICA RULE rewrite_rule_name
+ ENABLE ALWAYS RULE rewrite_rule_name
+ INHERIT parent_table
+ NO INHERIT parent_table
+ OWNER TO new_owner
+ OPTIONS ( [ ADD | SET | DROP ] option ['value '] [, ... ])
+
+
+
+
+ Description
+
+
+ ALTER FOREIGN TABLE changes the definition of an existing table.
+ There are several subforms:
+
+
+
+ ADD COLUMN
+
+
+ This form adds a new column to the foreign table, using the same syntax as
+ .
+
+
+
+
+
+ DROP COLUMN [ IF EXISTS ]
+
+
+ This form drops a column from a foreign table.
+ Table constraints involving the column will be automatically
+ dropped as well. You will need to say CASCADE> if
+ anything outside the table depends on the column, for example,
+ views.
+ If IF EXISTS is specified and the column
+ does not exist, no error is thrown. In this case a notice
+ is issued instead.
+
+
+
+
+
+ SET DATA TYPE
+
+
+ This form changes the type of a column of a foreign table.
+ Simple table constraints involving the column will be automatically
+ converted to use the new column type by reparsing the originally
+ supplied expression. The optional USING
+ clause specifies how to compute the new column value from the old;
+ if omitted, the default conversion is the same as an assignment
+ cast from old data type to new. A USING
+ clause must be provided if there is no implicit or assignment
+ cast from old to new type.
+
+
+
+
+
+ SET /DROP NOT NULL
+
+
+ These forms change whether a column is marked to allow null
+ values or to reject null values. You can only use SET
+ NOT NULL> when the column contains no null values.
+
+
+
+
+
+ ADD table_constraint
+
+
+ This form adds a new constraint to a foreign table using the same syntax as
+ .
+
+
+
+
+
+ DROP CONSTRAINT [ IF EXISTS ]
+
+
+ This form drops the specified constraint on a foreign table.
+ If IF EXISTS is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+
+
+
+
+
+ DISABLE /ENABLE [ REPLICA | ALWAYS ] RULE
+
+
+ These forms configure the firing of rewrite rules belonging to the table.
+ A disabled rule is still known to the system, but is not applied
+ during query rewriting.
+ This configuration is ignored for ON SELECT rules, which
+ are always applied in order to keep views working even if the current
+ session is in a non-default replication role.
+
+
+
+
+
+ INHERIT parent_table
+
+
+ This form adds the target table as a new child of the specified parent
+ table. Subsequently, queries against the parent will include records
+ of the target table. To be added as a child, the target table must
+ already contain all the same columns as the parent (it could have
+ additional columns, too). The columns must have matching data types,
+ and if they have NOT NULL constraints in the parent
+ then they must also have NOT NULL constraints in the
+ child.
+
+
+
+ There must also be matching child-table constraints for all
+ CHECK constraints of the parent.
+
+
+
+ The parent must not have OIDs because foreign table can't have OIDs.
+
+
+
+
+
+ NO INHERIT parent_table
+
+
+ This form removes the target table from the list of children of the
+ specified parent table.
+ Queries against the parent table will no longer include records drawn
+ from the target table.
+
+
+
+
+
+ OWNER
+
+
+ This form changes the owner of the foreign table to the
+ specified user.
+
+
+
+
+
+ RENAME
+
+
+ The RENAME forms change the name of a foreign table
+ or the name of an individual column in
+ a foreign table. There is no effect on the stored data.
+
+
+
+
+
+ SET SCHEMA
+
+
+ This form moves the foreign table into another schema. Associated
+ constraints owned by table columns are moved as well.
+
+
+
+
+
+ OPTIONS ( [ ADD | SET | DROP ] option ['value '] [, ... ] )
+
+
+ Change options for the foreign table or the column of the foreign table.
+ ADD>, SET>, and DROP>
+ specify the action to be performed. ADD> is assumed
+ if no operation is explicitly specified. Option names must be
+ unique; names and values are also validated using the foreign
+ data wrapper library.
+
+
+
+
+
+
+
+
+ All the actions except RENAME and SET SCHEMA>
+ can be combined into
+ a list of multiple alterations to apply in parallel. For example, it
+ is possible to add several columns and/or alter the type of several
+ columns in a single command. This is particularly useful with large
+ foreign tables, since only one pass over the table need be made.
+
+
+
+ You must own the table to use ALTER FOREIGN TABLE>.
+ To change the schema of a foreign table, you must also have
+ CREATE privilege on the new schema.
+ To add the table as a new child of a parent table, you must own the
+ parent table as well.
+ To alter the owner, you must also be a direct or indirect member of the new
+ owning role, and that role must have CREATE privilege on
+ the table's schema. (These restrictions enforce that altering the owner
+ doesn't do anything you couldn't do by dropping and recreating the table.
+ However, a superuser can alter ownership of any table anyway.)
+
+
+
+
+ Parameters
+
+
+
+
+ name
+
+
+ The name (possibly schema-qualified) of an existing table to
+ alter. If ONLY> is specified, only that table is
+ altered. If ONLY> is not specified, the table and any
+ descendant foreign tables are altered.
+
+
+
+
+
+ column
+
+
+ Name of a new or existing column.
+
+
+
+
+
+ new_column
+
+
+ New name for an existing column.
+
+
+
+
+
+ new_name
+
+
+ New name for the table.
+
+
+
+
+
+ type
+
+
+ Data type of the new column, or new data type for an existing
+ column.
+
+
+
+
+
+ table_constraint
+
+
+ New table constraint for the table.
+
+
+
+
+
+ constraint_name
+
+
+ Name of an existing constraint to drop.
+
+
+
+
+
+ CASCADE
+
+
+ Automatically drop objects that depend on the dropped column
+ or constraint (for example, views referencing the column).
+
+
+
+
+
+ RESTRICT
+
+
+ Refuse to drop the column or constraint if there are any dependent
+ objects. This is the default behavior.
+
+
+
+
+
+ storage_parameter
+
+
+ The name of a foreign table storage parameter.
+
+
+
+
+
+ value
+
+
+ The new value for a foreign table storage parameter.
+ This might be a number or a word depending on the parameter.
+
+
+
+
+
+ parent_table
+
+
+ A parent table to associate or de-associate with this table.
+
+
+
+
+
+ new_owner
+
+
+ The user name of the new owner of the table.
+
+
+
+
+
+ new_schema
+
+
+ The name of the schema to which the table will be moved.
+
+
+
+
+
+
+
+
+ Notes
+
+
+ The key word COLUMN is noise and can be omitted.
+
+
+
+ Consistency with the foreign server is not checked even when a column is
+ added or removed with ADD COLUMN or
+ DROP COLUMN , a system oid> column is added
+ or removed, a CHECK> or NOT NULL> constraint is
+ added, or column type is changed with ALTER TYPE>.
+
+
+
+ Refer to for a further description of valid
+ parameters. has further information on
+ inheritance.
+
+
+
+
+ Examples
+
+
+ To add a column of type varchar to a foreign table:
+
+ ALTER FOREIGN TABLE distributors ADD COLUMN address varchar(30);
+
+
+
+
+ To drop a column from a foreign table:
+
+ ALTER FOREIGN TABLE distributors DROP COLUMN address RESTRICT;
+
+
+
+
+ To change the types of two existing columns in one operation:
+
+ ALTER FOREIGN TABLE distributors
+ ALTER COLUMN address TYPE varchar(80),
+ ALTER COLUMN name TYPE varchar(100);
+
+
+
+
+ To rename an existing column:
+
+ ALTER FOREIGN TABLE distributors RENAME COLUMN address TO city;
+
+
+
+
+ To rename an existing table:
+
+ ALTER FOREIGN TABLE distributors RENAME TO suppliers;
+
+
+
+
+ To add a not-null constraint to a column:
+
+ ALTER FOREIGN TABLE distributors ALTER COLUMN street SET NOT NULL;
+
+ To remove a not-null constraint from a column:
+
+ ALTER FOREIGN TABLE distributors ALTER COLUMN street DROP NOT NULL;
+
+
+
+
+ To chage options of a column of a foreign table:
+
+ ALTER FOREIGN TABLE myschema.distributors ALTER COLUMN street OPTIONS (ADD opt1 'value', SET opt2, 'value2', DROP opt3 'value3');
+
+
+
+
+ To add a check constraint to a foreign table:
+
+ ALTER FOREIGN TABLE distributors ADD CONSTRAINT zipchk CHECK (char_length(zipcode) = 5);
+
+
+
+
+ To remove a check constraint from a foreign table and all its children:
+
+ ALTER FOREIGN TABLE distributors DROP CONSTRAINT zipchk;
+
+
+
+
+ To remove a check constraint from a foreign table only:
+
+ ALTER FOREIGN TABLE ONLY distributors DROP CONSTRAINT zipchk;
+
+
+
+
+ To move a foreign table to a different schema:
+
+ ALTER FOREIGN TABLE myschema.distributors SET SCHEMA yourschema;
+
+
+
+
+ To chage options of a foreign table:
+
+ ALTER FOREIGN TABLE myschema.distributors OPTIONS (ADD opt1 'value', SET opt2, 'value2', DROP opt3 'value3');
+
+
+
+
+
+
+ Compatibility
+
+
+ The forms ADD , DROP>,
+ and SET DATA TYPE
+ conform with the SQL standard. The other forms are
+ PostgreSQL extensions of the SQL standard.
+ Also, the ability to specify more than one manipulation in a single
+ ALTER FOREIGN TABLE> command is an extension.
+
+
+
+ ALTER FOREIGN TABLE DROP COLUMN> can be used to drop the only
+ column of a foreign table, leaving a zero-column table. This is an
+ extension of SQL, which disallows zero-column foreign tables.
+
+
+
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 784feae..7a1da1d 100644
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
*************** ALTER TABLE object_name |
DATABASE object_name |
DOMAIN object_name |
+ FOREIGN TABLE object_name |
FUNCTION function_name ( [ [ argmode ] [ argname ] argtype [, ...] ] ) |
INDEX object_name |
LARGE OBJECT large_object_oid |
*************** COMMENT ON COLUMN my_table.my_column IS
*** 247,252 ****
--- 248,254 ----
COMMENT ON CONVERSION my_conv IS 'Conversion to UTF8';
COMMENT ON DATABASE my_database IS 'Development Database';
COMMENT ON DOMAIN my_domain IS 'Email Address Domain';
+ COMMENT ON FOREIGN TABLE my_schema.my_foreign_table IS 'Employee Information in other database';
COMMENT ON FUNCTION my_function (timestamp) IS 'Returns Roman Numeral';
COMMENT ON INDEX my_index IS 'Enforces uniqueness on employee ID';
COMMENT ON LANGUAGE plpython IS 'Python support for stored procedures';
diff --git a/doc/src/sgml/ref/create_foreign_data_wrapper.sgml b/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
index f626d56..e61a725 100644
*** a/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
--- b/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
*************** PostgreSQL documentation
*** 23,28 ****
--- 23,29 ----
CREATE FOREIGN DATA WRAPPER name
[ VALIDATOR valfunction | NO VALIDATOR ]
+ [ HANDLER handler | NO HANDLER ]
[ OPTIONS ( option 'value ' [, ... ] ) ]
*************** CREATE FOREIGN DATA WRAPPER
+ HANDLER handler
+
+
+ handler is the
+ name of a previously registered function that will be called to
+ retrieve a set of functions for foreign tables.
+ The validator function must take no arguments.
+ The return type must be fdw_handler .
+
+
+
+
+
OPTIONS ( option 'value ' [, ... ] )
*************** CREATE FOREIGN DATA WRAPPER mywrapper
*** 151,158 ****
CREATE FOREIGN DATA WRAPPER conforms to ISO/IEC
! 9075-9 (SQL/MED), with the exception that
! the VALIDATOR clause is an extension and the
clauses LIBRARY and LANGUAGE
are not yet implemented in PostgreSQL.
--- 165,172 ----
CREATE FOREIGN DATA WRAPPER conforms to ISO/IEC
! 9075-9 (SQL/MED), with the exception that the VALIDATOR
! and HANDLER clauses are extensions and the
clauses LIBRARY and LANGUAGE
are not yet implemented in PostgreSQL.
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index ...572f70f .
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 0 ****
--- 1,435 ----
+
+
+
+
+ CREATE FOREIGN TABLE
+ 7
+ SQL - Language Statements
+
+
+
+ CREATE FOREIGN TABLE
+ define a new foreign table
+
+
+
+ CREATE FOREIGN TABLE
+
+
+
+
+ CREATE FOREIGN TABLE table_name ( [
+ { column_name data_type [ OPTIONS ( option 'value ' [, ... ] ) ] [ column_constraint [ ... ] ]
+ | table_constraint
+ | LIKE parent_table [ like_option ... ] }
+ [, ... ]
+ ] )
+ [ INHERITS ( parent_table [, ... ] ) ]
+ SERVER server_name
+ [ OPTIONS ( option 'value ' [, ... ] ) ]
+
+ where column_constraint is:
+
+ [ CONSTRAINT constraint_name ]
+ { NOT NULL |
+ NULL |
+ CHECK ( expression )
+ }
+
+ and table_constraint is:
+
+ [ CONSTRAINT constraint_name ]
+ CHECK ( expression )
+
+ and like_option is:
+
+ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | COMMENTS | ALL }
+
+
+
+
+
+ Description
+
+
+ CREATE FOREIGN TABLE will create a new foreign table
+ in the current database. The table will be owned by the user issuing the
+ command.
+
+
+
+ If a schema name is given (for example, CREATE FOREIGN TABLE
+ myschema.mytable ...>) then the table is created in the specified
+ schema. Otherwise it is created in the current schema.
+ The name of the foreign table must be
+ distinct from the name of any other foreign table, table, sequence, index,
+ or view in the same schema.
+
+
+
+ CREATE FOREIGN TABLE also automatically creates a data
+ type that represents the composite type corresponding to one row of
+ the foreign table. Therefore, foreign tables cannot have the same
+ name as any existing data type in the same schema.
+
+
+
+ The optional constraint clauses specify constraints (tests) that
+ retrieved rows must satisfy for an select operation
+ to succeed. A constraint is an SQL object that helps define the
+ set of valid values in the table in various ways.
+
+
+
+ There are two ways to define constraints: table constraints and
+ column constraints. A column constraint is defined as part of a
+ column definition. A table constraint definition is not tied to a
+ particular column, and it can encompass more than one column.
+ Every column constraint can also be written as a table constraint;
+ a column constraint is only a notational convenience for use when the
+ constraint only affects one column.
+
+
+
+ To create a foreign table, the foreign-data wrapper of the foreign
+ server must have handler function.
+
+
+
+
+ Parameters
+
+
+
+
+ table_name
+
+
+ The name (optionally schema-qualified) of the table to be created.
+
+
+
+
+
+ column_name
+
+
+ The name of a column to be created in the new table.
+
+
+
+
+
+ data_type
+
+
+ The data type of the column. This can include array
+ specifiers. For more information on the data types supported by
+ PostgreSQL , refer to .
+
+
+
+
+
+ OPTIONS ( option = 'value ' [, ...] )
+
+
+ This clause specified options for the new foreign table, or the
+ column of the new foreign table.
+ The allowed option names and values are specific to each foreign
+ data wrapper and are validated using the foreign-data wrapper
+ library. Option names must be unique.
+
+
+
+
+
+ INHERITS ( parent_table [, ... ] )
+
+
+ The optional INHERITS> clause specifies a list of
+ tables from which the new table automatically inherits all
+ columns.
+
+
+
+ Use of INHERITS> creates a persistent relationship
+ between the new child table and its parent table(s). Schema
+ modifications to the parent(s) normally propagate to children
+ as well, and by default the data of the child table is included in
+ scans of the parent(s).
+
+
+
+ If the same column name exists in more than one parent
+ table, an error is reported unless the data types of the columns
+ match in each of the parent tables. If there is no conflict,
+ then the duplicate columns are merged to form a single column in
+ the new table. If the column name list of the new table
+ contains a column name that is also inherited, the data type must
+ likewise match the inherited column(s), and the column
+ definitions are merged into one. If the
+ new table explicitly specifies a default value for the column,
+ this default overrides any defaults from inherited declarations
+ of the column. Otherwise, any parents that specify default
+ values for the column must all specify the same default, or an
+ error will be reported.
+
+
+
+ CHECK> constraints are merged in essentially the same way as
+ columns: if multiple parent tables and/or the new table definition
+ contain identically-named CHECK> constraints, these
+ constraints must all have the same check expression, or an error will be
+ reported. Constraints having the same name and expression will
+ be merged into one copy. Notice that an unnamed CHECK>
+ constraint in the new table will never be merged, since a unique name
+ will always be chosen for it.
+
+
+
+ The parent must not have OIDs because foreign table can't have OIDs.
+
+
+
+
+
+
+ LIKE parent_table [ like_option ... ]
+
+
+ The LIKE clause specifies a table from which
+ the new table automatically copies all column names, their data types,
+ and their not-null constraints.
+
+
+ Unlike INHERITS , the new table and original table
+ are completely decoupled after creation is complete. Changes to the
+ original table will not be applied to the new table, and it is not
+ possible to include data of the new table in scans of the original
+ table.
+
+
+ Default expressions for the copied column definitions will only be
+ copied if INCLUDING DEFAULTS is specified. The
+ default behavior is to exclude default expressions, resulting in the
+ copied columns in the new table having null defaults.
+
+
+ Not-null constraints are always copied to the new table.
+ CHECK constraints will only be copied if
+ INCLUDING CONSTRAINTS is specified; other types of
+ constraints will never be copied. Also, no distinction is made between
+ column constraints and table constraints — when constraints are
+ requested, all check constraints are copied.
+
+
+ Comments for the copied columns and constraints
+ will only be copied if INCLUDING COMMENTS
+ is specified. The default behavior is to exclude comments, resulting in
+ the copied columns and constraints in the new table having no comments.
+
+
+ INCLUDING ALL is an abbreviated form of
+ INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING COMMENTS .
+
+
+ Note also that unlike INHERITS , columns and
+ constraints copied by LIKE> are not merged with similarly
+ named columns and constraints.
+ If the same name is specified explicitly or in another
+ LIKE clause, an error is signalled.
+
+
+
+
+
+ CONSTRAINT constraint_name
+
+
+ An optional name for a column or table constraint. If the
+ constraint is violated, the constraint name is present in error messages,
+ so constraint names like col must be positive> can be used
+ to communicate helpful constraint information to client applications.
+ (Double-quotes are needed to specify constraint names that contain spaces.)
+ If a constraint name is not specified, the system generates a name.
+
+
+
+
+
+ NOT NULL>
+
+
+ The column is not allowed to contain null values.
+
+
+
+
+
+ NULL>
+
+
+ The column is allowed to contain null values. This is the default.
+
+
+
+ This clause is only provided for compatibility with
+ non-standard SQL databases. Its use is discouraged in new
+ applications.
+
+
+
+
+
+ CHECK ( expression )
+
+
+ The CHECK> clause specifies an expression producing a
+ Boolean result which new or updated rows must satisfy for an
+ insert or update operation to succeed. Expressions evaluating
+ to TRUE or UNKNOWN succeed. Should any row of an insert or
+ update operation produce a FALSE result an error exception is
+ raised and the insert or update does not alter the database. A
+ check constraint specified as a column constraint should
+ reference that column's value only, while an expression
+ appearing in a table constraint can reference multiple columns.
+
+
+
+ Currently, CHECK expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row.
+
+
+
+
+
+
+
+
+
+
+ Examples
+
+
+ Create foreign table films> with film_server>:
+
+
+ CREATE FOREIGN TABLE films (
+ code char(5) NOT NULL,
+ title varchar(40) NOT NULL,
+ did integer NOT NULL,
+ date_prod date,
+ kind varchar(10),
+ len interval hour to minute
+ )
+ SERVER film_server;
+
+
+
+
+ Define a check column constraint:
+
+
+ CREATE FOREIGN TABLE distributors (
+ did integer CHECK (did > 100),
+ name varchar(40)
+ )
+ SERVER distributor_server;
+
+
+
+
+ Define a check table constraint:
+
+
+ CREATE FOREIGN TABLE distributors (
+ did integer,
+ name varchar(40)
+ CONSTRAINT con1 CHECK (did > 100 AND name <> '')
+ )
+ SERVER distributor_server;
+
+
+
+
+
+
+ Compatibility
+
+
+ The CREATE FOREIGN TABLE command conforms to the
+ SQL standard, with exceptions listed below.
+
+
+
+ Column Check Constraints
+
+
+ The SQL standard says that CHECK> column constraints
+ can only refer to the column they apply to; only CHECK>
+ table constraints can refer to multiple columns.
+ PostgreSQL does not enforce this
+ restriction; it treats column and table check constraints alike.
+
+
+
+
+ NULL Constraint
+
+
+ The NULL> constraint
(actually a
+ non-constraint) is a PostgreSQL
+ extension to the SQL standard that is included for compatibility with some
+ other database systems (and for symmetry with the NOT
+ NULL constraint). Since it is the default for any
+ column, its presence is simply noise.
+
+
+
+
+ Inheritance
+
+
+ Multiple inheritance via the INHERITS clause is
+ a PostgreSQL language extension.
+ SQL:1999 and later define single inheritance using a
+ different syntax and different semantics. SQL:1999-style
+ inheritance is not yet supported by
+ PostgreSQL .
+
+
+
+
+ Zero-column tables
+
+
+ PostgreSQL allows a table of no columns
+ to be created (for example, CREATE FOREIGN TABLE foo();>). This
+ is an extension from the SQL standard, which does not allow zero-column
+ tables. Zero-column tables are not in themselves very useful, but
+ disallowing them creates odd special cases for ALTER TABLE
+ DROP COLUMN>, so it seems cleaner to ignore this spec restriction.
+
+
+
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_sequence.sgml
index 7f94d24..f36b616 100644
*** a/doc/src/sgml/ref/create_sequence.sgml
--- b/doc/src/sgml/ref/create_sequence.sgml
*************** CREATE [ TEMPORARY | TEMP ] SEQUENCE
--- 45,51 ----
Temporary sequences exist in a special schema, so a schema name cannot be
given when creating a temporary sequence.
The sequence name must be distinct from the name of any other sequence,
! table, index, view, or foreign table in the same schema.
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 8635e80..4314fb1 100644
*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
*************** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY
*** 96,103 ****
schema. Otherwise it is created in the current schema. Temporary
tables exist in a special schema, so a schema name cannot be given
when creating a temporary table. The name of the table must be
! distinct from the name of any other table, sequence, index, or view
! in the same schema.
--- 96,103 ----
schema. Otherwise it is created in the current schema. Temporary
tables exist in a special schema, so a schema name cannot be given
when creating a temporary table. The name of the table must be
! distinct from the name of any other table, sequence, index, view,
! or foreign table in the same schema.
diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml
index 6676383..dd15507 100644
*** a/doc/src/sgml/ref/create_view.sgml
--- b/doc/src/sgml/ref/create_view.sgml
*************** CREATE [ OR REPLACE ] [ TEMP | TEMPORARY
*** 50,56 ****
schema. Otherwise it is created in the current schema. Temporary
views exist in a special schema, so a schema name cannot be given
when creating a temporary view. The name of the view must be
! distinct from the name of any other view, table, sequence, or index
in the same schema.
--- 50,56 ----
schema. Otherwise it is created in the current schema. Temporary
views exist in a special schema, so a schema name cannot be given
when creating a temporary view. The name of the view must be
! distinct from the name of any other view, table, sequence, index or foreign table
in the same schema.
diff --git a/doc/src/sgml/ref/drop_foreign_table.sgml b/doc/src/sgml/ref/drop_foreign_table.sgml
index ...1b3af5b .
*** a/doc/src/sgml/ref/drop_foreign_table.sgml
--- b/doc/src/sgml/ref/drop_foreign_table.sgml
***************
*** 0 ****
--- 1,123 ----
+
+
+
+
+ DROP FOREIGN TABLE
+ 7
+ SQL - Language Statements
+
+
+
+ DROP FOREIGN TABLE
+ remove a foreign table
+
+
+
+ DROP FOREIGN TABLE
+
+
+
+
+ DROP FOREIGN TABLE [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ]
+
+
+
+
+ Description
+
+
+ DROP FOREIGN TABLE removes foreign tables from the database.
+ Only its owner can drop a foreign table.
+
+
+
+ DROP FOREIGN TABLE always removes any rules
+ and constraints that exist for the target table.
+ However, to drop a foreign table that is referenced by a view,
+ CASCADE> must be
+ specified. (CASCADE> will remove a dependent view entirely,
+
+
+
+
+ Parameters
+
+
+
+ IF EXISTS
+
+
+ Do not throw an error if theforeign table does not exist. A notice is issued
+ in this case.
+
+
+
+
+
+ name
+
+
+ The name (optionally schema-qualified) of theforeign table to drop.
+
+
+
+
+
+ CASCADE
+
+
+ Automatically drop objects that depend on theforeign table (such as
+ views).
+
+
+
+
+
+ RESTRICT
+
+
+ Refuse to drop theforeign table if any objects depend on it. This is
+ the default.
+
+
+
+
+
+
+
+ Examples
+
+
+ To destroy two foreign tables, films and
+ distributors :
+
+
+ DROP FOREIGN TABLE films, distributors;
+
+
+
+
+
+ Compatibility
+
+
+ This command conforms to the ISO/IEC 9075-9 (SQL/MED), except that the
+ standard only allows one foreign table to be dropped per command, and apart
+ from the IF EXISTS> option, which is a PostgreSQL>
+ extension.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index 0c960aa..7b25763 100644
*** a/doc/src/sgml/ref/lock.sgml
--- b/doc/src/sgml/ref/lock.sgml
*************** LOCK [ TABLE ] [ ONLY ] name
! The name (optionally schema-qualified) of an existing table to
lock. If ONLY> is specified, only that table is
locked. If ONLY> is not specified, the table and all
its descendant tables (if any) are locked.
--- 108,115 ----
name
! The name (optionally schema-qualified) of an existing table
! or a existing foreign table to
lock. If ONLY> is specified, only that table is
locked. If ONLY> is not specified, the table and all
its descendant tables (if any) are locked.
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 8242b53..8a18231 100644
*** a/doc/src/sgml/ref/pg_dump.sgml
--- b/doc/src/sgml/ref/pg_dump.sgml
*************** PostgreSQL documentation
*** 408,416 ****
--table=table
! Dump only tables (or views or sequences) matching table . Multiple tables can be
! selected by writing multiple -t> switches. Also, the
table parameter is
interpreted as a pattern according to the same rules used by
psql>'s \d> commands (see --table=table
! Dump only tables (or views or sequences or foreign tables) matching
! table . Multiple tables
! can be selected by writing multiple -t> switches. Also, the
table parameter is
interpreted as a pattern according to the same rules used by
psql>'s \d> commands (see
! For each relation (table, view, index, or sequence) matching the
pattern , show all
columns, their types, the tablespace (if not the default) and any
special attributes such as NOT NULL or defaults.
! Associated indexes, constraints, rules, and triggers are
! also shown.
(Matching the pattern> is defined in
below.)
--- 867,880 ----
! For each relation (table, view, index, sequence or foreign table)
! matching the
pattern , show all
columns, their types, the tablespace (if not the default) and any
special attributes such as NOT NULL or defaults.
! Associated indexes, constraints, rules, triggers are
! also shown. If the relation was as foreign table, associated foreign
! server is shown too.
(Matching the pattern> is defined in
below.)
*************** testdb=>
*** 882,888 ****
The command form \d+ is identical, except that
more information is displayed: any comments associated with the
columns of the table are shown, as is the presence of OIDs in the
! table, and the view definition if the relation is a view.
--- 884,891 ----
The command form \d+ is identical, except that
more information is displayed: any comments associated with the
columns of the table are shown, as is the presence of OIDs in the
! table, the view definition if the relation is a view, and the generic
! options if the relation is a foreign table.
*************** testdb=>
*** 895,903 ****
If \d is used without a
pattern argument, it is
! equivalent to \dtvs which will show a list of
! all visible tables, views, and sequences. This is purely a convenience
! measure.
--- 898,906 ----
If \d is used without a
pattern argument, it is
! equivalent to \dtvsE which will show a list of
! all visible tables, views, sequences and foreign tables.
! This is purely a convenience measure.
*************** testdb=>
*** 1035,1040 ****
--- 1038,1057 ----
+ \det[+] [ pattern ]
+
+
+ Lists foreign tables (mnemonic: external tables
).
+ If pattern is
+ specified, only entries whose table name or schema name matches
+ the pattern are listed. If the form \det+
+ is used, combined generic options is shown additionally.
+
+
+
+
+
+
\des[+] [ pattern ]
*************** testdb=>
*** 1197,1209 ****
\ds[S+] [ pattern ]
\dt[S+] [ pattern ]
\dv[S+] [ pattern ]
In this group of commands, the letters
i , s ,
t , and v
! stand for index, sequence, table, and view, respectively.
You can specify any or all of
these letters, in any order, to obtain a listing of objects
of these types. For example, \dit> lists indexes
--- 1214,1228 ----
\ds[S+] [ pattern ]
\dt[S+] [ pattern ]
\dv[S+] [ pattern ]
+ \dE[S+] [ pattern ]
In this group of commands, the letters
i , s ,
t , and v
! stand for index, sequence, table, view, and foreign table,
! respectively.
You can specify any or all of
these letters, in any order, to obtain a listing of objects
of these types. For example, \dit> lists indexes
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 24f8249..e5f7c86 100644
*** a/doc/src/sgml/ref/select.sgml
--- b/doc/src/sgml/ref/select.sgml
*************** TABLE { [ ONLY ] objtype);
*************** ExecGrantStmt_oids(InternalGrant *istmt)
*** 545,550 ****
--- 552,558 ----
{
case ACL_OBJECT_RELATION:
case ACL_OBJECT_SEQUENCE:
+ case ACL_OBJECT_FOREIGN_TABLE:
ExecGrant_Relation(istmt);
break;
case ACL_OBJECT_DATABASE:
*************** objectNamesToOids(GrantObjectType objtyp
*** 594,599 ****
--- 602,608 ----
{
case ACL_OBJECT_RELATION:
case ACL_OBJECT_SEQUENCE:
+ case ACL_OBJECT_FOREIGN_TABLE:
foreach(cell, objnames)
{
RangeVar *relvar = (RangeVar *) lfirst(cell);
*************** objectsInSchemaToOids(GrantObjectType ob
*** 718,728 ****
switch (objtype)
{
case ACL_OBJECT_RELATION:
! /* Process both regular tables and views */
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
break;
case ACL_OBJECT_SEQUENCE:
objs = getRelationsInNamespace(namespaceId, RELKIND_SEQUENCE);
--- 727,739 ----
switch (objtype)
{
case ACL_OBJECT_RELATION:
! /* Process both regular tables, views and foreign tables */
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_FOREIGN_TABLE);
+ objects = list_concat(objects, objs);
break;
case ACL_OBJECT_SEQUENCE:
objs = getRelationsInNamespace(namespaceId, RELKIND_SEQUENCE);
*************** ExecGrant_Relation(InternalGrant *istmt)
*** 1689,1701 ****
errmsg("\"%s\" is not a sequence",
NameStr(pg_class_tuple->relname))));
/* Adjust the default permissions based on whether it is a sequence */
if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
{
! if (pg_class_tuple->relkind == RELKIND_SEQUENCE)
! this_privileges = ACL_ALL_RIGHTS_SEQUENCE;
! else
! this_privileges = ACL_ALL_RIGHTS_RELATION;
}
else
this_privileges = istmt->privileges;
--- 1700,1728 ----
errmsg("\"%s\" is not a sequence",
NameStr(pg_class_tuple->relname))));
+ /* Used GRANT FOREIGN TABLE on a non-foreign-table? */
+ if (istmt->objtype == ACL_OBJECT_FOREIGN_TABLE &&
+ pg_class_tuple->relkind != RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a foreign table",
+ NameStr(pg_class_tuple->relname))));
+
/* Adjust the default permissions based on whether it is a sequence */
if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
{
! switch (pg_class_tuple->relkind)
! {
! case RELKIND_SEQUENCE:
! this_privileges = ACL_ALL_RIGHTS_SEQUENCE;
! break;
! case RELKIND_FOREIGN_TABLE:
! this_privileges = ACL_ALL_RIGHTS_FOREIGN_TABLE;
! break;
! default:
! this_privileges = ACL_ALL_RIGHTS_RELATION;
! break;
! }
}
else
this_privileges = istmt->privileges;
*************** ExecGrant_Relation(InternalGrant *istmt)
*** 1729,1734 ****
--- 1756,1771 ----
this_privileges &= (AclMode) ACL_ALL_RIGHTS_SEQUENCE;
}
}
+ else if (pg_class_tuple->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ if (this_privileges & ~((AclMode) ACL_ALL_RIGHTS_FOREIGN_TABLE))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+ errmsg("foreign table \"%s\" only supports SELECT privileges",
+ NameStr(pg_class_tuple->relname))));
+ }
+ }
else
{
if (this_privileges & ~((AclMode) ACL_ALL_RIGHTS_RELATION))
*************** ExecGrant_Relation(InternalGrant *istmt)
*** 1781,1789 ****
&isNull);
if (isNull)
{
! old_acl = acldefault(pg_class_tuple->relkind == RELKIND_SEQUENCE ?
! ACL_OBJECT_SEQUENCE : ACL_OBJECT_RELATION,
! ownerId);
/* There are no old member roles according to the catalogs */
noldmembers = 0;
oldmembers = NULL;
--- 1818,1835 ----
&isNull);
if (isNull)
{
! switch (pg_class_tuple->relkind)
! {
! case RELKIND_SEQUENCE:
! old_acl = acldefault(ACL_OBJECT_SEQUENCE, ownerId);
! break;
! case RELKIND_FOREIGN_TABLE:
! old_acl = acldefault(ACL_OBJECT_FOREIGN_TABLE, ownerId);
! break;
! default:
! old_acl = acldefault(ACL_OBJECT_RELATION, ownerId);
! break;
! }
/* There are no old member roles according to the catalogs */
noldmembers = 0;
oldmembers = NULL;
*************** ExecGrant_Relation(InternalGrant *istmt)
*** 1812,1823 ****
--- 1858,1883 ----
bool replaces[Natts_pg_class];
int nnewmembers;
Oid *newmembers;
+ AclObjectKind aclkind;
/* Determine ID to do the grant as, and available grant options */
select_best_grantor(GetUserId(), this_privileges,
old_acl, ownerId,
&grantorId, &avail_goptions);
+ switch (pg_class_tuple->relkind)
+ {
+ case RELKIND_SEQUENCE:
+ aclkind = ACL_KIND_SEQUENCE;
+ break;
+ case RELKIND_FOREIGN_TABLE:
+ aclkind = ACL_KIND_FOREIGN_TABLE;
+ break;
+ default:
+ aclkind = ACL_KIND_CLASS;
+ break;
+ }
+
/*
* Restrict the privileges to what we can actually grant, and emit
* the standards-mandated warning and error messages.
*************** ExecGrant_Relation(InternalGrant *istmt)
*** 1825,1833 ****
this_privileges =
restrict_and_check_grant(istmt->is_grant, avail_goptions,
istmt->all_privs, this_privileges,
! relOid, grantorId,
! pg_class_tuple->relkind == RELKIND_SEQUENCE
! ? ACL_KIND_SEQUENCE : ACL_KIND_CLASS,
NameStr(pg_class_tuple->relname),
0, NULL);
--- 1885,1891 ----
this_privileges =
restrict_and_check_grant(istmt->is_grant, avail_goptions,
istmt->all_privs, this_privileges,
! relOid, grantorId, aclkind,
NameStr(pg_class_tuple->relname),
0, NULL);
*************** ExecGrant_Relation(InternalGrant *istmt)
*** 1909,1914 ****
--- 1967,1982 ----
this_privileges &= (AclMode) ACL_SELECT;
}
+ else if (pg_class_tuple->relkind == RELKIND_FOREIGN_TABLE &&
+ this_privileges & ~((AclMode) ACL_SELECT))
+ {
+ /* Foreign tables have the same restriction with sequences. */
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+ errmsg("foreign table \"%s\" only supports SELECT column privileges",
+ NameStr(pg_class_tuple->relname))));
+ this_privileges &= (AclMode) ACL_SELECT;
+ }
expand_col_privileges(col_privs->cols, relOid,
this_privileges,
*************** static const char *const no_priv_msg[MAX
*** 3080,3086 ****
/* ACL_KIND_FDW */
gettext_noop("permission denied for foreign-data wrapper %s"),
/* ACL_KIND_FOREIGN_SERVER */
! gettext_noop("permission denied for foreign server %s")
};
static const char *const not_owner_msg[MAX_ACL_KIND] =
--- 3148,3156 ----
/* ACL_KIND_FDW */
gettext_noop("permission denied for foreign-data wrapper %s"),
/* ACL_KIND_FOREIGN_SERVER */
! gettext_noop("permission denied for foreign server %s"),
! /* ACL_KIND_FOREIGN_TABLE */
! gettext_noop("permission denied for foreign table %s"),
};
static const char *const not_owner_msg[MAX_ACL_KIND] =
*************** static const char *const not_owner_msg[M
*** 3120,3126 ****
/* ACL_KIND_FDW */
gettext_noop("must be owner of foreign-data wrapper %s"),
/* ACL_KIND_FOREIGN_SERVER */
! gettext_noop("must be owner of foreign server %s")
};
--- 3190,3198 ----
/* ACL_KIND_FDW */
gettext_noop("must be owner of foreign-data wrapper %s"),
/* ACL_KIND_FOREIGN_SERVER */
! gettext_noop("must be owner of foreign server %s"),
! /* ACL_KIND_FOREIGN_TABLE */
! gettext_noop("must be owner of foreign table %s"),
};
*************** pg_class_aclmask(Oid table_oid, Oid role
*** 3410,3418 ****
if (isNull)
{
/* No ACL, so build default ACL */
! acl = acldefault(classForm->relkind == RELKIND_SEQUENCE ?
! ACL_OBJECT_SEQUENCE : ACL_OBJECT_RELATION,
! ownerId);
aclDatum = (Datum) 0;
}
else
--- 3482,3499 ----
if (isNull)
{
/* No ACL, so build default ACL */
! switch (classForm->relkind)
! {
! case RELKIND_SEQUENCE:
! acl = acldefault(ACL_OBJECT_SEQUENCE, ownerId);
! break;
! case RELKIND_FOREIGN_TABLE:
! acl = acldefault(ACL_OBJECT_FOREIGN_TABLE, ownerId);
! break;
! default:
! acl = acldefault(ACL_OBJECT_RELATION, ownerId);
! break;
! }
aclDatum = (Datum) 0;
}
else
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index a912971..461cb79 100644
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 36,41 ****
--- 36,42 ----
#include "catalog/pg_depend.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
+ #include "catalog/pg_foreign_table.h"
#include "catalog/pg_language.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_namespace.h"
*************** static const Oid object_classes[MAX_OCLA
*** 152,157 ****
--- 153,159 ----
ForeignDataWrapperRelationId, /* OCLASS_FDW */
ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */
UserMappingRelationId, /* OCLASS_USER_MAPPING */
+ ForeignTableRelationId, /* OCLASS_FOREIGN_TABLE */
DefaultAclRelationId /* OCLASS_DEFACL */
};
*************** getObjectClass(const ObjectAddress *obje
*** 2072,2077 ****
--- 2074,2083 ----
case UserMappingRelationId:
return OCLASS_USER_MAPPING;
+ case ForeignTableRelationId:
+ Assert(object->objectSubId == 0);
+ return OCLASS_FOREIGN_TABLE;
+
case DefaultAclRelationId:
return OCLASS_DEFACL;
}
*************** getRelationDescription(StringInfo buffer
*** 2754,2759 ****
--- 2760,2769 ----
appendStringInfo(buffer, _("composite type %s"),
relname);
break;
+ case RELKIND_FOREIGN_TABLE:
+ appendStringInfo(buffer, _("foreign table %s"),
+ relname);
+ break;
default:
/* shouldn't get here */
appendStringInfo(buffer, _("relation %s"),
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index a3b8e7b..821f3e7 100644
*** a/src/backend/catalog/genbki.pl
--- b/src/backend/catalog/genbki.pl
*************** sub emit_pgattr_row
*** 368,374 ****
attislocal => 't',
attinhcount => '0',
attacl => '_null_',
! attoptions => '_null_'
);
return {%PGATTR_DEFAULTS, %row};
}
--- 368,375 ----
attislocal => 't',
attinhcount => '0',
attacl => '_null_',
! attoptions => '_null_',
! attgenoptions => '_null_'
);
return {%PGATTR_DEFAULTS, %row};
}
*************** sub emit_schemapg_row
*** 399,404 ****
--- 400,406 ----
# Only the fixed-size portions of the descriptors are ever used.
delete $row->{attacl};
delete $row->{attoptions};
+ delete $row->{attgenoptions};
# Expand booleans from 'f'/'t' to 'false'/'true'.
# Some values might be other macros (eg FLOAT4PASSBYVAL), don't change.
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index dcc53e1..a11f3e1 100644
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 42,47 ****
--- 42,48 ----
#include "catalog/namespace.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_constraint.h"
+ #include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_statistic.h"
*************** static List *insert_ordered_unique_oid(L
*** 121,129 ****
*/
/*
! * The initializers below do not include the attoptions or attacl fields,
! * but that's OK - we're never going to reference anything beyond the
! * fixed-size portion of the structure anyway.
*/
static FormData_pg_attribute a1 = {
--- 122,130 ----
*/
/*
! * The initializers below do not include the attgenoptions or attoptions or
! * attacl fields, but that's OK - we're never going to reference anything
! * beyond the fixed-size portion of the structure anyway.
*/
static FormData_pg_attribute a1 = {
*************** heap_create(const char *relname,
*** 267,272 ****
--- 268,274 ----
{
case RELKIND_VIEW:
case RELKIND_COMPOSITE_TYPE:
+ case RELKIND_FOREIGN_TABLE:
create_storage = false;
/*
*************** InsertPgAttributeTuple(Relation pg_attri
*** 541,546 ****
--- 543,549 ----
/* start out with empty permissions and empty options */
nulls[Anum_pg_attribute_attacl - 1] = true;
nulls[Anum_pg_attribute_attoptions - 1] = true;
+ nulls[Anum_pg_attribute_attgenoptions - 1] = true;
tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
*************** AddNewRelationTuple(Relation pg_class_de
*** 769,775 ****
new_rel_reltup->reltuples = 1;
break;
default:
! /* Views, etc, have no disk storage */
new_rel_reltup->relpages = 0;
new_rel_reltup->reltuples = 0;
break;
--- 772,778 ----
new_rel_reltup->reltuples = 1;
break;
default:
! /* Views and foreign tables, etc, have no disk storage */
new_rel_reltup->relpages = 0;
new_rel_reltup->reltuples = 0;
break;
*************** heap_create_with_catalog(const char *rel
*** 983,989 ****
/* Use binary-upgrade overrides if applicable */
if (OidIsValid(binary_upgrade_next_heap_relfilenode) &&
(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
! relkind == RELKIND_VIEW || relkind == RELKIND_COMPOSITE_TYPE))
{
relid = binary_upgrade_next_heap_relfilenode;
binary_upgrade_next_heap_relfilenode = InvalidOid;
--- 986,993 ----
/* Use binary-upgrade overrides if applicable */
if (OidIsValid(binary_upgrade_next_heap_relfilenode) &&
(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
! relkind == RELKIND_VIEW || relkind == RELKIND_COMPOSITE_TYPE ||
! relkind == RELKIND_FOREIGN_TABLE))
{
relid = binary_upgrade_next_heap_relfilenode;
binary_upgrade_next_heap_relfilenode = InvalidOid;
*************** heap_create_with_catalog(const char *rel
*** 1009,1014 ****
--- 1013,1019 ----
{
case RELKIND_RELATION:
case RELKIND_VIEW:
+ case RELKIND_FOREIGN_TABLE:
relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid,
relnamespace);
break;
*************** heap_create_with_catalog(const char *rel
*** 1045,1051 ****
* Decide whether to create an array type over the relation's rowtype. We
* do not create any array types for system catalogs (ie, those made
* during initdb). We create array types for regular relations, views,
! * and composite types ... but not, eg, for toast tables or sequences.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
relkind == RELKIND_VIEW ||
--- 1050,1056 ----
* Decide whether to create an array type over the relation's rowtype. We
* do not create any array types for system catalogs (ie, those made
* during initdb). We create array types for regular relations, views,
! * composite types and foreign tables ... but not, eg, for toast tables or sequences.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
relkind == RELKIND_VIEW ||
*************** heap_drop_with_catalog(Oid relid)
*** 1564,1573 ****
RelationGetRelationName(rel))));
/*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
! rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE)
{
RelationDropStorage(rel);
}
--- 1569,1600 ----
RelationGetRelationName(rel))));
/*
+ * Delete pg_foreign_table tuple first.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(ForeignTableRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache(FOREIGNTABLEREL,
+ ObjectIdGetDatum(relid), 0, 0, 0);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for foreign table %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+ }
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
! rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
! rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
{
RelationDropStorage(rel);
}
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 8d9790d..feb16ff 100644
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
*************** CREATE VIEW column_domain_usage AS
*** 465,471 ****
AND a.attrelid = c.oid
AND a.atttypid = t.oid
AND t.typtype = 'd'
! AND c.relkind IN ('r', 'v')
AND a.attnum > 0
AND NOT a.attisdropped
AND pg_has_role(t.typowner, 'USAGE');
--- 465,471 ----
AND a.attrelid = c.oid
AND a.atttypid = t.oid
AND t.typtype = 'd'
! AND c.relkind IN ('r', 'v', 'f')
AND a.attnum > 0
AND NOT a.attisdropped
AND pg_has_role(t.typowner, 'USAGE');
*************** CREATE VIEW column_privileges AS
*** 504,510 ****
pr_c.relowner
FROM (SELECT oid, relname, relnamespace, relowner, (aclexplode(relacl)).*
FROM pg_class
! WHERE relkind IN ('r', 'v')
) pr_c (oid, relname, relnamespace, relowner, grantor, grantee, prtype, grantable),
pg_attribute a
WHERE a.attrelid = pr_c.oid
--- 504,510 ----
pr_c.relowner
FROM (SELECT oid, relname, relnamespace, relowner, (aclexplode(relacl)).*
FROM pg_class
! WHERE relkind IN ('r', 'v', 'f')
) pr_c (oid, relname, relnamespace, relowner, grantor, grantee, prtype, grantable),
pg_attribute a
WHERE a.attrelid = pr_c.oid
*************** CREATE VIEW column_privileges AS
*** 526,532 ****
) pr_a (attrelid, attname, grantor, grantee, prtype, grantable),
pg_class c
WHERE pr_a.attrelid = c.oid
! AND relkind IN ('r','v')
) x,
pg_namespace nc,
pg_authid u_grantor,
--- 526,532 ----
) pr_a (attrelid, attname, grantor, grantee, prtype, grantable),
pg_class c
WHERE pr_a.attrelid = c.oid
! AND relkind IN ('r', 'v', 'f')
) x,
pg_namespace nc,
pg_authid u_grantor,
*************** CREATE VIEW column_udt_usage AS
*** 569,575 ****
WHERE a.attrelid = c.oid
AND a.atttypid = t.oid
AND nc.oid = c.relnamespace
! AND a.attnum > 0 AND NOT a.attisdropped AND c.relkind in ('r', 'v')
AND pg_has_role(coalesce(bt.typowner, t.typowner), 'USAGE');
GRANT SELECT ON column_udt_usage TO PUBLIC;
--- 569,575 ----
WHERE a.attrelid = c.oid
AND a.atttypid = t.oid
AND nc.oid = c.relnamespace
! AND a.attnum > 0 AND NOT a.attisdropped AND c.relkind in ('r', 'v', 'f')
AND pg_has_role(coalesce(bt.typowner, t.typowner), 'USAGE');
GRANT SELECT ON column_udt_usage TO PUBLIC;
*************** CREATE VIEW columns AS
*** 692,698 ****
AND nc.oid = c.relnamespace
AND (NOT pg_is_other_temp_schema(nc.oid))
! AND a.attnum > 0 AND NOT a.attisdropped AND c.relkind in ('r', 'v')
AND (pg_has_role(c.relowner, 'USAGE')
OR has_column_privilege(c.oid, a.attnum,
--- 692,698 ----
AND nc.oid = c.relnamespace
AND (NOT pg_is_other_temp_schema(nc.oid))
! AND a.attnum > 0 AND NOT a.attisdropped AND c.relkind in ('r', 'v', 'f')
AND (pg_has_role(c.relowner, 'USAGE')
OR has_column_privilege(c.oid, a.attnum,
*************** CREATE VIEW tables AS
*** 1793,1798 ****
--- 1793,1799 ----
CASE WHEN nc.oid = pg_my_temp_schema() THEN 'LOCAL TEMPORARY'
WHEN c.relkind = 'r' THEN 'BASE TABLE'
WHEN c.relkind = 'v' THEN 'VIEW'
+ WHEN c.relkind = 'f' THEN 'FOREIGN TABLE'
ELSE null END
AS character_data) AS table_type,
*************** CREATE VIEW tables AS
*** 1817,1823 ****
FROM pg_namespace nc JOIN pg_class c ON (nc.oid = c.relnamespace)
LEFT JOIN (pg_type t JOIN pg_namespace nt ON (t.typnamespace = nt.oid)) ON (c.reloftype = t.oid)
! WHERE c.relkind IN ('r', 'v')
AND (NOT pg_is_other_temp_schema(nc.oid))
AND (pg_has_role(c.relowner, 'USAGE')
OR has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
--- 1818,1824 ----
FROM pg_namespace nc JOIN pg_class c ON (nc.oid = c.relnamespace)
LEFT JOIN (pg_type t JOIN pg_namespace nt ON (t.typnamespace = nt.oid)) ON (c.reloftype = t.oid)
! WHERE c.relkind IN ('r', 'v', 'f')
AND (NOT pg_is_other_temp_schema(nc.oid))
AND (pg_has_role(c.relowner, 'USAGE')
OR has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
*************** CREATE VIEW view_column_usage AS
*** 2129,2135 ****
AND dt.refclassid = 'pg_catalog.pg_class'::regclass
AND dt.refobjid = t.oid
AND t.relnamespace = nt.oid
! AND t.relkind IN ('r', 'v')
AND t.oid = a.attrelid
AND dt.refobjsubid = a.attnum
AND pg_has_role(t.relowner, 'USAGE');
--- 2130,2136 ----
AND dt.refclassid = 'pg_catalog.pg_class'::regclass
AND dt.refobjid = t.oid
AND t.relnamespace = nt.oid
! AND t.relkind IN ('r', 'v', 'f')
AND t.oid = a.attrelid
AND dt.refobjsubid = a.attnum
AND pg_has_role(t.relowner, 'USAGE');
*************** CREATE VIEW view_table_usage AS
*** 2199,2205 ****
AND dt.refclassid = 'pg_catalog.pg_class'::regclass
AND dt.refobjid = t.oid
AND t.relnamespace = nt.oid
! AND t.relkind IN ('r', 'v')
AND pg_has_role(t.relowner, 'USAGE');
GRANT SELECT ON view_table_usage TO PUBLIC;
--- 2200,2206 ----
AND dt.refclassid = 'pg_catalog.pg_class'::regclass
AND dt.refobjid = t.oid
AND t.relnamespace = nt.oid
! AND t.relkind IN ('r', 'v', 'f')
AND pg_has_role(t.relowner, 'USAGE');
GRANT SELECT ON view_table_usage TO PUBLIC;
*************** CREATE VIEW element_types AS
*** 2344,2350 ****
'TABLE'::text, a.attnum, a.atttypid
FROM pg_class c, pg_attribute a
WHERE c.oid = a.attrelid
! AND c.relkind IN ('r', 'v')
AND attnum > 0 AND NOT attisdropped
UNION ALL
--- 2345,2351 ----
'TABLE'::text, a.attnum, a.atttypid
FROM pg_class c, pg_attribute a
WHERE c.oid = a.attrelid
! AND c.relkind IN ('r', 'v', 'f')
AND attnum > 0 AND NOT attisdropped
UNION ALL
*************** GRANT SELECT ON element_types TO PUBLIC;
*** 2388,2393 ****
--- 2389,2415 ----
-- SQL/MED views; these use section numbers from part 9 of the standard.
+ /*
+ * 24.2
+ * COLUMN_OPTIONS view
+ */
+ CREATE VIEW column_options AS
+ SELECT CAST(current_database() AS sql_identifier) AS table_catalog,
+ CAST(nc.nspname AS sql_identifier) AS table_schema,
+ CAST(c.relname AS sql_identifier) AS table_name,
+ CAST(a.attname AS sql_identifier) AS column_name,
+ CAST((pg_options_to_table(a.attgenoptions)).option_name AS sql_identifier) AS option_name,
+ CAST((pg_options_to_table(a.attgenoptions)).option_value AS sql_identifier) AS option_value
+ FROM pg_attribute a, pg_class c, pg_authid u, pg_namespace nc
+ WHERE u.oid = c.relowner
+ AND c.oid = a.attrelid
+ AND nc.oid = c.relnamespace
+ AND (pg_has_role(c.relowner, 'USAGE')
+ OR has_column_privilege(c.oid, a.attnum,
+ 'SELECT, INSERT, UPDATE, REFERENCES'));
+
+ GRANT SELECT ON column_options TO PUBLIC;
+
/* Base view for foreign-data wrappers */
CREATE VIEW _pg_foreign_data_wrappers AS
SELECT w.oid,
*************** CREATE VIEW foreign_servers AS
*** 2481,2486 ****
--- 2503,2562 ----
GRANT SELECT ON foreign_servers TO PUBLIC;
+ /* Base view for foreign tables */
+ CREATE VIEW _pg_foreign_tables AS
+ SELECT
+ CAST(current_database() AS sql_identifier) AS foreign_table_catalog,
+ n.nspname AS foreign_table_schema,
+ c.relname AS foreign_table_name,
+ t.ftoptions AS ftoptions,
+ CAST(current_database() AS sql_identifier) AS foreign_server_catalog,
+ CAST(srvname AS sql_identifier) AS foreign_server_name,
+ CAST(u.rolname AS sql_identifier) AS authorization_identifier
+ FROM pg_foreign_table t, pg_foreign_server s, pg_foreign_data_wrapper w,
+ pg_authid u, pg_namespace n, pg_class c
+ WHERE w.oid = s.srvfdw
+ AND u.oid = c.relowner
+ AND (pg_has_role(c.relowner, 'USAGE')
+ OR has_table_privilege(c.oid, 'SELECT')
+ OR has_any_column_privilege(c.oid, 'SELECT'))
+ AND n.oid = c.relnamespace
+ AND c.oid = t.ftrelid
+ AND c.relkind = 'f'
+ AND s.oid = t.ftserver;
+
+
+ /*
+ * 24.8
+ * FOREIGN_TABLE_OPTIONS view
+ */
+ CREATE VIEW foreign_table_options AS
+ SELECT foreign_table_catalog,
+ foreign_table_schema,
+ foreign_table_name,
+ CAST((pg_options_to_table(t.ftoptions)).option_name AS sql_identifier) AS option_name,
+ CAST((pg_options_to_table(t.ftoptions)).option_value AS character_data) AS option_value
+ FROM _pg_foreign_tables t;
+
+ GRANT SELECT ON TABLE foreign_table_options TO PUBLIC;
+
+
+ /*
+ * 24.9
+ * FOREIGN_TABLES view
+ */
+ CREATE VIEW foreign_tables AS
+ SELECT foreign_table_catalog,
+ foreign_table_schema,
+ foreign_table_name,
+ foreign_server_catalog,
+ foreign_server_name
+ FROM _pg_foreign_tables;
+
+ GRANT SELECT ON foreign_tables TO PUBLIC;
+
+
+
/* Base view for user mappings */
CREATE VIEW _pg_user_mappings AS
SELECT um.oid,
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 6a37e61..9f5d423 100644
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
*************** get_object_address(ObjectType objtype, L
*** 111,116 ****
--- 111,117 ----
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
+ case OBJECT_FOREIGN_TABLE:
relation =
get_relation_by_qualified_name(objtype, objname, lockmode);
address.classId = RelationRelationId;
*************** get_relation_by_qualified_name(ObjectTyp
*** 369,374 ****
--- 370,382 ----
errmsg("\"%s\" is not a view",
RelationGetRelationName(relation))));
break;
+ case OBJECT_FOREIGN_TABLE:
+ if (relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a foreign table",
+ RelationGetRelationName(relation))));
+ break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
break;
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 0d0227d..73b0b6e 100644
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
*************** ExecRenameStmt(RenameStmt *stmt)
*** 91,96 ****
--- 91,97 ----
case OBJECT_COLUMN:
case OBJECT_ATTRIBUTE:
case OBJECT_TRIGGER:
+ case OBJECT_FOREIGN_TABLE:
{
Oid relid;
*************** ExecRenameStmt(RenameStmt *stmt)
*** 104,109 ****
--- 105,111 ----
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
case OBJECT_INDEX:
+ case OBJECT_FOREIGN_TABLE:
{
/*
* RENAME TABLE requires that we (still) hold
*************** ExecAlterObjectSchemaStmt(AlterObjectSch
*** 186,191 ****
--- 188,194 ----
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
+ case OBJECT_FOREIGN_TABLE:
CheckRelationOwnership(stmt->relation, true);
AlterTableNamespace(stmt->relation, stmt->newschema,
stmt->objectType, AccessExclusiveLock);
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 15464c3..13c76b7 100644
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
*************** analyze_rel(Oid relid, VacuumStmt *vacst
*** 187,193 ****
/* No need for a WARNING if we already complained during VACUUM */
if (!(vacstmt->options & VACOPT_VACUUM))
ereport(WARNING,
! (errmsg("skipping \"%s\" --- cannot analyze indexes, views, or special system tables",
RelationGetRelationName(onerel))));
relation_close(onerel, ShareUpdateExclusiveLock);
return;
--- 187,193 ----
/* No need for a WARNING if we already complained during VACUUM */
if (!(vacstmt->options & VACOPT_VACUUM))
ereport(WARNING,
! (errmsg("skipping \"%s\" --- cannot analyze indexes, views, foreign tables or special system tables",
RelationGetRelationName(onerel))));
relation_close(onerel, ShareUpdateExclusiveLock);
return;
*************** acquire_inherited_sample_rows(Relation o
*** 1458,1463 ****
--- 1458,1470 ----
continue;
}
+ /* Ignore child foreign tables */
+ if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ heap_close(childrel, AccessShareLock);
+ continue;
+ }
+
rels[nrels] = childrel;
relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
totalblocks += relblocks[nrels];
diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index b578818..c0731ef 100644
*** a/src/backend/commands/comment.c
--- b/src/backend/commands/comment.c
*************** CommentObject(CommentStmt *stmt)
*** 91,96 ****
--- 91,97 ----
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
+ case OBJECT_FOREIGN_TABLE:
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(relation));
*************** CheckAttributeComment(Relation relation)
*** 574,580 ****
RelationGetRelationName(relation));
/*
! * Allow comments only on columns of tables, views, and composite types
* (which are the only relkinds for which pg_dump will dump per-column
* comments). In particular we wish to disallow comments on index
* columns, because the naming of an index's columns may change across PG
--- 575,581 ----
RelationGetRelationName(relation));
/*
! * Allow comments only on columns of tables, views, composite types, and foreign tables
* (which are the only relkinds for which pg_dump will dump per-column
* comments). In particular we wish to disallow comments on index
* columns, because the naming of an index's columns may change across PG
*************** CheckAttributeComment(Relation relation)
*** 582,591 ****
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_VIEW &&
! relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view, or composite type",
RelationGetRelationName(relation))));
}
--- 583,593 ----
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_VIEW &&
! relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
! relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view, composite type, or foreign table",
RelationGetRelationName(relation))));
}
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index a5e44c0..7799482 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** ExplainNode(PlanState *planstate, List *
*** 705,710 ****
--- 705,713 ----
case T_WorkTableScan:
pname = sname = "WorkTable Scan";
break;
+ case T_ForeignScan:
+ pname = sname = "Foreign Scan";
+ break;
case T_Material:
pname = sname = "Materialize";
break;
*************** ExplainNode(PlanState *planstate, List *
*** 854,859 ****
--- 857,863 ----
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
+ case T_ForeignScan:
ExplainScanTarget((Scan *) plan, es);
break;
case T_BitmapIndexScan:
*************** ExplainNode(PlanState *planstate, List *
*** 1031,1036 ****
--- 1035,1041 ----
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
+ case T_ForeignScan:
case T_SubqueryScan:
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
break;
*************** ExplainScanTarget(Scan *plan, ExplainSta
*** 1568,1573 ****
--- 1573,1579 ----
case T_IndexScan:
case T_BitmapHeapScan:
case T_TidScan:
+ case T_ForeignScan:
/* Assert it's on a real relation */
Assert(rte->rtekind == RTE_RELATION);
objectname = get_rel_name(rte->relid);
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 4e6367c..926e007 100644
*** a/src/backend/commands/foreigncmds.c
--- b/src/backend/commands/foreigncmds.c
***************
*** 15,38 ****
#include "access/heapam.h"
#include "access/reloptions.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
#include "foreign/foreign.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
!
/*
* Convert a DefElem list to the text array format that is used in
--- 15,42 ----
#include "access/heapam.h"
#include "access/reloptions.h"
+ #include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
+ #include "catalog/pg_foreign_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
#include "foreign/foreign.h"
#include "miscadmin.h"
+ #include "nodes/execnodes.h"
#include "parser/parse_func.h"
#include "utils/acl.h"
#include "utils/builtins.h"
+ #include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
! #include "utils/tqual.h"
/*
* Convert a DefElem list to the text array format that is used in
*************** AlterForeignServerOwner(const char *name
*** 314,329 ****
* Convert a validator function name passed from the parser to an Oid.
*/
static Oid
! lookup_fdw_validator_func(List *validator)
{
Oid funcargtypes[2];
funcargtypes[0] = TEXTARRAYOID;
funcargtypes[1] = OIDOID;
! return LookupFuncName(validator, 2, funcargtypes, false);
/* return value is ignored, so we don't check the type */
}
/*
* Create a foreign-data wrapper
--- 318,386 ----
* Convert a validator function name passed from the parser to an Oid.
*/
static Oid
! lookup_fdw_validator_func(DefElem *validator)
{
Oid funcargtypes[2];
+ if (validator == NULL || validator->arg == NULL)
+ return InvalidOid;
+
funcargtypes[0] = TEXTARRAYOID;
funcargtypes[1] = OIDOID;
! return LookupFuncName((List *) validator->arg, 2, funcargtypes, false);
/* return value is ignored, so we don't check the type */
}
+ static Oid
+ lookup_fdw_handler_func(DefElem *handler)
+ {
+ Oid handlerOid;
+
+ if (handler == NULL || handler->arg == NULL)
+ return InvalidOid;
+
+ /* check that handler have correct return type */
+ handlerOid = LookupFuncName((List *) handler->arg, 0, NULL, false);
+ if (get_func_rettype(handlerOid) != FDW_HANDLEROID)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("function %s must return type \"fdw_handler\"",
+ NameListToString((List *) handler->arg))));
+ }
+
+ return handlerOid;
+ }
+
+ static void
+ parse_func_options(List *func_options, DefElem **validator, DefElem **handler)
+ {
+ ListCell *cell;
+
+ *validator = NULL;
+ *handler = NULL;
+ foreach (cell, func_options)
+ {
+ DefElem *def = lfirst(cell);
+
+ if (pg_strcasecmp(def->defname, "validator") == 0)
+ {
+ if (*validator)
+ elog(ERROR, "duplicated VALIDATOR");
+ *validator = def;
+ }
+ else if (pg_strcasecmp(def->defname, "handler") == 0)
+ {
+ if (*handler)
+ elog(ERROR, "duplicated HANDLER");
+ *handler = def;
+ }
+ else
+ {
+ elog(ERROR, "invalid option");
+ }
+ }
+ }
/*
* Create a foreign-data wrapper
*************** CreateForeignDataWrapper(CreateFdwStmt *
*** 336,342 ****
--- 393,402 ----
bool nulls[Natts_pg_foreign_data_wrapper];
HeapTuple tuple;
Oid fdwId;
+ DefElem *defvalidator;
+ DefElem *defhandler;
Oid fdwvalidator;
+ Oid fdwhandler;
Datum fdwoptions;
Oid ownerId;
*************** CreateForeignDataWrapper(CreateFdwStmt *
*** 372,383 ****
DirectFunctionCall1(namein, CStringGetDatum(stmt->fdwname));
values[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(ownerId);
! if (stmt->validator)
! fdwvalidator = lookup_fdw_validator_func(stmt->validator);
! else
! fdwvalidator = InvalidOid;
values[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = fdwvalidator;
nulls[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true;
--- 432,444 ----
DirectFunctionCall1(namein, CStringGetDatum(stmt->fdwname));
values[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(ownerId);
! /* determin which validator to be used (or not used at all) */
! parse_func_options(stmt->func_options, &defvalidator, &defhandler);
! fdwvalidator = lookup_fdw_validator_func(defvalidator);
! fdwhandler = lookup_fdw_handler_func(defhandler);
values[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = fdwvalidator;
+ values[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = fdwhandler;
nulls[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true;
*************** CreateForeignDataWrapper(CreateFdwStmt *
*** 413,418 ****
--- 474,494 ----
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
+ if (fdwhandler != InvalidOid)
+ {
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ myself.classId = ForeignDataWrapperRelationId;
+ myself.objectId = fdwId;
+ myself.objectSubId = 0;
+
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = fdwhandler;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
recordDependencyOnOwner(ForeignDataWrapperRelationId, fdwId, ownerId);
heap_close(rel, NoLock);
*************** AlterForeignDataWrapper(AlterFdwStmt *st
*** 433,439 ****
--- 509,518 ----
Oid fdwId;
bool isnull;
Datum datum;
+ DefElem *defvalidator;
+ DefElem *defhandler;
Oid fdwvalidator;
+ Oid fdwhandler;
/* Must be super user */
if (!superuser())
*************** AlterForeignDataWrapper(AlterFdwStmt *st
*** 457,465 ****
memset(repl_null, false, sizeof(repl_null));
memset(repl_repl, false, sizeof(repl_repl));
! if (stmt->change_validator)
{
! fdwvalidator = stmt->validator ? lookup_fdw_validator_func(stmt->validator) : InvalidOid;
repl_val[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = ObjectIdGetDatum(fdwvalidator);
repl_repl[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = true;
--- 536,546 ----
memset(repl_null, false, sizeof(repl_null));
memset(repl_repl, false, sizeof(repl_repl));
! parse_func_options(stmt->func_options, &defvalidator, &defhandler);
!
! if (defvalidator)
{
! fdwvalidator = lookup_fdw_validator_func(defvalidator);
repl_val[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = ObjectIdGetDatum(fdwvalidator);
repl_repl[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = true;
*************** AlterForeignDataWrapper(AlterFdwStmt *st
*** 467,473 ****
* It could be that the options for the FDW, SERVER and USER MAPPING
* are no longer valid with the new validator. Warn about this.
*/
! if (stmt->validator)
ereport(WARNING,
(errmsg("changing the foreign-data wrapper validator can cause "
"the options for dependent objects to become invalid")));
--- 548,554 ----
* It could be that the options for the FDW, SERVER and USER MAPPING
* are no longer valid with the new validator. Warn about this.
*/
! if (defvalidator->arg)
ereport(WARNING,
(errmsg("changing the foreign-data wrapper validator can cause "
"the options for dependent objects to become invalid")));
*************** AlterForeignDataWrapper(AlterFdwStmt *st
*** 485,490 ****
--- 566,599 ----
fdwvalidator = DatumGetObjectId(datum);
}
+ if (defhandler)
+ {
+ fdwhandler = lookup_fdw_handler_func(defhandler);
+ repl_val[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = ObjectIdGetDatum(fdwhandler);
+ repl_repl[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = true;
+
+ /*
+ * It could be that the behavior of accessing foreign table changes
+ * with the new handler. Warn about this.
+ */
+ if (defhandler->arg)
+ ereport(WARNING,
+ (errmsg("changing the foreign-data wrapper handler would change "
+ "the behavior of accessing foreign tables")));
+ }
+ else
+ {
+ /*
+ * Validator is not changed, but we need it for validating options.
+ */
+ datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
+ tp,
+ Anum_pg_foreign_data_wrapper_fdwhandler,
+ &isnull);
+ Assert(!isnull);
+ fdwhandler = DatumGetObjectId(datum);
+ }
+
/*
* Options specified, validate and update.
*/
*************** RemoveUserMappingById(Oid umId)
*** 1147,1149 ****
--- 1256,1523 ----
heap_close(rel, RowExclusiveLock);
}
+
+ /*
+ * Create a foreign table
+ * call after DefineRelation().
+ */
+ void
+ CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
+ {
+ Relation ftrel;
+ Datum ftoptions;
+ Datum values[Natts_pg_foreign_table];
+ bool nulls[Natts_pg_foreign_table];
+ HeapTuple tuple;
+ Oid ftId;
+ AclResult aclresult;
+ ObjectAddress myself;
+ ObjectAddress referenced;
+ Oid ownerId;
+ ForeignDataWrapper *fdw;
+ ForeignServer *server;
+ ListCell *lc;
+
+ /*
+ * Advance command counter to ensure the pg_attribute tuple visible; the
+ * tuple might be updated to add default value or constraints in previous
+ * step.
+ */
+ CommandCounterIncrement();
+
+ /*
+ * For now the owner cannot be specified on create. Use effective user ID.
+ */
+ ownerId = GetUserId();
+
+ /*
+ * Check that the foreign server exists and that we have USAGE on it. Also
+ * get the actual FDW for option validation etc.
+ */
+ server = GetForeignServerByName(stmt->servername, false);
+ aclresult = pg_foreign_server_aclcheck(server->serverid, ownerId, ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, server->servername);
+
+ fdw = GetForeignDataWrapper(server->fdwid);
+
+ aclresult = pg_foreign_data_wrapper_aclcheck(fdw->fdwid, ownerId, ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname);
+
+ /*
+ * Insert tuple into pg_foreign_table.
+ */
+ ftrel = heap_open(ForeignTableRelationId, RowExclusiveLock);
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ values[Anum_pg_foreign_table_ftrelid - 1] = ObjectIdGetDatum(relid);
+ values[Anum_pg_foreign_table_ftserver - 1] = ObjectIdGetDatum(server->serverid);
+ /* Add table generic options */
+ ftoptions = transformGenericOptions(ForeignTableRelationId,
+ PointerGetDatum(NULL),
+ stmt->options,
+ fdw->fdwvalidator);
+
+ if (PointerIsValid(DatumGetPointer(ftoptions)))
+ values[Anum_pg_foreign_table_ftoptions - 1] = ftoptions;
+ else
+ nulls[Anum_pg_foreign_table_ftoptions - 1] = true;
+
+ tuple = heap_form_tuple(ftrel->rd_att, values, nulls);
+
+ /* pg_foreign_table don't have OID */
+ ftId = simple_heap_insert(ftrel, tuple);
+
+ CatalogUpdateIndexes(ftrel, tuple);
+
+ heap_freetuple(tuple);
+
+ /* Add pg_class dependency on the server */
+ myself.classId = RelationRelationId;
+ myself.objectId = relid;
+ myself.objectSubId = 0;
+
+ referenced.classId = ForeignServerRelationId;
+ referenced.objectId = server->serverid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ /* Advance command counter to ensure the pg_foreign_table tuple visible */
+ CommandCounterIncrement();
+
+ /* Set per-column generic options in pg_attribute */
+ foreach (lc, stmt->base.tableElts)
+ {
+ ColumnDef *coldef = lfirst(lc);
+
+ AlterColumnGenericOptions(relid, coldef->colname, coldef->genoptions);
+ }
+
+ heap_close(ftrel, NoLock);
+ }
+
+ /*
+ * Modify generic options of the column which is identified by relid and
+ * colname.
+ */
+ void
+ AlterColumnGenericOptions(Oid relid, const char *colname, List *options)
+ {
+ Relation rel; /* the foreign table itself */
+ Relation attrelation; /* pg_attribute relation */
+ HeapTuple tuple;
+ HeapTuple newtuple;
+ Form_pg_attribute attrtuple;
+ Datum oldopt;
+ Datum newopt;
+ bool isnull;
+ Datum repl_val[Natts_pg_attribute];
+ bool repl_null[Natts_pg_attribute];
+ bool repl_repl[Natts_pg_attribute];
+
+ if (options == NIL)
+ return;
+
+ /*
+ * Open target relation for only its name. Appropriate lock should be held
+ * already.
+ */
+ rel = heap_open(relid, NoLock);
+
+ /* Open pg_attribute anyway, even if it had been opened already. */
+ attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheAttName(relid, colname);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ colname, RelationGetRelationName(rel))));
+ attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
+ if (attrtuple->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot alter system column \"%s\"",
+ colname)));
+
+ /* Generate new proposed generic options */
+ oldopt = SysCacheGetAttr(ATTNAME, tuple,
+ Anum_pg_attribute_attgenoptions, &isnull);
+ newopt = transformGenericOptions(AttributeRelationId,
+ isnull ? PointerGetDatum(0) : oldopt,
+ options,
+ GetFdwValidator(relid));
+
+ /* build new tuple */
+ memset(repl_null, false, sizeof(repl_null));
+ memset(repl_repl, false, sizeof(repl_repl));
+ if (newopt != (Datum) 0)
+ repl_val[Anum_pg_attribute_attgenoptions - 1] = newopt;
+ else
+ repl_null[Anum_pg_attribute_attgenoptions - 1] = true;
+ repl_repl[Anum_pg_attribute_attgenoptions - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrelation),
+ repl_val, repl_null, repl_repl);
+ ReleaseSysCache(tuple);
+
+ /* Update system catalog */
+ simple_heap_update(attrelation, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(attrelation, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(attrelation, NoLock);
+ heap_close(rel, NoLock);
+ }
+
+
+ /*
+ * ALTER FOREIGN TABLE OPTIONS (...)
+ */
+ void
+ ATExecGenericOptions(Relation rel, List *options)
+ {
+ Relation ftrel;
+ ForeignTable *table;
+ ForeignServer *server;
+ ForeignDataWrapper *fdw;
+ HeapTuple tuple;
+ bool isnull;
+ Datum repl_val[Natts_pg_foreign_table];
+ bool repl_null[Natts_pg_foreign_table];
+ bool repl_repl[Natts_pg_foreign_table];
+ Datum datum;
+
+ if (options == NIL)
+ return;
+
+ tuple = SearchSysCacheCopy1(FOREIGNTABLEREL, rel->rd_id);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("foreign table \"%s\" does not exist",
+ RelationGetRelationName(rel))));
+
+ table = GetForeignTable(rel->rd_id);
+ server = GetForeignServer(table->serverid);
+ fdw = GetForeignDataWrapper(server->fdwid);
+
+ memset(repl_val, 0, sizeof(repl_val));
+ memset(repl_null, false, sizeof(repl_null));
+ memset(repl_repl, false, sizeof(repl_repl));
+
+ /* Extract the current options */
+ datum = SysCacheGetAttr(FOREIGNTABLEREL,
+ tuple,
+ Anum_pg_foreign_table_ftoptions,
+ &isnull);
+ if (isnull)
+ datum = PointerGetDatum(NULL);
+
+ /* Transform the options */
+ datum = transformGenericOptions(ForeignTableRelationId,
+ datum,
+ options,
+ fdw->fdwvalidator);
+
+ if (PointerIsValid(DatumGetPointer(datum)))
+ repl_val[Anum_pg_foreign_table_ftoptions - 1] = datum;
+ else
+ repl_null[Anum_pg_foreign_table_ftoptions - 1] = true;
+
+ repl_repl[Anum_pg_foreign_table_ftoptions - 1] = true;
+
+ /* Everything looks good - update the tuple */
+
+ ftrel = heap_open(ForeignTableRelationId, RowExclusiveLock);
+
+ tuple = heap_modify_tuple(tuple, RelationGetDescr(ftrel),
+ repl_val, repl_null, repl_repl);
+
+ simple_heap_update(ftrel, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(ftrel, tuple);
+
+ heap_close(ftrel, RowExclusiveLock);
+
+ heap_freetuple(tuple);
+ }
+
+ /*
+ * ALTER FOREIGN TABLE ALTER COLUMN colname OPTIONS (...)
+ */
+ void
+ ATExecColumnGenericOptions(Relation rel, const char *colname, List *options)
+ {
+ if (options == NIL)
+ return;
+
+ /*
+ * Advance command counter to ensure the pg_attribute tuple visible; the
+ * tuple might be updated to add default value or constraints in previous
+ * step.
+ */
+ CommandCounterIncrement();
+
+ AlterColumnGenericOptions(rel->rd_id, colname, options);
+ }
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 35fc1b3..5b4d5e2 100644
*** a/src/backend/commands/lockcmds.c
--- b/src/backend/commands/lockcmds.c
*************** LockTableRecurse(Oid reloid, RangeVar *r
*** 142,152 ****
aclcheck_error(aclresult, ACL_KIND_CLASS,
RelationGetRelationName(rel));
! /* Currently, we only allow plain tables to be locked */
! if (rel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table",
RelationGetRelationName(rel))));
/*
--- 142,153 ----
aclcheck_error(aclresult, ACL_KIND_CLASS,
RelationGetRelationName(rel));
! /* Currently, we only allow plain tables and foreign tables to be locked */
! if (rel->rd_rel->relkind != RELKIND_RELATION &&
! rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table or foreign table",
RelationGetRelationName(rel))));
/*
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 762bbae..98f7e18 100644
*** a/src/backend/commands/seclabel.c
--- b/src/backend/commands/seclabel.c
*************** ExecSecLabelStmt(SecLabelStmt *stmt)
*** 103,108 ****
--- 103,109 ----
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
+ case OBJECT_FOREIGN_TABLE:
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(relation));
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index bb8ebce..2b9a686 100644
*** a/src/backend/commands/sequence.c
--- b/src/backend/commands/sequence.c
*************** DefineSequence(CreateSeqStmt *seq)
*** 136,141 ****
--- 136,142 ----
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
coldef->constraints = NIL;
+ coldef->genoptions = NIL;
null[i - 1] = false;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e8808e2..0d2aeea 100644
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 28,33 ****
--- 28,34 ----
#include "catalog/namespace.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
+ #include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
*************** static const struct dropmsgstrings dropm
*** 218,223 ****
--- 219,230 ----
gettext_noop("type \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a type"),
gettext_noop("Use DROP TYPE to remove a type.")},
+ {RELKIND_FOREIGN_TABLE,
+ ERRCODE_UNDEFINED_OBJECT,
+ gettext_noop("foreign table \"%s\" does not exist"),
+ gettext_noop("foreign table \"%s\" does not exist, skipping"),
+ gettext_noop("\"%s\" is not a foreign table"),
+ gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table.")},
{'\0', 0, NULL, NULL, NULL, NULL}
};
*************** static void ATExecCmd(List **wqueue, Alt
*** 263,269 ****
static void ATRewriteTables(List **wqueue, LOCKMODE lockmode);
static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
! static void ATSimplePermissions(Relation rel, bool allowView, bool allowType);
static void ATSimplePermissionsRelationOrIndex(Relation rel);
static void ATSimpleRecursion(List **wqueue, Relation rel,
AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
--- 270,276 ----
static void ATRewriteTables(List **wqueue, LOCKMODE lockmode);
static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
! static void ATSimplePermissions(Relation rel, bool allowView, bool allowType, bool allowForeignTable);
static void ATSimplePermissionsRelationOrIndex(Relation rel);
static void ATSimpleRecursion(List **wqueue, Relation rel,
AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
*************** DropErrorMsgWrongType(const char *relnam
*** 671,677 ****
/*
* RemoveRelations
! * Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW
*/
void
RemoveRelations(DropStmt *drop)
--- 678,685 ----
/*
* RemoveRelations
! * Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
! * DROP FOREIGN TABLE
*/
void
RemoveRelations(DropStmt *drop)
*************** RemoveRelations(DropStmt *drop)
*** 705,710 ****
--- 713,722 ----
relkind = RELKIND_VIEW;
break;
+ case OBJECT_FOREIGN_TABLE:
+ relkind = RELKIND_FOREIGN_TABLE;
+ break;
+
default:
elog(ERROR, "unrecognized drop object type: %d",
(int) drop->removeType);
*************** MergeAttributes(List *schema, List *supe
*** 1438,1443 ****
--- 1450,1456 ----
def->raw_default = NULL;
def->cooked_default = NULL;
def->constraints = NIL;
+ def->genoptions = NIL;
inhSchema = lappend(inhSchema, def);
newattno[parent_attno - 1] = ++child_attno;
}
*************** renameatt_internal(Oid myrelid,
*** 1986,1995 ****
if (relkind != RELKIND_RELATION &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
! relkind != RELKIND_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view, composite type or index",
RelationGetRelationName(targetrelation))));
/*
--- 1999,2009 ----
if (relkind != RELKIND_RELATION &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
! relkind != RELKIND_INDEX &&
! relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view, composite type,index or foreign table",
RelationGetRelationName(targetrelation))));
/*
*************** AlterTable(AlterTableStmt *stmt)
*** 2436,2441 ****
--- 2450,2463 ----
RelationGetRelationName(rel))));
break;
+ case OBJECT_FOREIGN_TABLE:
+ if (rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a foreign table",
+ RelationGetRelationName(rel))));
+ break;
+
default:
elog(ERROR, "unrecognized object type: %d", (int) stmt->relkind);
}
*************** AlterTableGetLockLevel(List *cmds)
*** 2521,2526 ****
--- 2543,2550 ----
case AT_SetTableSpace: /* must rewrite heap */
case AT_DropNotNull: /* may change some SQL plans */
case AT_SetNotNull:
+ case AT_GenericOptions:
+ case AT_ColumnGenericOptions:
cmd_lockmode = AccessExclusiveLock;
break;
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 2679,2692 ****
switch (cmd->subtype)
{
case AT_AddColumn: /* ADD COLUMN */
! ATSimplePermissions(rel, false, true);
/* Performs own recursion */
ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
pass = AT_PASS_ADD_COL;
break;
case AT_AddColumnToView: /* add column via CREATE OR REPLACE
* VIEW */
! ATSimplePermissions(rel, true, false);
/* Performs own recursion */
ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
pass = AT_PASS_ADD_COL;
--- 2703,2716 ----
switch (cmd->subtype)
{
case AT_AddColumn: /* ADD COLUMN */
! ATSimplePermissions(rel, false, true, true);
/* Performs own recursion */
ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
pass = AT_PASS_ADD_COL;
break;
case AT_AddColumnToView: /* add column via CREATE OR REPLACE
* VIEW */
! ATSimplePermissions(rel, true, false, false);
/* Performs own recursion */
ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
pass = AT_PASS_ADD_COL;
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 2699,2717 ****
* substitutes default values into INSERTs before it expands
* rules.
*/
! ATSimplePermissions(rel, true, false);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
! ATSimplePermissions(rel, false, false);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_DROP;
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
! ATSimplePermissions(rel, false, false);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR;
--- 2723,2741 ----
* substitutes default values into INSERTs before it expands
* rules.
*/
! ATSimplePermissions(rel, true, false, true);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
! ATSimplePermissions(rel, false, false, true);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_DROP;
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
! ATSimplePermissions(rel, false, false, true);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR;
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 2729,2753 ****
pass = AT_PASS_MISC;
break;
case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
! ATSimplePermissions(rel, false, false);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
case AT_DropColumn: /* DROP COLUMN */
! ATSimplePermissions(rel, false, true);
ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
/* Recursion occurs during execution phase */
pass = AT_PASS_DROP;
break;
case AT_AddIndex: /* ADD INDEX */
! ATSimplePermissions(rel, false, false);
/* This command never recurses */
/* No command-specific prep needed */
pass = AT_PASS_ADD_INDEX;
break;
case AT_AddConstraint: /* ADD CONSTRAINT */
! ATSimplePermissions(rel, false, false);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
--- 2753,2777 ----
pass = AT_PASS_MISC;
break;
case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
! ATSimplePermissions(rel, false, false, false);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
case AT_DropColumn: /* DROP COLUMN */
! ATSimplePermissions(rel, false, true, true);
ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
/* Recursion occurs during execution phase */
pass = AT_PASS_DROP;
break;
case AT_AddIndex: /* ADD INDEX */
! ATSimplePermissions(rel, false, false, false);
/* This command never recurses */
/* No command-specific prep needed */
pass = AT_PASS_ADD_INDEX;
break;
case AT_AddConstraint: /* ADD CONSTRAINT */
! ATSimplePermissions(rel, false, false, true);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 2755,2761 ****
pass = AT_PASS_ADD_CONSTR;
break;
case AT_DropConstraint: /* DROP CONSTRAINT */
! ATSimplePermissions(rel, false, false);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
--- 2779,2785 ----
pass = AT_PASS_ADD_CONSTR;
break;
case AT_DropConstraint: /* DROP CONSTRAINT */
! ATSimplePermissions(rel, false, false, true);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 2763,2769 ****
pass = AT_PASS_DROP;
break;
case AT_AlterColumnType: /* ALTER COLUMN TYPE */
! ATSimplePermissions(rel, false, true);
/* Performs own recursion */
ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode);
pass = AT_PASS_ALTER_TYPE;
--- 2787,2793 ----
pass = AT_PASS_DROP;
break;
case AT_AlterColumnType: /* ALTER COLUMN TYPE */
! ATSimplePermissions(rel, false, true, true);
/* Performs own recursion */
ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode);
pass = AT_PASS_ALTER_TYPE;
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 2775,2794 ****
break;
case AT_ClusterOn: /* CLUSTER ON */
case AT_DropCluster: /* SET WITHOUT CLUSTER */
! ATSimplePermissions(rel, false, false);
/* These commands never recurse */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
case AT_AddOids: /* SET WITH OIDS */
! ATSimplePermissions(rel, false, false);
/* Performs own recursion */
if (!rel->rd_rel->relhasoids || recursing)
ATPrepAddOids(wqueue, rel, recurse, cmd, lockmode);
pass = AT_PASS_ADD_COL;
break;
case AT_DropOids: /* SET WITHOUT OIDS */
! ATSimplePermissions(rel, false, false);
/* Performs own recursion */
if (rel->rd_rel->relhasoids)
{
--- 2799,2818 ----
break;
case AT_ClusterOn: /* CLUSTER ON */
case AT_DropCluster: /* SET WITHOUT CLUSTER */
! ATSimplePermissions(rel, false, false, false);
/* These commands never recurse */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
case AT_AddOids: /* SET WITH OIDS */
! ATSimplePermissions(rel, false, false, false);
/* Performs own recursion */
if (!rel->rd_rel->relhasoids || recursing)
ATPrepAddOids(wqueue, rel, recurse, cmd, lockmode);
pass = AT_PASS_ADD_COL;
break;
case AT_DropOids: /* SET WITHOUT OIDS */
! ATSimplePermissions(rel, false, false, false);
/* Performs own recursion */
if (rel->rd_rel->relhasoids)
{
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 2815,2821 ****
pass = AT_PASS_MISC;
break;
case AT_AddInherit: /* INHERIT */
! ATSimplePermissions(rel, false, false);
/* This command never recurses */
ATPrepAddInherit(rel);
pass = AT_PASS_MISC;
--- 2839,2845 ----
pass = AT_PASS_MISC;
break;
case AT_AddInherit: /* INHERIT */
! ATSimplePermissions(rel, false, false, true);
/* This command never recurses */
ATPrepAddInherit(rel);
pass = AT_PASS_MISC;
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 2828,2839 ****
case AT_DisableTrig: /* DISABLE TRIGGER variants */
case AT_DisableTrigAll:
case AT_DisableTrigUser:
case AT_EnableRule: /* ENABLE/DISABLE RULE variants */
case AT_EnableAlwaysRule:
case AT_EnableReplicaRule:
case AT_DisableRule:
case AT_DropInherit: /* NO INHERIT */
! ATSimplePermissions(rel, false, false);
/* These commands never recurse */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
--- 2852,2870 ----
case AT_DisableTrig: /* DISABLE TRIGGER variants */
case AT_DisableTrigAll:
case AT_DisableTrigUser:
+ ATSimplePermissions(rel, false, false, false);
+ /* These commands never recurse */
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
case AT_EnableRule: /* ENABLE/DISABLE RULE variants */
case AT_EnableAlwaysRule:
case AT_EnableReplicaRule:
case AT_DisableRule:
case AT_DropInherit: /* NO INHERIT */
! case AT_GenericOptions:
! case AT_ColumnGenericOptions:
! ATSimplePermissions(rel, false, false, true);
/* These commands never recurse */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
*************** ATExecCmd(List **wqueue, AlteredTableInf
*** 3080,3085 ****
--- 3111,3122 ----
case AT_DropInherit:
ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
break;
+ case AT_GenericOptions:
+ ATExecGenericOptions(rel, (List *) cmd->def);
+ break;
+ case AT_ColumnGenericOptions:
+ ATExecColumnGenericOptions(rel, cmd->name, (List *) cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
*************** ATRewriteTables(List **wqueue, LOCKMODE
*** 3106,3111 ****
--- 3143,3152 ----
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
+ /* If it's a foreign table, no need to rewrite it. */
+ if (tab->relkind == RELKIND_FOREIGN_TABLE)
+ continue;
+
/*
* We only need to rewrite the table if at least one column needs to
* be recomputed, or we are adding/removing the OID column.
*************** ATGetQueueEntry(List **wqueue, Relation
*** 3559,3594 ****
* - Ensure that it is not a system table
*/
static void
! ATSimplePermissions(Relation rel, bool allowView, bool allowType)
{
if (rel->rd_rel->relkind != RELKIND_RELATION)
{
! if (allowView)
! {
! if (rel->rd_rel->relkind != RELKIND_VIEW)
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table or view",
! RelationGetRelationName(rel))));
! }
! else if (allowType)
{
- if (rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table or composite type",
- RelationGetRelationName(rel))));
- }
- else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table",
! RelationGetRelationName(rel))));
}
/* Permissions checks */
if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(rel));
if (!allowSystemTableMods && IsSystemRelation(rel))
--- 3600,3636 ----
* - Ensure that it is not a system table
*/
static void
! ATSimplePermissions(Relation rel, bool allowView, bool allowType, bool allowForeignTable)
{
+ int relkind = rel->rd_rel->relkind;
+
if (rel->rd_rel->relkind != RELKIND_RELATION)
{
! StringInfoData allowed;
!
! initStringInfo(&allowed);
! appendStringInfo(&allowed, "table%s%s%s",
! allowView ? " or view" : "",
! allowType ? " or composite type" : "",
! allowForeignTable ? " or foreign table" : "");
!
! if ((relkind == RELKIND_VIEW && !allowView) ||
! (relkind == RELKIND_COMPOSITE_TYPE && !allowType) ||
! (relkind == RELKIND_FOREIGN_TABLE && !allowForeignTable))
{
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a %s",
! RelationGetRelationName(rel), allowed.data)));
! }
! pfree(allowed.data);
}
/* Permissions checks */
if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
! aclcheck_error(ACLCHECK_NOT_OWNER,
! relkind == RELKIND_FOREIGN_TABLE ?
! ACL_KIND_FOREIGN_TABLE : ACL_KIND_CLASS,
RelationGetRelationName(rel));
if (!allowSystemTableMods && IsSystemRelation(rel))
*************** ATExecAddColumn(AlteredTableInfo *tab, R
*** 4178,4183 ****
--- 4220,4237 ----
}
/*
+ * Store the generic options, if any, in the catalogs
+ */
+ if (colDef->genoptions != NIL)
+ {
+ AlterColumnGenericOptions(rel->rd_id, colDef->colname,
+ colDef->genoptions);
+
+ /* Make the additional catalog changes visible */
+ CommandCounterIncrement();
+ }
+
+ /*
* If we are adding an OID column, we have to tell Phase 3 to rewrite the
* table to fix that.
*/
*************** ATExecDropColumn(List **wqueue, Relation
*** 4683,4689 ****
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, false, true);
/*
* get the number of the attribute
--- 4737,4743 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, false, true, true);
/*
* get the number of the attribute
*************** ATAddCheckConstraint(List **wqueue, Alte
*** 4987,4993 ****
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, false, false);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
--- 5041,5047 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, false, false, true);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
*************** ATExecDropConstraint(Relation rel, const
*** 5930,5936 ****
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, false, false);
conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
--- 5984,5990 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, false, false, true);
conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
*************** ATExecChangeOwner(Oid relationOid, Oid n
*** 6817,6822 ****
--- 6871,6877 ----
{
case RELKIND_RELATION:
case RELKIND_VIEW:
+ case RELKIND_FOREIGN_TABLE:
/* ok to change owner */
break;
case RELKIND_INDEX:
*************** ATExecChangeOwner(Oid relationOid, Oid n
*** 6873,6879 ****
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view, or sequence",
NameStr(tuple_class->relname))));
}
--- 6928,6934 ----
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view, sequence, or foreign tabl, or foreign tablee",
NameStr(tuple_class->relname))));
}
*************** ATExecAddInherit(Relation child_rel, Ran
*** 7530,7536 ****
* Must be owner of both parent and child -- child was checked by
* ATSimplePermissions call in ATPrepCmd
*/
! ATSimplePermissions(parent_rel, false, false);
/* Permanent rels cannot inherit from temporary ones */
if (parent_rel->rd_istemp && !child_rel->rd_istemp)
--- 7585,7591 ----
* Must be owner of both parent and child -- child was checked by
* ATSimplePermissions call in ATPrepCmd
*/
! ATSimplePermissions(parent_rel, false, false, true);
/* Permanent rels cannot inherit from temporary ones */
if (parent_rel->rd_istemp && !child_rel->rd_istemp)
*************** AlterTableNamespace(RangeVar *relation,
*** 8135,8140 ****
--- 8190,8203 ----
RelationGetRelationName(rel))));
break;
+ case OBJECT_FOREIGN_TABLE:
+ if (rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a foreign table",
+ RelationGetRelationName(rel))));
+ break;
+
default:
elog(ERROR, "unrecognized object type: %d", (int) stmttype);
}
*************** AlterTableNamespace(RangeVar *relation,
*** 8144,8149 ****
--- 8207,8213 ----
{
case RELKIND_RELATION:
case RELKIND_VIEW:
+ case RELKIND_FOREIGN_TABLE:
/* ok to change schema */
break;
case RELKIND_SEQUENCE:
*************** AlterTableNamespace(RangeVar *relation,
*** 8174,8180 ****
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view, or sequence",
RelationGetRelationName(rel))));
}
--- 8238,8244 ----
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view, sequence, or foreign table",
RelationGetRelationName(rel))));
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 2f68df4..143ec79 100644
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
*************** vacuum_rel(Oid relid, VacuumStmt *vacstm
*** 894,900 ****
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
ereport(WARNING,
! (errmsg("skipping \"%s\" --- cannot vacuum indexes, views, or special system tables",
RelationGetRelationName(onerel))));
relation_close(onerel, lmode);
PopActiveSnapshot();
--- 894,900 ----
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
ereport(WARNING,
! (errmsg("skipping \"%s\" --- cannot vacuum indexes, views, foreign tables or special system tables",
RelationGetRelationName(onerel))));
relation_close(onerel, lmode);
PopActiveSnapshot();
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 09ab24b..976401d 100644
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
*************** DefineVirtualRelation(const RangeVar *re
*** 128,133 ****
--- 128,134 ----
def->raw_default = NULL;
def->cooked_default = NULL;
def->constraints = NIL;
+ def->genoptions = NIL;
attrList = lappend(attrList, def);
}
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index da4fbb4..a854c9a 100644
*** a/src/backend/executor/Makefile
--- b/src/backend/executor/Makefile
*************** OBJS = execAmi.o execCurrent.o execGroup
*** 23,28 ****
nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
! nodeWindowAgg.o tstoreReceiver.o spi.o
include $(top_srcdir)/src/backend/common.mk
--- 23,28 ----
nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
! nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o spi.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index d999a44..67b64d7 100644
*** a/src/backend/executor/execAmi.c
--- b/src/backend/executor/execAmi.c
***************
*** 22,27 ****
--- 22,28 ----
#include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h"
#include "executor/nodeFunctionscan.h"
+ #include "executor/nodeForeignscan.h"
#include "executor/nodeGroup.h"
#include "executor/nodeGroup.h"
#include "executor/nodeHash.h"
*************** ExecReScan(PlanState *node)
*** 186,191 ****
--- 187,196 ----
ExecReScanWorkTableScan((WorkTableScanState *) node);
break;
+ case T_ForeignScanState:
+ ExecForeignReScan((ForeignScanState *) node);
+ break;
+
case T_NestLoopState:
ExecReScanNestLoop((NestLoopState *) node);
break;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 69f3a28..4d801c4 100644
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
*************** InitResultRelInfo(ResultRelInfo *resultR
*** 934,939 ****
--- 934,945 ----
break;
}
break;
+ case RELKIND_FOREIGN_TABLE:
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change foreign table \"%s\"",
+ RelationGetRelationName(resultRelationDesc))));
+ break;
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index edd175e..5ba828d 100644
*** a/src/backend/executor/execProcnode.c
--- b/src/backend/executor/execProcnode.c
***************
*** 86,91 ****
--- 86,92 ----
#include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h"
#include "executor/nodeFunctionscan.h"
+ #include "executor/nodeForeignscan.h"
#include "executor/nodeGroup.h"
#include "executor/nodeHash.h"
#include "executor/nodeHashjoin.h"
*************** ExecInitNode(Plan *node, EState *estate,
*** 232,237 ****
--- 233,243 ----
estate, eflags);
break;
+ case T_ForeignScan:
+ result = (PlanState *) ExecInitForeignScan((ForeignScan *) node,
+ estate, eflags);
+ break;
+
/*
* join nodes
*/
*************** ExecProcNode(PlanState *node)
*** 422,427 ****
--- 428,437 ----
result = ExecWorkTableScan((WorkTableScanState *) node);
break;
+ case T_ForeignScanState:
+ result = ExecForeignScan((ForeignScanState *) node);
+ break;
+
/*
* join nodes
*/
*************** ExecEndNode(PlanState *node)
*** 650,655 ****
--- 660,669 ----
ExecEndWorkTableScan((WorkTableScanState *) node);
break;
+ case T_ForeignScanState:
+ ExecEndForeignScan((ForeignScanState *) node);
+ break;
+
/*
* join nodes
*/
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index ...77c108c .
*** a/src/backend/executor/nodeForeignscan.c
--- b/src/backend/executor/nodeForeignscan.c
***************
*** 0 ****
--- 1,275 ----
+ /*-------------------------------------------------------------------------
+ *
+ * nodeForeignscan.c
+ * Support routines for sequential scans of foreign tables.
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+ /*
+ * INTERFACE ROUTINES
+ * ExecForeignScan sequentially scans a foreign table.
+ * ExecForeignNext retrieve next tuple in sequential order.
+ * ExecInitForeignScan creates and initializes a seqscan node.
+ * ExecEndForeignScan releases any storage allocated.
+ * ExecForeignReScan rescans the foreign table
+ * ExecForeignMarkPos marks scan position
+ * ExecForeignRestrPos restores scan position
+ */
+ #include "postgres.h"
+
+ #include "executor/executor.h"
+ #include "executor/nodeForeignscan.h"
+ #include "foreign/foreign.h"
+ #include "miscadmin.h"
+
+ static TupleTableSlot *ForeignNext(ForeignScanState *node);
+ static bool ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot);
+
+ /* ----------------------------------------------------------------
+ * Scan Support
+ * ----------------------------------------------------------------
+ */
+
+ /* ----------------------------------------------------------------
+ * ForeignNext
+ *
+ * This is a workhorse for ExecForeignScan
+ * ----------------------------------------------------------------
+ */
+ static TupleTableSlot *
+ ForeignNext(ForeignScanState *node)
+ {
+ TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+
+ Assert(node->ss.ps.state->es_direction == ForwardScanDirection);
+
+ /* tupleslot will be filled by Iterate. */
+ if (node->routine->Iterate == NULL)
+ ereport(ERROR,
+ (errmsg("foreign-data wrapper must support Iterate to scan foreign table")));
+ node->routine->Iterate(node);
+
+ /* Set tableoid if the tuple was valid. */
+ if (HeapTupleIsValid(slot->tts_tuple))
+ {
+ /*
+ * If the foreign-data wrapper returned a MinimalTuple, materialize the
+ * tuple to store system attributes.
+ */
+ if (!TTS_HAS_PHYSICAL_TUPLE(slot))
+ ExecMaterializeSlot(slot);
+
+ /* overwrite only tableoid of the tuple */
+ slot->tts_tuple->t_tableOid =
+ RelationGetRelid(node->ss.ss_currentRelation);
+ }
+
+ return slot;
+ }
+
+ /*
+ * ForeignRecheck -- access method routine to recheck a tuple in EvalPlanQual
+ */
+ static bool
+ ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot)
+ {
+ /* ForeignScan never use keys in ForeignNext. */
+ return true;
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecForeignScan(node)
+ *
+ * Scans the relation sequentially and returns the next qualifying
+ * tuple.
+ * We call the ExecScan() routine and pass it the appropriate
+ * access method functions.
+ * ----------------------------------------------------------------
+ */
+ TupleTableSlot *
+ ExecForeignScan(ForeignScanState *node)
+ {
+ return ExecScan((ScanState *) node,
+ (ExecScanAccessMtd) ForeignNext,
+ (ExecScanRecheckMtd) ForeignRecheck);
+ }
+
+
+ /* ----------------------------------------------------------------
+ * ExecInitForeignScan
+ * ----------------------------------------------------------------
+ */
+ ForeignScanState *
+ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
+ {
+ ForeignScanState *scanstate;
+ Relation currentRelation;
+ Oid userid;
+ FdwRoutine *routine;
+
+ /*
+ * foreign scan has no child node.
+ * but not any more.
+ */
+ Assert(outerPlan(node) == NULL);
+ Assert(innerPlan(node) == NULL);
+
+ /*
+ * create state structure
+ */
+ scanstate = makeNode(ForeignScanState);
+ scanstate->ss.ps.plan = (Plan *) node;
+ scanstate->ss.ps.state = estate;
+
+ /*
+ * Miscellaneous initialization
+ *
+ * create expression context for node
+ */
+ ExecAssignExprContext(estate, &scanstate->ss.ps);
+
+ /*
+ * initialize child expressions
+ */
+ scanstate->ss.ps.targetlist = (List *)
+ ExecInitExpr((Expr *) node->scan.plan.targetlist,
+ (PlanState *) scanstate);
+ scanstate->ss.ps.qual = (List *)
+ ExecInitExpr((Expr *) node->scan.plan.qual,
+ (PlanState *) scanstate);
+
+ /*
+ * tuple table initialization
+ */
+ ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
+ ExecInitScanTupleSlot(estate, &scanstate->ss);
+
+ /*
+ * initialize scan relation. get the relation object id from the
+ * relid'th entry in the range table, open that relation and acquire
+ * appropriate lock on it.
+ */
+ currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid);
+ scanstate->ss.ss_currentRelation = currentRelation;
+ ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+ scanstate->ss.ps.ps_TupFromTlist = false;
+
+ /*
+ * Initialize result tuple type and projection info.
+ */
+ ExecAssignResultTypeFromTL(&scanstate->ss.ps);
+ ExecAssignScanProjectionInfo(&scanstate->ss);
+
+ /* Initialize forein-data wrapper specific data. */
+ userid = GetOuterUserId();
+ scanstate->table = GetForeignTable(RelationGetRelid(currentRelation));
+ scanstate->server = GetForeignServer(scanstate->table->serverid);
+ scanstate->wrapper = GetForeignDataWrapper(scanstate->server->fdwid);
+ scanstate->user = GetUserMapping(userid, scanstate->server->serverid);
+ routine = GetFdwRoutine(scanstate->wrapper->fdwhandler);
+ scanstate->routine = routine;
+
+ /* Initialize the scan */
+ if (routine->Open != NULL)
+ routine->Open(scanstate);
+
+ /*
+ * If this execution was not for EXPLAIN w/o ANALYZE flag, initiate the
+ * foreign scan.
+ */
+ if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
+ {
+ /* connect to the foreign server and prepare to execute scan */
+ if (routine->ConnectServer != NULL)
+ scanstate->conn = routine->ConnectServer(scanstate->server,
+ scanstate->user);
+ if (routine->BeginScan != NULL)
+ routine->BeginScan(scanstate);
+ }
+
+ return scanstate;
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecEndForeignScan
+ *
+ * frees any storage allocated through C routines.
+ * ----------------------------------------------------------------
+ */
+ void
+ ExecEndForeignScan(ForeignScanState *node)
+ {
+ Relation relation;
+
+ /* close the scan */
+ if (node->routine->Close != NULL)
+ node->routine->Close(node);
+
+ /* close the foreign connection for this scan node */
+ if (node->routine->FreeFSConnection != NULL)
+ node->routine->FreeFSConnection(node->conn);
+
+ /* get information from node */
+ relation = node->ss.ss_currentRelation;
+
+ /* Free the exprcontext */
+ ExecFreeExprContext(&node->ss.ps);
+
+ /* clean out the tuple table */
+ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+ ExecClearTuple(node->ss.ss_ScanTupleSlot);
+
+ /* close the relation. */
+ ExecCloseScanRelation(relation);
+ }
+
+ /* ----------------------------------------------------------------
+ * Join Support
+ * ----------------------------------------------------------------
+ */
+
+ /* ----------------------------------------------------------------
+ * ExecForeignReScan
+ *
+ * Rescans the relation.
+ * ----------------------------------------------------------------
+ */
+ void
+ ExecForeignReScan(ForeignScanState *node)
+ {
+ if (node->routine->ReOpen != NULL)
+ node->routine->ReOpen(node);
+
+ ExecScanReScan((ScanState *) node);
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecForeignMarkPos(node)
+ *
+ * Marks scan position.
+ * ----------------------------------------------------------------
+ */
+ void
+ ExecForeignMarkPos(ForeignScanState *node)
+ {
+ elog(ERROR, "ForeignScan does not support mark/restore");
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecForeignRestrPos
+ *
+ * Restores scan position.
+ * ----------------------------------------------------------------
+ */
+ void
+ ExecForeignRestrPos(ForeignScanState *node)
+ {
+ elog(ERROR, "ForeignScan does not support mark/restore");
+ }
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index cc67bcb..7834d30 100644
*** a/src/backend/foreign/foreign.c
--- b/src/backend/foreign/foreign.c
***************
*** 16,21 ****
--- 16,22 ----
#include "catalog/namespace.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
+ #include "catalog/pg_foreign_table.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
#include "foreign/foreign.h"
*************** GetForeignDataWrapper(Oid fdwid)
*** 59,64 ****
--- 60,66 ----
fdw->owner = fdwform->fdwowner;
fdw->fdwname = pstrdup(NameStr(fdwform->fdwname));
fdw->fdwvalidator = fdwform->fdwvalidator;
+ fdw->fdwhandler = fdwform->fdwhandler;
/* Extract the options */
datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
*************** postgresql_fdw_validator(PG_FUNCTION_ARG
*** 414,416 ****
--- 416,656 ----
PG_RETURN_BOOL(true);
}
+
+ /*
+ * GetForeignTable - look up the foreign table definition by relation oid.
+ */
+ ForeignTable *
+ GetForeignTable(Oid relid)
+ {
+ Form_pg_foreign_table tableform;
+ ForeignTable *ft;
+ HeapTuple tp;
+ Datum datum;
+ bool isnull;
+
+ tp = SearchSysCache(FOREIGNTABLEREL,
+ ObjectIdGetDatum(relid),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for foreign table %u", relid);
+
+ tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
+
+ ft = palloc(sizeof(ForeignTable));
+ ft->relid = relid;
+ ft->serverid = tableform->ftserver;
+
+ /* Extract the ftoptions */
+ datum = SysCacheGetAttr(FOREIGNTABLEREL,
+ tp,
+ Anum_pg_foreign_table_ftoptions,
+ &isnull);
+
+ /* untransformRelOptions does exactly what we want - avoid duplication */
+ ft->options = untransformRelOptions(datum);
+ ReleaseSysCache(tp);
+
+ return ft;
+ }
+
+ /*
+ * GetFdwRoutine - look up the handler of the foreign-data wrapper by OID and
+ * retrieve FdwRoutine.
+ */
+ FdwRoutine *
+ GetFdwRoutine(Oid fdwhandler)
+ {
+ FmgrInfo flinfo;
+ FunctionCallInfoData fcinfo;
+ Datum result;
+ FdwRoutine *routine;
+
+ if (fdwhandler == InvalidOid)
+ elog(ERROR, "foreign-data wrapper has no handler");
+
+ fmgr_info(fdwhandler, &flinfo);
+ InitFunctionCallInfoData(fcinfo, &flinfo, 0, NULL, NULL);
+ result = FunctionCallInvoke(&fcinfo);
+
+ if (fcinfo.isnull ||
+ (routine = (FdwRoutine *) DatumGetPointer(result)) == NULL)
+ {
+ elog(ERROR, "function %u returned NULL", flinfo.fn_oid);
+ routine = NULL; /* keep compiler quiet */
+ }
+
+ return routine;
+ }
+
+ /*
+ * GetFdwRoutineByRelId - look up the handler of the foreign-data wrapper by
+ * OID of the foreign table and retrieve FdwRoutine.
+ */
+ FdwRoutine *
+ GetFdwRoutineByRelId(Oid relid)
+ {
+ HeapTuple tp;
+ Form_pg_foreign_data_wrapper fdwform;
+ Form_pg_foreign_server serverform;
+ Form_pg_foreign_table tableform;
+ Oid serverid;
+ Oid fdwid;
+ Oid fdwhandler;
+
+ /* Get function OID for the foreign table. */
+ tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for foreign table %u", relid);
+ tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
+ serverid = tableform->ftserver;
+ ReleaseSysCache(tp);
+
+ tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+ serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
+ fdwid = serverform->srvfdw;
+ ReleaseSysCache(tp);
+
+ tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+ fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
+ fdwhandler = fdwform->fdwhandler;
+ ReleaseSysCache(tp);
+
+ return GetFdwRoutine(fdwhandler);
+ }
+
+ /*
+ * Determine the relation is a foreign table.
+ */
+ bool
+ IsForeignTable(Oid relid)
+ {
+ HeapTuple tuple;
+ Form_pg_class classForm;
+ char relkind;
+
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation with OID %u does not exist", relid)));
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ relkind = classForm->relkind;
+ ReleaseSysCache(tuple);
+
+ return (relkind == RELKIND_FOREIGN_TABLE);
+ }
+
+ /*
+ * Get fdwvalidator of the foreign table without generating ForeignTable,
+ * ForeignServer and ForeignDataWrapper.
+ */
+ Oid
+ GetFdwValidator(Oid relid)
+ {
+ HeapTuple tuple;
+ Oid serverid;
+ Oid fdwid;
+ Oid fdwvalidator;
+ Form_pg_foreign_table form_ft;
+ Form_pg_foreign_server form_srv;
+ Form_pg_foreign_data_wrapper form_fdw;
+
+ /* pg_foreign_table */
+ tuple = SearchSysCache(FOREIGNTABLEREL,
+ ObjectIdGetDatum(relid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("foreign table reference \"%u\" is ambiguous", relid)));
+ form_ft = (Form_pg_foreign_table) GETSTRUCT(tuple);
+ serverid = form_ft->ftserver;
+ ReleaseSysCache(tuple);
+
+ /* pg_foreign_server */
+ tuple = SearchSysCache1(FOREIGNSERVEROID,
+ ObjectIdGetDatum(serverid));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("foreign server reference \"%u\" is ambiguous",
+ serverid)));
+ form_srv = (Form_pg_foreign_server) GETSTRUCT(tuple);
+ fdwid = form_srv->srvfdw;
+ ReleaseSysCache(tuple);
+
+ /* pg_foreign_table */
+ tuple = SearchSysCache1(FOREIGNDATAWRAPPEROID,
+ ObjectIdGetDatum(fdwid));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("foreign-data wrapper reference \"%u\" is ambiguous",
+ fdwid)));
+ form_fdw = (Form_pg_foreign_data_wrapper) GETSTRUCT(tuple);
+ fdwvalidator = form_fdw->fdwvalidator;
+ ReleaseSysCache(tuple);
+
+ return fdwvalidator;
+ }
+
+ /*
+ * Flattern generic options into keywords and values buffers.
+ */
+ int
+ flatten_generic_options(List *options, const char **keywords,
+ const char **values)
+ {
+ ListCell *cell;
+ int n = 0;
+
+ foreach(cell, options)
+ {
+ DefElem *def = lfirst(cell);
+
+ keywords[n] = def->defname;
+ values[n] = strVal(def->arg);
+ n++;
+ }
+ return n;
+ }
+
+ /*
+ * Retrieve per-column generic options in form of DefElem.
+ */
+ List *
+ GetGenericOptionsPerColumn(Oid relid, int2 attnum)
+ {
+ Form_pg_attribute attform;
+ Datum datum;
+ HeapTuple tp;
+ bool isnull;
+ List *options = NIL;
+
+ tp = SearchSysCache2(ATTNUM,
+ ObjectIdGetDatum(relid),
+ Int16GetDatum(attnum));
+
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for attribute attnum %u of relation %u", (int) attnum, relid);
+
+ attform = (Form_pg_attribute) GETSTRUCT(tp);
+
+ /* Extract the options */
+ datum = SysCacheGetAttr(ATTNUM,
+ tp,
+ Anum_pg_attribute_attgenoptions,
+ &isnull);
+ if (!isnull)
+ options = untransformRelOptions(datum);
+
+ ReleaseSysCache(tp);
+
+ return options;
+ }
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0e0b4dc..066d016 100644
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyWorkTableScan(WorkTableScan *from)
*** 545,550 ****
--- 545,566 ----
}
/*
+ * _copyForeignScan
+ */
+ static ForeignScan *
+ _copyForeignScan(ForeignScan *from)
+ {
+ ForeignScan *newnode = makeNode(ForeignScan);
+
+ /*
+ * copy node superclass fields
+ */
+ CopyScanFields((Scan *) from, (Scan *) newnode);
+
+ return newnode;
+ }
+
+ /*
* CopyJoinFields
*
* This function copies the fields of the Join node. It is used by
*************** _copyColumnDef(ColumnDef *from)
*** 2214,2219 ****
--- 2230,2236 ----
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
COPY_NODE_FIELD(constraints);
+ COPY_NODE_FIELD(genoptions);
return newnode;
}
*************** _copyCreateFdwStmt(CreateFdwStmt *from)
*** 3188,3194 ****
CreateFdwStmt *newnode = makeNode(CreateFdwStmt);
COPY_STRING_FIELD(fdwname);
! COPY_NODE_FIELD(validator);
COPY_NODE_FIELD(options);
return newnode;
--- 3205,3211 ----
CreateFdwStmt *newnode = makeNode(CreateFdwStmt);
COPY_STRING_FIELD(fdwname);
! COPY_NODE_FIELD(func_options);
COPY_NODE_FIELD(options);
return newnode;
*************** _copyAlterFdwStmt(AlterFdwStmt *from)
*** 3200,3207 ****
AlterFdwStmt *newnode = makeNode(AlterFdwStmt);
COPY_STRING_FIELD(fdwname);
! COPY_NODE_FIELD(validator);
! COPY_SCALAR_FIELD(change_validator);
COPY_NODE_FIELD(options);
return newnode;
--- 3217,3223 ----
AlterFdwStmt *newnode = makeNode(AlterFdwStmt);
COPY_STRING_FIELD(fdwname);
! COPY_NODE_FIELD(func_options);
COPY_NODE_FIELD(options);
return newnode;
*************** _copyDropUserMappingStmt(DropUserMapping
*** 3294,3299 ****
--- 3310,3334 ----
return newnode;
}
+ static CreateForeignTableStmt *
+ _copyCreateForeignTableStmt(CreateForeignTableStmt *from)
+ {
+ CreateForeignTableStmt *newnode = makeNode(CreateForeignTableStmt);
+
+ COPY_NODE_FIELD(base.relation);
+ COPY_NODE_FIELD(base.tableElts);
+ COPY_NODE_FIELD(base.inhRelations);
+ COPY_NODE_FIELD(base.ofTypename);
+ COPY_NODE_FIELD(base.constraints);
+ COPY_NODE_FIELD(base.options);
+ COPY_SCALAR_FIELD(base.oncommit);
+ COPY_STRING_FIELD(base.tablespacename);
+ COPY_STRING_FIELD(servername);
+ COPY_NODE_FIELD(options);
+
+ return newnode;
+ }
+
static CreateTrigStmt *
_copyCreateTrigStmt(CreateTrigStmt *from)
{
*************** copyObject(void *from)
*** 3730,3735 ****
--- 3765,3773 ----
case T_WorkTableScan:
retval = _copyWorkTableScan(from);
break;
+ case T_ForeignScan:
+ retval = _copyForeignScan(from);
+ break;
case T_Join:
retval = _copyJoin(from);
break;
*************** copyObject(void *from)
*** 4192,4197 ****
--- 4230,4238 ----
case T_DropUserMappingStmt:
retval = _copyDropUserMappingStmt(from);
break;
+ case T_CreateForeignTableStmt:
+ retval = _copyCreateForeignTableStmt(from);
+ break;
case T_CreateTrigStmt:
retval = _copyCreateTrigStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2d2b8c7..e2d6273 100644
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** static bool
*** 1628,1634 ****
_equalCreateFdwStmt(CreateFdwStmt *a, CreateFdwStmt *b)
{
COMPARE_STRING_FIELD(fdwname);
! COMPARE_NODE_FIELD(validator);
COMPARE_NODE_FIELD(options);
return true;
--- 1628,1634 ----
_equalCreateFdwStmt(CreateFdwStmt *a, CreateFdwStmt *b)
{
COMPARE_STRING_FIELD(fdwname);
! COMPARE_NODE_FIELD(func_options);
COMPARE_NODE_FIELD(options);
return true;
*************** static bool
*** 1638,1645 ****
_equalAlterFdwStmt(AlterFdwStmt *a, AlterFdwStmt *b)
{
COMPARE_STRING_FIELD(fdwname);
! COMPARE_NODE_FIELD(validator);
! COMPARE_SCALAR_FIELD(change_validator);
COMPARE_NODE_FIELD(options);
return true;
--- 1638,1644 ----
_equalAlterFdwStmt(AlterFdwStmt *a, AlterFdwStmt *b)
{
COMPARE_STRING_FIELD(fdwname);
! COMPARE_NODE_FIELD(func_options);
COMPARE_NODE_FIELD(options);
return true;
*************** _equalDropUserMappingStmt(DropUserMappin
*** 1719,1724 ****
--- 1718,1736 ----
}
static bool
+ _equalCreateForeignTableStmt(CreateForeignTableStmt *a, CreateForeignTableStmt *b)
+ {
+ if (!_equalCreateStmt(&a->base, &b->base))
+ return false;
+
+ COMPARE_STRING_FIELD(base.tablespacename);
+ COMPARE_STRING_FIELD(servername);
+ COMPARE_NODE_FIELD(options);
+
+ return true;
+ }
+
+ static bool
_equalCreateTrigStmt(CreateTrigStmt *a, CreateTrigStmt *b)
{
COMPARE_STRING_FIELD(trigname);
*************** _equalColumnDef(ColumnDef *a, ColumnDef
*** 2152,2157 ****
--- 2164,2170 ----
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
COMPARE_NODE_FIELD(constraints);
+ COMPARE_NODE_FIELD(genoptions);
return true;
}
*************** equal(void *a, void *b)
*** 2821,2826 ****
--- 2834,2842 ----
case T_DropUserMappingStmt:
retval = _equalDropUserMappingStmt(a, b);
break;
+ case T_CreateForeignTableStmt:
+ retval = _equalCreateForeignTableStmt(a, b);
+ break;
case T_CreateTrigStmt:
retval = _equalCreateTrigStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index afbfcca..a3f4ebb 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outWorkTableScan(StringInfo str, WorkTa
*** 531,536 ****
--- 531,544 ----
}
static void
+ _outForeignScan(StringInfo str, ForeignScan *node)
+ {
+ WRITE_NODE_TYPE("FOREIGNSCAN");
+
+ _outScanInfo(str, (Scan *) node);
+ }
+
+ static void
_outJoin(StringInfo str, Join *node)
{
WRITE_NODE_TYPE("JOIN");
*************** _outTidPath(StringInfo str, TidPath *nod
*** 1476,1481 ****
--- 1484,1497 ----
}
static void
+ _outForeignPath(StringInfo str, ForeignPath *node)
+ {
+ WRITE_NODE_TYPE("FOREIGNPATH");
+
+ _outPathInfo(str, (Path *) node);
+ }
+
+ static void
_outAppendPath(StringInfo str, AppendPath *node)
{
WRITE_NODE_TYPE("APPENDPATH");
*************** _outColumnDef(StringInfo str, ColumnDef
*** 1988,1993 ****
--- 2004,2010 ----
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
WRITE_NODE_FIELD(constraints);
+ WRITE_NODE_FIELD(genoptions);
}
static void
*************** _outNode(StringInfo str, void *obj)
*** 2603,2608 ****
--- 2620,2628 ----
case T_WorkTableScan:
_outWorkTableScan(str, obj);
break;
+ case T_ForeignScan:
+ _outForeignScan(str, obj);
+ break;
case T_Join:
_outJoin(str, obj);
break;
*************** _outNode(StringInfo str, void *obj)
*** 2805,2810 ****
--- 2825,2833 ----
case T_TidPath:
_outTidPath(str, obj);
break;
+ case T_ForeignPath:
+ _outForeignPath(str, obj);
+ break;
case T_AppendPath:
_outAppendPath(str, obj);
break;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index ce893a7..758c1e2 100644
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
***************
*** 17,22 ****
--- 17,23 ----
#include
+ #include "foreign/foreign.h"
#include "nodes/nodeFuncs.h"
#ifdef OPTIMIZER_DEBUG
#include "nodes/print.h"
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 255,268 ****
* least one dimension of cost or sortedness.
*/
! /* Consider sequential scan */
! add_path(rel, create_seqscan_path(root, rel));
! /* Consider index scans */
! create_index_paths(root, rel);
! /* Consider TID scans */
! create_tidscan_paths(root, rel);
/* Now find the cheapest of the paths for this rel */
set_cheapest(rel);
--- 256,277 ----
* least one dimension of cost or sortedness.
*/
! if (IsForeignTable(rte->relid))
! {
! /* only foreign scan path is applyable to foreign table */
! add_path(rel, create_foreignscan_path(root, rel));
! }
! else
! {
! /* Consider sequential scan */
! add_path(rel, create_seqscan_path(root, rel));
! /* Consider index scans */
! create_index_paths(root, rel);
! /* Consider TID scans */
! create_tidscan_paths(root, rel);
! }
/* Now find the cheapest of the paths for this rel */
set_cheapest(rel);
*************** print_path(PlannerInfo *root, Path *path
*** 1503,1508 ****
--- 1512,1520 ----
case T_TidPath:
ptype = "TidScan";
break;
+ case T_ForeignPath:
+ ptype = "ForeignScan";
+ break;
case T_AppendPath:
ptype = "Append";
break;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 0724f9a..49366bc 100644
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
*************** cost_ctescan(Path *path, PlannerInfo *ro
*** 1023,1028 ****
--- 1023,1050 ----
}
/*
+ * cost_foreignscan
+ * Determines and returns the cost of scanning a foreign table sequentially.
+ */
+ void
+ cost_foreignscan(ForeignPath *path, PlannerInfo *root,
+ RelOptInfo *baserel)
+ {
+ RangeTblEntry *rte;
+ FdwRoutine *routine;
+
+ /* Should only be applied to base relations */
+ Assert(baserel->relid > 0);
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ /* Leave estimation of the costs to the wrapper handler */
+ rte = planner_rt_fetch(baserel->relid, root);
+ routine = GetFdwRoutineByRelId(rte->relid);
+ if (routine->EstimateCosts != NULL)
+ routine->EstimateCosts(path, root, baserel);
+ }
+
+ /*
* cost_recursive_union
* Determines and returns the cost of performing a recursive union,
* and also the estimated output size.
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 41ad512..bd95c91 100644
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 20,25 ****
--- 20,26 ----
#include
#include "access/skey.h"
+ #include "catalog/pg_class.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
*************** static CteScan *create_ctescan_plan(Plan
*** 71,76 ****
--- 72,79 ----
List *tlist, List *scan_clauses);
static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
+ static ForeignScan *create_foreignscan_plan(PlannerInfo *root, Path *best_path,
+ List *tlist, List *scan_clauses);
static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path,
Plan *outer_plan, Plan *inner_plan);
static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
*************** static CteScan *make_ctescan(List *qptli
*** 109,114 ****
--- 112,119 ----
Index scanrelid, int ctePlanId, int cteParam);
static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
Index scanrelid, int wtParam);
+ static ForeignScan *make_foreignscan(List *qptlist, RangeTblEntry *rte,
+ List *qpqual, Index scanrelid);
static BitmapAnd *make_bitmap_and(List *bitmapplans);
static BitmapOr *make_bitmap_or(List *bitmapplans);
static NestLoop *make_nestloop(List *tlist,
*************** create_plan_recurse(PlannerInfo *root, P
*** 201,206 ****
--- 206,212 ----
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
+ case T_ForeignScan:
plan = create_scan_plan(root, best_path);
break;
case T_HashJoin:
*************** create_scan_plan(PlannerInfo *root, Path
*** 341,346 ****
--- 347,359 ----
scan_clauses);
break;
+ case T_ForeignScan:
+ plan = (Plan *) create_foreignscan_plan(root,
+ best_path,
+ tlist,
+ scan_clauses);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) best_path->pathtype);
*************** disuse_physical_tlist(Plan *plan, Path *
*** 463,468 ****
--- 476,482 ----
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
+ case T_ForeignScan:
plan->targetlist = build_relation_tlist(path->parent);
break;
default:
*************** create_worktablescan_plan(PlannerInfo *r
*** 1732,1737 ****
--- 1746,1786 ----
return scan_plan;
}
+ /*
+ * create_foreignscan_plan
+ * Returns a foreignscan plan for the base relation scanned by 'best_path'
+ * with restriction clauses 'scan_clauses' and targetlist 'tlist'.
+ */
+ static ForeignScan *
+ create_foreignscan_plan(PlannerInfo *root, Path *best_path,
+ List *tlist, List *scan_clauses)
+ {
+ ForeignScan *scan_plan;
+ Index scan_relid = best_path->parent->relid;
+ RangeTblEntry *rte;
+
+ /* it should be a base rel... */
+ Assert(scan_relid > 0);
+ Assert(best_path->parent->rtekind == RTE_RELATION);
+ rte = planner_rt_fetch(scan_relid, root);
+ Assert(rte->rtekind == RTE_RELATION);
+
+ /* Sort clauses into best execution order */
+ scan_clauses = order_qual_clauses(root, scan_clauses);
+
+ /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+ scan_plan = make_foreignscan(tlist,
+ rte,
+ scan_clauses,
+ scan_relid);
+
+ copy_path_costsize(&scan_plan->scan.plan, best_path);
+
+ return scan_plan;
+ }
+
/*****************************************************************************
*
*************** make_worktablescan(List *qptlist,
*** 2886,2891 ****
--- 2935,2959 ----
return node;
}
+ static ForeignScan *
+ make_foreignscan(List *qptlist,
+ RangeTblEntry *rte,
+ List *qpqual,
+ Index scanrelid)
+ {
+ ForeignScan *node = makeNode(ForeignScan);
+ Plan *plan = &node->scan.plan;
+
+ /* cost should be inserted by caller */
+ plan->targetlist = qptlist;
+ plan->qual = qpqual;
+ plan->lefttree = NULL;
+ plan->righttree = NULL;
+ node->scan.scanrelid = scanrelid;
+
+ return node;
+ }
+
Append *
make_append(List *appendplans, List *tlist)
{
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 9aef7fc..7feccbd 100644
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** set_plan_refs(PlannerGlobal *glob, Plan
*** 396,401 ****
--- 396,412 ----
fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
}
break;
+ case T_ForeignScan:
+ {
+ ForeignScan *splan = (ForeignScan *) plan;
+
+ splan->scan.scanrelid += rtoffset;
+ splan->scan.plan.targetlist =
+ fix_scan_list(glob, splan->scan.plan.targetlist, rtoffset);
+ splan->scan.plan.qual =
+ fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
+ }
+ break;
case T_NestLoop:
case T_MergeJoin:
case T_HashJoin:
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 754753c..83bae76 100644
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
*************** finalize_plan(PlannerInfo *root, Plan *p
*** 2041,2046 ****
--- 2041,2050 ----
context.paramids = bms_add_members(context.paramids, scan_params);
break;
+ case T_ForeignScan:
+ context.paramids = bms_add_members(context.paramids, scan_params);
+ break;
+
case T_ModifyTable:
{
ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 4686578..d154c30 100644
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** expand_inherited_rtentry(PlannerInfo *ro
*** 1238,1243 ****
--- 1238,1252 ----
}
/*
+ * SELECT FOR UPDATE/SHARE is not allowd to foreign tables because
+ * they are read-only.
+ */
+ if (newrelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE &&
+ lockmode != AccessShareLock)
+ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT FOR UPDATE/SHARE is not allowed with foreign tables")));
+
+ /*
* Build an RTE for the child, and attach to query's rangetable list.
* We copy most fields of the parent's RTE, but replace relation OID,
* and set inh = false. Also, set requiredPerms to zero since all
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 231d221..eb8d87f 100644
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** create_worktablescan_path(PlannerInfo *r
*** 1416,1421 ****
--- 1416,1440 ----
}
/*
+ * create_foreignscan_path
+ * Creates a path corresponding to a scan of a foreign table,
+ * returning the pathnode.
+ */
+ Path *
+ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel)
+ {
+ ForeignPath *pathnode = makeNode(ForeignPath);
+
+ pathnode->path.pathtype = T_ForeignScan;
+ pathnode->path.parent = rel;
+ pathnode->path.pathkeys = NIL; /* result is always unordered */
+
+ cost_foreignscan(pathnode, root, rel);
+
+ return (Path *) pathnode;
+ }
+
+ /*
* create_nestloop_path
* Creates a pathnode corresponding to a nestloop join between two
* relations.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 908b4f7..e2bc8ea 100644
*** a/src/backend/optimizer/util/plancat.c
--- b/src/backend/optimizer/util/plancat.c
*************** estimate_rel_size(Relation rel, int32 *a
*** 423,428 ****
--- 423,433 ----
*pages = 1;
*tuples = 1;
break;
+ case RELKIND_FOREIGN_TABLE:
+ /* foreign tables has no storage, trust statistics */
+ *pages = rel->rd_rel->relpages;
+ *tuples = rel->rd_rel->reltuples;
+ break;
default:
/* else it has no disk storage; probably shouldn't get here? */
*pages = 0;
*************** relation_excluded_by_constraints(Planner
*** 722,728 ****
*
* We also support building a "physical" tlist for subqueries, functions,
* values lists, and CTEs, since the same optimization can occur in
! * SubqueryScan, FunctionScan, ValuesScan, CteScan, and WorkTableScan nodes.
*/
List *
build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
--- 727,734 ----
*
* We also support building a "physical" tlist for subqueries, functions,
* values lists, and CTEs, since the same optimization can occur in
! * SubqueryScan, FunctionScan, ValuesScan, CteScan, WorkTableScan and
! * ForeignScan nodes.
*/
List *
build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 3d84f61..6f05d59 100644
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 28,33 ****
--- 28,34 ----
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+ #include "nodes/pg_list.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
#include "parser/parse_agg.h"
***************
*** 40,45 ****
--- 41,47 ----
#include "parser/parse_target.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
+ #include "utils/lsyscache.h"
#include "utils/rel.h"
*************** applyLockingClause(Query *qry, Index rti
*** 2281,2286 ****
--- 2283,2297 ----
bool forUpdate, bool noWait, bool pushedDown)
{
RowMarkClause *rc;
+ RangeTblEntry *rte;
+
+ /* If rangetable is a foreign table, locking is not allowed */
+ rte = list_nth(qry->rtable, rtindex - 1);
+ if (rte->rtekind == RTE_RELATION &&
+ get_rel_relkind(rte->relid) == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SELECT FOR UPDATE/SHARE is not allowed with foreign tables")));
/* If it's an explicit clause, make sure hasForUpdate gets set */
if (!pushedDown)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1c17be8..deceed5 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static RangeVar *makeRangeVarFromAnyName
*** 185,190 ****
--- 185,191 ----
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
+ AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
AlterRoleStmt AlterRoleSetStmt
AlterDefaultPrivilegesStmt DefACLAction
*************** static RangeVar *makeRangeVarFromAnyName
*** 193,199 ****
CreateDomainStmt CreateGroupStmt CreateOpClassStmt
CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
! CreateFdwStmt CreateForeignServerStmt CreateAssertStmt CreateTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
--- 194,201 ----
CreateDomainStmt CreateGroupStmt CreateOpClassStmt
CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
! CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
! CreateAssertStmt CreateTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
*************** static RangeVar *makeRangeVarFromAnyName
*** 219,226 ****
%type alter_column_default opclass_item opclass_drop alter_using
%type add_drop opt_asc_desc opt_nulls_order
! %type alter_table_cmd alter_type_cmd
! %type alter_table_cmds alter_type_cmds
%type opt_drop_behavior
--- 221,228 ----
%type alter_column_default opclass_item opclass_drop alter_using
%type add_drop opt_asc_desc opt_nulls_order
! %type alter_table_cmd alter_type_cmd alter_foreign_table_cmd
! %type alter_table_cmds alter_type_cmds alter_foreign_table_cmds
%type opt_drop_behavior
*************** static RangeVar *makeRangeVarFromAnyName
*** 279,284 ****
--- 281,287 ----
%type stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
+ OptForeignTableElementList ForeignTableElementList
reloptions opt_reloptions
OptWith opt_distinct opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
*************** static RangeVar *makeRangeVarFromAnyName
*** 303,308 ****
--- 306,314 ----
create_generic_options alter_generic_options
relation_expr_list dostmt_opt_list
+ %type opt_func_options func_options
+ %type fdw_option
+
%type OptTempTableName
%type into_clause create_as_target
*************** static RangeVar *makeRangeVarFromAnyName
*** 350,356 ****
--- 356,366 ----
%type set_rest SetResetClause
%type TableElement TypedTableElement ConstraintElem TableFuncElement
+ ForeignTableElement ForeignConstraintElem
+ %type CheckConstraintElem UniqueConstraintElem PrimaryKeyConstraintElem
+ ExcludeConstraintElem ForeignKeyConstraintElem
%type columnDef columnOptions
+ foreignColumnDef
%type def_elem reloption_elem old_aggr_elem
%type def_arg columnElem where_clause where_or_current_clause
a_expr b_expr c_expr func_expr AexprConst indirection_el
*************** static RangeVar *makeRangeVarFromAnyName
*** 410,419 ****
%type unreserved_keyword type_func_name_keyword
%type col_name_keyword reserved_keyword
! %type TableConstraint TableLikeClause
%type TableLikeOptionList TableLikeOption
! %type ColQualList
%type ColConstraint ColConstraintElem ConstraintAttr
%type key_actions key_delete key_match key_update key_action
%type ConstraintAttributeSpec ConstraintDeferrabilitySpec
ConstraintTimeSpec
--- 420,434 ----
%type unreserved_keyword type_func_name_keyword
%type col_name_keyword reserved_keyword
! %type TableConstraint TableLikeClause ForeignTableLikeClause
! ForeignTableConstraint
%type TableLikeOptionList TableLikeOption
! %type ForeignTableLikeOptionList ForeignTableLikeOption
! %type ColQualList ForeignColQualList
%type ColConstraint ColConstraintElem ConstraintAttr
+ ColNotNullConstraintElem ColNullConstraintElem
+ ColCheckConstraintElem
+ ForeignColConstraint ForeignColConstraintElem
%type key_actions key_delete key_match key_update key_action
%type ConstraintAttributeSpec ConstraintDeferrabilitySpec
ConstraintTimeSpec
*************** stmt :
*** 655,660 ****
--- 670,676 ----
| AlterEnumStmt
| AlterFdwStmt
| AlterForeignServerStmt
+ | AlterForeignTableStmt
| AlterFunctionStmt
| AlterGroupStmt
| AlterObjectSchemaStmt
*************** stmt :
*** 683,688 ****
--- 699,705 ----
| CreateDomainStmt
| CreateFdwStmt
| CreateForeignServerStmt
+ | CreateForeignTableStmt
| CreateFunctionStmt
| CreateGroupStmt
| CreateOpClassStmt
*************** ColConstraint:
*** 2455,2474 ****
* or be part of a_expr NOT LIKE or similar constructs).
*/
ColConstraintElem:
! NOT NULL_P
! {
! Constraint *n = makeNode(Constraint);
! n->contype = CONSTR_NOTNULL;
! n->location = @1;
! $$ = (Node *)n;
! }
! | NULL_P
! {
! Constraint *n = makeNode(Constraint);
! n->contype = CONSTR_NULL;
! n->location = @1;
! $$ = (Node *)n;
! }
| UNIQUE opt_definition OptConsTableSpace
{
Constraint *n = makeNode(Constraint);
--- 2472,2479 ----
* or be part of a_expr NOT LIKE or similar constructs).
*/
ColConstraintElem:
! ColNotNullConstraintElem { $$ = $1; }
! | ColNullConstraintElem { $$ = $1; }
| UNIQUE opt_definition OptConsTableSpace
{
Constraint *n = makeNode(Constraint);
*************** ColConstraintElem:
*** 2489,2503 ****
n->indexspace = $4;
$$ = (Node *)n;
}
! | CHECK '(' a_expr ')'
! {
! Constraint *n = makeNode(Constraint);
! n->contype = CONSTR_CHECK;
! n->location = @1;
! n->raw_expr = $3;
! n->cooked_expr = NULL;
! $$ = (Node *)n;
! }
| DEFAULT b_expr
{
Constraint *n = makeNode(Constraint);
--- 2494,2500 ----
n->indexspace = $4;
$$ = (Node *)n;
}
! | ColCheckConstraintElem { $$ = $1; }
| DEFAULT b_expr
{
Constraint *n = makeNode(Constraint);
*************** ColConstraintElem:
*** 2523,2528 ****
--- 2520,2554 ----
}
;
+ ColNotNullConstraintElem: NOT NULL_P
+ {
+ Constraint *n = makeNode(Constraint);
+ n->contype = CONSTR_NOTNULL;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
+ ;
+
+ ColNullConstraintElem: NULL_P
+ {
+ Constraint *n = makeNode(Constraint);
+ n->contype = CONSTR_NULL;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
+ ;
+
+ ColCheckConstraintElem: CHECK '(' a_expr ')'
+ {
+ Constraint *n = makeNode(Constraint);
+ n->contype = CONSTR_CHECK;
+ n->location = @1;
+ n->raw_expr = $3;
+ n->cooked_expr = NULL;
+ $$ = (Node *)n;
+ }
+ ;
+
/*
* ConstraintAttr represents constraint attributes, which we parse as if
* they were independent constraint clauses, in order to avoid shift/reduce
*************** TableLikeOption:
*** 2600,2605 ****
--- 2626,2656 ----
| ALL { $$ = CREATE_TABLE_LIKE_ALL; }
;
+ ForeignTableLikeClause:
+ LIKE qualified_name ForeignTableLikeOptionList
+ {
+ InhRelation *n = makeNode(InhRelation);
+ n->relation = $2;
+ n->options = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
+ ForeignTableLikeOptionList:
+ ForeignTableLikeOptionList INCLUDING ForeignTableLikeOption
+ { $$ = $1 | $3; }
+ | ForeignTableLikeOptionList EXCLUDING ForeignTableLikeOption
+ { $$ = $1 & ~$3; }
+ | /* EMPTY */ { $$ = 0; }
+ ;
+
+ ForeignTableLikeOption:
+ DEFAULTS { $$ = CREATE_TABLE_LIKE_DEFAULTS; }
+ | CONSTRAINTS { $$ = CREATE_TABLE_LIKE_CONSTRAINTS; }
+ | COMMENTS { $$ = CREATE_TABLE_LIKE_COMMENTS; }
+ | ALL { $$ = CREATE_TABLE_LIKE_FOREIGN_ALL; }
+ ;
+
/* ConstraintElem specifies constraint syntax which is not embedded into
* a column definition. ColConstraintElem specifies the embedded form.
*************** TableConstraint:
*** 2617,2623 ****
--- 2668,2699 ----
| ConstraintElem { $$ = $1; }
;
+ ForeignTableConstraint:
+ CONSTRAINT name ForeignConstraintElem
+ {
+ Constraint *n = (Constraint *) $3;
+ Assert(IsA(n, Constraint));
+ n->conname = $2;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ | ForeignConstraintElem { $$ = $1; }
+ ;
+
+
ConstraintElem:
+ CheckConstraintElem { $$ = $1; }
+ | UniqueConstraintElem { $$ = $1; }
+ | PrimaryKeyConstraintElem { $$ = $1; }
+ | ExcludeConstraintElem { $$ = $1; }
+ | ForeignKeyConstraintElem { $$ = $1; }
+ ;
+
+ ForeignConstraintElem:
+ CheckConstraintElem { $$ = $1; }
+ ;
+
+ CheckConstraintElem:
CHECK '(' a_expr ')' ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
*************** ConstraintElem:
*** 2632,2638 ****
parser_errposition(@5)));
$$ = (Node *)n;
}
! | UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
--- 2708,2717 ----
parser_errposition(@5)));
$$ = (Node *)n;
}
! ;
!
! UniqueConstraintElem:
! UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
*************** ConstraintElem:
*** 2645,2651 ****
n->initdeferred = ($7 & 2) != 0;
$$ = (Node *)n;
}
! | PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace
ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
--- 2724,2733 ----
n->initdeferred = ($7 & 2) != 0;
$$ = (Node *)n;
}
! ;
!
! PrimaryKeyConstraintElem:
! PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace
ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
*************** ConstraintElem:
*** 2658,2664 ****
n->initdeferred = ($8 & 2) != 0;
$$ = (Node *)n;
}
! | EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
opt_definition OptConsTableSpace ExclusionWhereClause
ConstraintAttributeSpec
{
--- 2740,2749 ----
n->initdeferred = ($8 & 2) != 0;
$$ = (Node *)n;
}
! ;
!
! ExcludeConstraintElem:
! EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
opt_definition OptConsTableSpace ExclusionWhereClause
ConstraintAttributeSpec
{
*************** ConstraintElem:
*** 2674,2680 ****
n->initdeferred = ($9 & 2) != 0;
$$ = (Node *)n;
}
! | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
opt_column_list key_match key_actions ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
--- 2759,2767 ----
n->initdeferred = ($9 & 2) != 0;
$$ = (Node *)n;
}
! ;
! ForeignKeyConstraintElem:
! FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
opt_column_list key_match key_actions ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
*************** DropTableSpaceStmt: DROP TABLESPACE name
*** 3136,3151 ****
*
*****************************************************************************/
! CreateFdwStmt: CREATE FOREIGN DATA_P WRAPPER name opt_validator create_generic_options
{
CreateFdwStmt *n = makeNode(CreateFdwStmt);
n->fdwname = $5;
! n->validator = $6;
n->options = $7;
$$ = (Node *) n;
}
;
/*****************************************************************************
*
* QUERY :
--- 3223,3255 ----
*
*****************************************************************************/
! CreateFdwStmt: CREATE FOREIGN DATA_P WRAPPER name opt_func_options create_generic_options
{
CreateFdwStmt *n = makeNode(CreateFdwStmt);
n->fdwname = $5;
! n->func_options = $6;
n->options = $7;
$$ = (Node *) n;
}
;
+ fdw_option:
+ VALIDATOR handler_name { $$ = makeDefElem("validator", (Node *)$2); }
+ | NO VALIDATOR { $$ = makeDefElem("validator", NULL); }
+ | HANDLER handler_name { $$ = makeDefElem("handler", (Node *)$2); }
+ | NO HANDLER { $$ = makeDefElem("handler", NULL); }
+ ;
+
+ func_options:
+ fdw_option { $$ = list_make1($1); }
+ | func_options fdw_option { $$ = lappend($1, $2); }
+ ;
+
+ opt_func_options:
+ func_options { $$ = $1; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
/*****************************************************************************
*
* QUERY :
*************** DropFdwStmt: DROP FOREIGN DATA_P WRAPPER
*** 3178,3205 ****
*
****************************************************************************/
! AlterFdwStmt: ALTER FOREIGN DATA_P WRAPPER name validator_clause alter_generic_options
{
AlterFdwStmt *n = makeNode(AlterFdwStmt);
n->fdwname = $5;
! n->validator = $6;
! n->change_validator = true;
n->options = $7;
$$ = (Node *) n;
}
! | ALTER FOREIGN DATA_P WRAPPER name validator_clause
! {
! AlterFdwStmt *n = makeNode(AlterFdwStmt);
! n->fdwname = $5;
! n->validator = $6;
! n->change_validator = true;
! $$ = (Node *) n;
! }
! | ALTER FOREIGN DATA_P WRAPPER name alter_generic_options
{
AlterFdwStmt *n = makeNode(AlterFdwStmt);
n->fdwname = $5;
! n->options = $6;
$$ = (Node *) n;
}
;
--- 3282,3301 ----
*
****************************************************************************/
! AlterFdwStmt: ALTER FOREIGN DATA_P WRAPPER name opt_func_options alter_generic_options
{
AlterFdwStmt *n = makeNode(AlterFdwStmt);
n->fdwname = $5;
! n->func_options = $6;
n->options = $7;
$$ = (Node *) n;
}
! | ALTER FOREIGN DATA_P WRAPPER name func_options
{
AlterFdwStmt *n = makeNode(AlterFdwStmt);
n->fdwname = $5;
! n->func_options = $6;
! n->options = NIL;
$$ = (Node *) n;
}
;
*************** AlterForeignServerStmt: ALTER SERVER nam
*** 3371,3376 ****
--- 3467,3771 ----
/*****************************************************************************
*
* QUERY:
+ * CREATE FORIGN TABLE relname (...) SERVER name (...)
+ *
+ *****************************************************************************/
+
+ CreateForeignTableStmt:
+ CREATE FOREIGN TABLE qualified_name
+ OptForeignTableElementList OptInherit
+ SERVER name create_generic_options
+ {
+ CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+ $4->istemp = false;
+ n->base.relation = $4;
+ n->base.tableElts = $5;
+ n->base.inhRelations = $6;
+ /* FDW-specific data */
+ n->servername = $8;
+ n->options = $9;
+ $$ = (Node *) n;
+ }
+ ;
+
+ OptForeignTableElementList:
+ '(' ForeignTableElementList ')' { $$ = $2; }
+ | '(' ')' { $$ = NIL; }
+ | /*EMPTY*/
+ {
+ /* TODO: import definitions from foreign server in this case. */
+ $$ = NIL;
+ }
+ ;
+
+ ForeignTableElementList:
+ ForeignTableElement
+ {
+ $$ = list_make1($1);
+ }
+ | ForeignTableElementList ',' ForeignTableElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
+ ForeignTableElement:
+ foreignColumnDef { $$ = $1; }
+ | ForeignTableLikeClause { $$ = $1; }
+ | ForeignTableConstraint { $$ = $1; }
+ ;
+
+ foreignColumnDef: ColId Typename create_generic_options ForeignColQualList
+ {
+ ColumnDef *n = makeNode(ColumnDef);
+ n->colname = $1;
+ n->typeName = $2;
+ n->constraints = $4;
+ n->genoptions = $3;
+ n->is_local = true;
+ $$ = (Node *)n;
+ }
+ ;
+
+ ForeignColQualList:
+ ForeignColQualList ForeignColConstraint { $$ = lappend($1, $2); }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
+ ForeignColConstraint:
+ CONSTRAINT name ForeignColConstraintElem
+ {
+ Constraint *n = (Constraint *) $3;
+ Assert(IsA(n, Constraint));
+ n->conname = $2;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ | ForeignColConstraintElem { $$ = $1; }
+ | DEFAULT b_expr
+ {
+ Constraint *n = makeNode(Constraint);
+ n->contype = CONSTR_DEFAULT;
+ n->location = @1;
+ n->raw_expr = $2;
+ n->cooked_expr = NULL;
+ $$ = (Node *)n;
+ }
+ ;
+
+ ForeignColConstraintElem:
+ ColNotNullConstraintElem { $$ = $1; }
+ | ColNullConstraintElem { $$ = $1; }
+ | ColCheckConstraintElem { $$ = $1; }
+ ;
+
+ /*****************************************************************************
+ *
+ * QUERY:
+ * ALTER FORIGN TABLE relname ADD [COLUMN] column-definition;
+ * ALTER FORIGN TABLE relname ALTERR [COLUMN] name OPTIONS (...);
+ * ALTER FORIGN TABLE relname DROP [COLUMN] name [CASCADE];
+ * ALTER FORIGN TABLE relname OPTIONS (...);
+ *
+ *****************************************************************************/
+ AlterForeignTableStmt:
+ ALTER FOREIGN TABLE relation_expr alter_foreign_table_cmds
+ {
+ AlterTableStmt *n = makeNode(AlterTableStmt);
+ n->relation = $4;
+ n->cmds = $5;
+ n->relkind = OBJECT_FOREIGN_TABLE;
+ $$ = (Node *)n;
+ }
+ ;
+
+ alter_foreign_table_cmds:
+ alter_foreign_table_cmd { $$ = list_make1($1); }
+ | alter_foreign_table_cmds ',' alter_foreign_table_cmd
+ { $$ = lappend($1, $3); }
+ ;
+
+ alter_foreign_table_cmd:
+ /* ALTER FOREIGN TABLE ADD */
+ ADD_P columnDef
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_AddColumn;
+ n->def = $2;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE ADD COLUMN */
+ | ADD_P COLUMN columnDef
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_AddColumn;
+ n->def = $3;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE ALTER [COLUMN] {SET DEFAULT |DROP DEFAULT} */
+ | ALTER opt_column ColId alter_column_default
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ColumnDefault;
+ n->name = $3;
+ n->def = $4;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE ALTER [COLUMN] DROP NOT NULL */
+ | ALTER opt_column ColId DROP NOT NULL_P
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DropNotNull;
+ n->name = $3;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE ALTER [COLUMN] SET NOT NULL */
+ | ALTER opt_column ColId SET NOT NULL_P
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_SetNotNull;
+ n->name = $3;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE DROP [COLUMN] IF EXISTS [RESTRICT|CASCADE] */
+ | DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DropColumn;
+ n->name = $5;
+ n->behavior = $6;
+ n->missing_ok = TRUE;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE DROP [COLUMN] [RESTRICT|CASCADE] */
+ | DROP opt_column ColId opt_drop_behavior
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DropColumn;
+ n->name = $3;
+ n->behavior = $4;
+ n->missing_ok = FALSE;
+ $$ = (Node *)n;
+ }
+ /*
+ * ALTER FOREIGN TABLE ALTER [COLUMN] [SET DATA] TYPE
+ */
+ | ALTER opt_column ColId opt_set_data TYPE_P Typename
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_AlterColumnType;
+ n->name = $3;
+ n->def = (Node *) $6;
+ $$ = (Node *)n;
+ }
+ /*
+ * ALTER FOREIGN TABLE ALTER [COLUMN] OPTIONS (...)
+ */
+ | ALTER opt_column ColId alter_generic_options
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ColumnGenericOptions;
+ n->name = $3;
+ n->def = (Node *) $4;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE ADD CONSTRAINT ... */
+ | ADD_P ForeignTableConstraint
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_AddConstraint;
+ n->def = $2;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE DROP CONSTRAINT IF EXISTS [RESTRICT|CASCADE] */
+ | DROP CONSTRAINT IF_P EXISTS name opt_drop_behavior
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DropConstraint;
+ n->name = $5;
+ n->behavior = $6;
+ n->missing_ok = TRUE;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE DROP CONSTRAINT [RESTRICT|CASCADE] */
+ | DROP CONSTRAINT name opt_drop_behavior
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DropConstraint;
+ n->name = $3;
+ n->behavior = $4;
+ n->missing_ok = FALSE;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE ENABLE RULE */
+ | ENABLE_P RULE name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_EnableRule;
+ n->name = $3;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE ENABLE ALWAYS RULE */
+ | ENABLE_P ALWAYS RULE name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_EnableAlwaysRule;
+ n->name = $4;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE ENABLE REPLICA RULE */
+ | ENABLE_P REPLICA RULE name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_EnableReplicaRule;
+ n->name = $4;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE DISABLE RULE */
+ | DISABLE_P RULE name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DisableRule;
+ n->name = $3;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE INHERIT */
+ | INHERIT qualified_name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_AddInherit;
+ n->def = (Node *) $2;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE NO INHERIT */
+ | NO INHERIT qualified_name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DropInherit;
+ n->def = (Node *) $3;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE OWNER TO RoleId */
+ | OWNER TO RoleId
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ChangeOwner;
+ n->name = $3;
+ $$ = (Node *)n;
+ }
+ /* ALTER FOREIGN TABLE OPTIONS (...) */
+ | alter_generic_options
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_GenericOptions;
+ n->def = (Node *)$1;
+ $$ = (Node *) n;
+ }
+ ;
+
+ /*****************************************************************************
+ *
+ * QUERY:
* CREATE USER MAPPING FOR auth_ident SERVER name [OPTIONS]
*
*****************************************************************************/
*************** drop_type: TABLE { $$ = OBJECT_T
*** 4184,4189 ****
--- 4579,4585 ----
| TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; }
| TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; }
| TEXT_P SEARCH CONFIGURATION { $$ = OBJECT_TSCONFIGURATION; }
+ | FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; }
;
any_name_list:
*************** opt_restart_seqs:
*** 4235,4242 ****
* CONVERSION | LANGUAGE | OPERATOR CLASS | LARGE OBJECT |
* CAST | COLUMN | SCHEMA | TABLESPACE | ROLE |
* TEXT SEARCH PARSER | TEXT SEARCH DICTIONARY |
! * TEXT SEARCH TEMPLATE |
! * TEXT SEARCH CONFIGURATION ] |
* AGGREGATE (arg1, ...) |
* FUNCTION (arg1, arg2, ...) |
* OPERATOR (leftoperand_typ, rightoperand_typ) |
--- 4631,4638 ----
* CONVERSION | LANGUAGE | OPERATOR CLASS | LARGE OBJECT |
* CAST | COLUMN | SCHEMA | TABLESPACE | ROLE |
* TEXT SEARCH PARSER | TEXT SEARCH DICTIONARY |
! * TEXT SEARCH TEMPLATE | TEXT SEARCH CONFIGURATION |
! * FOREIGN TABLE ] |
* AGGREGATE (arg1, ...) |
* FUNCTION (arg1, arg2, ...) |
* OPERATOR (leftoperand_typ, rightoperand_typ) |
*************** comment_type:
*** 4413,4418 ****
--- 4809,4815 ----
| CONVERSION_P { $$ = OBJECT_CONVERSION; }
| TABLESPACE { $$ = OBJECT_TABLESPACE; }
| ROLE { $$ = OBJECT_ROLE; }
+ | FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; }
;
comment_text:
*************** privilege_target:
*** 4829,4834 ****
--- 5226,5239 ----
n->objs = $3;
$$ = n;
}
+ | FOREIGN TABLE qualified_name_list
+ {
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+ n->targtype = ACL_TARGET_OBJECT;
+ n->objtype = ACL_OBJECT_FOREIGN_TABLE;
+ n->objs = $3;
+ $$ = n;
+ }
| FUNCTION function_with_argtypes_list
{
PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
*************** RenameStmt: ALTER AGGREGATE func_name ag
*** 5915,5920 ****
--- 6320,6334 ----
n->newname = $6;
$$ = (Node *)n;
}
+ | ALTER FOREIGN TABLE relation_expr RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_FOREIGN_TABLE;
+ n->relation = $4;
+ n->subname = NULL;
+ n->newname = $7;
+ $$ = (Node *)n;
+ }
| ALTER TABLE relation_expr RENAME opt_column name TO name
{
RenameStmt *n = makeNode(RenameStmt);
*************** RenameStmt: ALTER AGGREGATE func_name ag
*** 5924,5929 ****
--- 6338,6352 ----
n->newname = $8;
$$ = (Node *)n;
}
+ | ALTER FOREIGN TABLE relation_expr RENAME opt_column name TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_COLUMN;
+ n->relation = $4;
+ n->subname = $7;
+ n->newname = $9;
+ $$ = (Node *)n;
+ }
| ALTER TRIGGER name ON qualified_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
*************** AlterObjectSchemaStmt:
*** 6092,6097 ****
--- 6515,6528 ----
n->newschema = $6;
$$ = (Node *)n;
}
+ | ALTER FOREIGN TABLE relation_expr SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_FOREIGN_TABLE;
+ n->relation = $4;
+ n->newschema = $7;
+ $$ = (Node *)n;
+ }
| ALTER TYPE_P any_name SET SCHEMA name
{
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index a8aee20..a23e644 100644
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 65,71 ****
/* State shared by transformCreateStmt and its subroutines */
typedef struct
{
! const char *stmtType; /* "CREATE TABLE" or "ALTER TABLE" */
RangeVar *relation; /* relation to create */
Relation rel; /* opened/locked rel, if ALTER */
List *inhRelations; /* relations to inherit from */
--- 65,71 ----
/* State shared by transformCreateStmt and its subroutines */
typedef struct
{
! const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */
RangeVar *relation; /* relation to create */
Relation rel; /* opened/locked rel, if ALTER */
List *inhRelations; /* relations to inherit from */
*************** transformCreateStmt(CreateStmt *stmt, co
*** 172,178 ****
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
! cxt.stmtType = "CREATE TABLE";
cxt.relation = stmt->relation;
cxt.rel = NULL;
cxt.inhRelations = stmt->inhRelations;
--- 172,181 ----
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
! if (IsA(stmt, CreateForeignServerStmt))
! cxt.stmtType = "CREATE FOREIGN TABLE";
! else
! cxt.stmtType = "CREATE TABLE";
cxt.relation = stmt->relation;
cxt.rel = NULL;
cxt.inhRelations = stmt->inhRelations;
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 029a288..2823f97 100644
*** a/src/backend/rewrite/rewriteDefine.c
--- b/src/backend/rewrite/rewriteDefine.c
*************** DefineQueryRewrite(char *rulename,
*** 249,258 ****
* Verify relation is of a type that rules can sensibly be applied to.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
! event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table or view",
RelationGetRelationName(event_relation))));
if (!allowSystemTableMods && IsSystemRelation(event_relation))
--- 249,259 ----
* Verify relation is of a type that rules can sensibly be applied to.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
! event_relation->rd_rel->relkind != RELKIND_VIEW &&
! event_relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view or foreign table",
RelationGetRelationName(event_relation))));
if (!allowSystemTableMods && IsSystemRelation(event_relation))
*************** DefineQueryRewrite(char *rulename,
*** 293,298 ****
--- 294,307 ----
if (event_type == CMD_SELECT)
{
+ /* Foreign tables can't be changed to view. */
+ if (event_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot define rule on SELECT to a foreign table")));
+ }
+
/*
* Rules ON SELECT are restricted to view definitions
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 2300e88..8cc68db 100644
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
*************** check_xact_readonly(Node *parsetree)
*** 220,225 ****
--- 220,226 ----
case T_AlterUserMappingStmt:
case T_DropUserMappingStmt:
case T_AlterTableSpaceOptionsStmt:
+ case T_CreateForeignTableStmt:
case T_SecLabelStmt:
PreventCommandIfReadOnly(CreateCommandTag(parsetree));
break;
*************** standard_ProcessUtility(Node *parsetree,
*** 492,497 ****
--- 493,499 ----
break;
case T_CreateStmt:
+ case T_CreateForeignTableStmt:
{
List *stmts;
ListCell *l;
*************** standard_ProcessUtility(Node *parsetree,
*** 540,545 ****
--- 542,557 ----
AlterTableCreateToastTable(relOid, toast_options);
}
+ else if (IsA(stmt, CreateForeignTableStmt))
+ {
+ /* Create the table itself */
+ relOid = DefineRelation((CreateStmt *) stmt,
+ RELKIND_FOREIGN_TABLE,
+ InvalidOid);
+
+ CreateForeignTable((CreateForeignTableStmt *) stmt,
+ relOid);
+ }
else
{
/* Recurse for anything else */
*************** standard_ProcessUtility(Node *parsetree,
*** 618,623 ****
--- 630,636 ----
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
case OBJECT_INDEX:
+ case OBJECT_FOREIGN_TABLE:
RemoveRelations(stmt);
break;
*************** CreateCommandTag(Node *parsetree)
*** 1557,1562 ****
--- 1570,1579 ----
tag = "DROP USER MAPPING";
break;
+ case T_CreateForeignTableStmt:
+ tag = "CREATE FOREIGN TABLE";
+ break;
+
case T_DropStmt:
switch (((DropStmt *) parsetree)->removeType)
{
*************** CreateCommandTag(Node *parsetree)
*** 1596,1601 ****
--- 1613,1621 ----
case OBJECT_TSCONFIGURATION:
tag = "DROP TEXT SEARCH CONFIGURATION";
break;
+ case OBJECT_FOREIGN_TABLE:
+ tag = "DROP FOREIGN TABLE";
+ break;
default:
tag = "???";
}
*************** CreateCommandTag(Node *parsetree)
*** 1666,1671 ****
--- 1686,1694 ----
case OBJECT_VIEW:
tag = "ALTER VIEW";
break;
+ case OBJECT_FOREIGN_TABLE:
+ tag = "ALTER FOREIGN TABLE";
+ break;
case OBJECT_TSPARSER:
tag = "ALTER TEXT SEARCH PARSER";
break;
*************** CreateCommandTag(Node *parsetree)
*** 1724,1729 ****
--- 1747,1755 ----
case OBJECT_VIEW:
tag = "ALTER VIEW";
break;
+ case OBJECT_FOREIGN_TABLE:
+ tag = "ALTER FOREIGN TABLE";
+ break;
default:
tag = "???";
break;
*************** CreateCommandTag(Node *parsetree)
*** 1784,1789 ****
--- 1810,1818 ----
case OBJECT_FOREIGN_SERVER:
tag = "ALTER SERVER";
break;
+ case OBJECT_FOREIGN_TABLE:
+ tag = "ALTER FOREIGN TABLE";
+ break;
default:
tag = "???";
break;
*************** CreateCommandTag(Node *parsetree)
*** 1808,1813 ****
--- 1837,1845 ----
case OBJECT_VIEW:
tag = "ALTER VIEW";
break;
+ case OBJECT_FOREIGN_TABLE:
+ tag = "ALTER FOREIGN TABLE";
+ break;
default:
tag = "???";
break;
*************** GetCommandLogLevel(Node *parsetree)
*** 2304,2309 ****
--- 2336,2342 ----
break;
case T_CreateStmt:
+ case T_CreateForeignTableStmt:
lev = LOGSTMT_DDL;
break;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index e2af059..6f9b925 100644
*** a/src/backend/utils/adt/acl.c
--- b/src/backend/utils/adt/acl.c
*************** acldefault(GrantObjectType objtype, Oid
*** 782,787 ****
--- 782,791 ----
world_default = ACL_NO_RIGHTS;
owner_default = ACL_ALL_RIGHTS_FOREIGN_SERVER;
break;
+ case ACL_OBJECT_FOREIGN_TABLE:
+ world_default = ACL_NO_RIGHTS;
+ owner_default = ACL_ALL_RIGHTS_FOREIGN_TABLE;
+ break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
world_default = ACL_NO_RIGHTS; /* keep compiler quiet */
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index dd12592..a08af41 100644
*** a/src/backend/utils/adt/pseudotypes.c
--- b/src/backend/utils/adt/pseudotypes.c
*************** pg_node_tree_send(PG_FUNCTION_ARGS)
*** 453,455 ****
--- 453,481 ----
{
return textsend(fcinfo);
}
+
+ /*
+ * fdw_handler_in - input routine for pseudo-type FDW_HANDLER.
+ */
+ Datum
+ fdw_handler_in(PG_FUNCTION_ARGS)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot accept a value of type fdw_handler")));
+
+ PG_RETURN_VOID(); /* keep compiler quiet */
+ }
+
+ /*
+ * fdw_handler_out - output routine for pseudo-type FDW_HANDLER.
+ */
+ Datum
+ fdw_handler_out(PG_FUNCTION_ARGS)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot display a value of type fdw_handler")));
+
+ PG_RETURN_VOID(); /* keep compiler quiet */
+ }
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 08a1443..12297fa 100644
*** a/src/backend/utils/cache/syscache.c
--- b/src/backend/utils/cache/syscache.c
***************
*** 35,40 ****
--- 35,41 ----
#include "catalog/pg_enum.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
+ #include "catalog/pg_foreign_table.h"
#include "catalog/pg_language.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
*************** static const struct cachedesc cacheinfo[
*** 398,403 ****
--- 399,415 ----
},
32
},
+ {ForeignTableRelationId, /* FOREIGNTABLEREL */
+ ForeignTableRelidIndexId,
+ 1,
+ {
+ Anum_pg_foreign_table_ftrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{IndexRelationId, /* INDEXRELID */
IndexRelidIndexId,
1,
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 14ed914..380162b 100644
*** a/src/backend/utils/init/miscinit.c
--- b/src/backend/utils/init/miscinit.c
***************
*** 30,35 ****
--- 30,36 ----
#endif
#include "catalog/pg_authid.h"
+ #include "foreign/foreign.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "postmaster/autovacuum.h"
*************** SetUserIdAndContext(Oid userid, bool sec
*** 379,384 ****
--- 380,386 ----
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("cannot set parameter \"%s\" within security-restricted operation",
"role")));
+
CurrentUserId = userid;
if (sec_def_context)
SecurityRestrictionContext |= SECURITY_LOCAL_USERID_CHANGE;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index e4a0201..6451329 100644
*** a/src/bin/pg_dump/common.c
--- b/src/bin/pg_dump/common.c
*************** flagInhTables(TableInfo *tblinfo, int nu
*** 247,253 ****
for (i = 0; i < numTables; i++)
{
! /* Sequences and views never have parents */
if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
tblinfo[i].relkind == RELKIND_VIEW)
continue;
--- 247,253 ----
for (i = 0; i < numTables; i++)
{
! /* Sequences and views and foreign tables never have parents */
if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
tblinfo[i].relkind == RELKIND_VIEW)
continue;
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index d53e908..d96a6b0 100644
*** a/src/bin/pg_dump/dumputils.c
--- b/src/bin/pg_dump/dumputils.c
*************** do { \
*** 868,873 ****
--- 868,875 ----
CONVERT_PRIV('U', "USAGE");
else if (strcmp(type, "FOREIGN SERVER") == 0)
CONVERT_PRIV('U', "USAGE");
+ else if (strcmp(type, "FOREIGN TABLE") == 0)
+ CONVERT_PRIV('r', "SELECT");
else if (strcmp(type, "LARGE OBJECT") == 0)
{
CONVERT_PRIV('r', "SELECT");
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index d1a9c54..d8c6087 100644
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
*************** _getObjectDescription(PQExpBuffer buf, T
*** 2724,2729 ****
--- 2724,2730 ----
strcmp(type, "DOMAIN") == 0 ||
strcmp(type, "TABLE") == 0 ||
strcmp(type, "TYPE") == 0 ||
+ strcmp(type, "FOREIGN TABLE") == 0 ||
strcmp(type, "TEXT SEARCH DICTIONARY") == 0 ||
strcmp(type, "TEXT SEARCH CONFIGURATION") == 0)
{
*************** _printTocEntry(ArchiveHandle *AH, TocEnt
*** 2916,2921 ****
--- 2917,2923 ----
strcmp(te->desc, "TYPE") == 0 ||
strcmp(te->desc, "VIEW") == 0 ||
strcmp(te->desc, "SEQUENCE") == 0 ||
+ strcmp(te->desc, "FOREIGN TABLE") == 0 ||
strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 ||
strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0 ||
strcmp(te->desc, "FOREIGN DATA WRAPPER") == 0 ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 66274b4..8f7ece4 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** expand_table_name_patterns(SimpleStringL
*** 970,977 ****
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
! "\nWHERE c.relkind in ('%c', '%c', '%c')\n",
! RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW);
processSQLNamePattern(g_conn, query, cell->val, true, false,
"n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
--- 970,978 ----
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
! "\nWHERE c.relkind in ('%c', '%c', '%c', '%c')\n",
! RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
! RELKIND_FOREIGN_TABLE);
processSQLNamePattern(g_conn, query, cell->val, true, false,
"n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
*************** getTableData(TableInfo *tblinfo, int num
*** 1471,1476 ****
--- 1472,1480 ----
/* Skip SEQUENCEs (handled elsewhere) */
if (tblinfo[i].relkind == RELKIND_SEQUENCE)
continue;
+ /* Skip FOREIGN TABLEs (no data to dump) */
+ if (tblinfo[i].relkind == RELKIND_FOREIGN_TABLE)
+ continue;
if (tblinfo[i].dobj.dump)
{
*************** getTables(int *numTables)
*** 3477,3483 ****
* we cannot correctly identify inherited columns, owned sequences, etc.
*/
! if (g_fout->remoteVersion >= 90000)
{
/*
* Left join to pick up dependency info linking sequences to their
--- 3481,3521 ----
* we cannot correctly identify inherited columns, owned sequences, etc.
*/
! if (g_fout->remoteVersion >= 90100)
! {
! /*
! * Left join to pick up dependency info linking sequences to their
! * owning column, if any (note this dependency is AUTO as of 8.2)
! */
! appendPQExpBuffer(query,
! "SELECT c.tableoid, c.oid, c.relname, "
! "c.relacl, c.relkind, c.relnamespace, "
! "(%s c.relowner) AS rolname, "
! "c.relchecks, c.relhastriggers, "
! "c.relhasindex, c.relhasrules, c.relhasoids, "
! "c.relfrozenxid, "
! "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
! "d.refobjid AS owning_tab, "
! "d.refobjsubid AS owning_col, "
! "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
! "array_to_string(c.reloptions, ', ') AS reloptions, "
! "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
! "FROM pg_class c "
! "LEFT JOIN pg_depend d ON "
! "(c.relkind = '%c' AND "
! "d.classid = c.tableoid AND d.objid = c.oid AND "
! "d.objsubid = 0 AND "
! "d.refclassid = c.tableoid AND d.deptype = 'a') "
! "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
! "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c') "
! "ORDER BY c.oid",
! username_subquery,
! RELKIND_SEQUENCE,
! RELKIND_RELATION, RELKIND_SEQUENCE,
! RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
! RELKIND_FOREIGN_TABLE);
! }
! else if (g_fout->remoteVersion >= 90000)
{
/*
* Left join to pick up dependency info linking sequences to their
*************** getTables(int *numTables)
*** 3829,3835 ****
* NOTE: it'd be kinda nice to lock views and sequences too, not only
* plain tables, but the backend doesn't presently allow that.
*/
! if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION)
{
resetPQExpBuffer(query);
appendPQExpBuffer(query,
--- 3867,3874 ----
* NOTE: it'd be kinda nice to lock views and sequences too, not only
* plain tables, but the backend doesn't presently allow that.
*/
! if (tblinfo[i].dobj.dump && (tblinfo[i].relkind == RELKIND_RELATION ||
! tblinfo[i].relkind == RELKIND_FOREIGN_TABLE))
{
resetPQExpBuffer(query);
appendPQExpBuffer(query,
*************** getForeignDataWrappers(int *numForeignDa
*** 5886,5891 ****
--- 5925,5931 ----
int i_fdwname;
int i_rolname;
int i_fdwvalidator;
+ int i_fdwhandler;
int i_fdwacl;
int i_fdwoptions;
*************** getForeignDataWrappers(int *numForeignDa
*** 5900,5906 ****
selectSourceSchema("pg_catalog");
appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, "
! "(%s fdwowner) AS rolname, fdwvalidator::pg_catalog.regproc, fdwacl,"
"array_to_string(ARRAY("
" SELECT option_name || ' ' || quote_literal(option_value) "
" FROM pg_options_to_table(fdwoptions)), ', ') AS fdwoptions "
--- 5940,5947 ----
selectSourceSchema("pg_catalog");
appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, "
! "(%s fdwowner) AS rolname, fdwvalidator::pg_catalog.regproc, "
! "fdwhandler::pg_catalog.regproc, fdwacl,"
"array_to_string(ARRAY("
" SELECT option_name || ' ' || quote_literal(option_value) "
" FROM pg_options_to_table(fdwoptions)), ', ') AS fdwoptions "
*************** getForeignDataWrappers(int *numForeignDa
*** 5920,5925 ****
--- 5961,5967 ----
i_fdwname = PQfnumber(res, "fdwname");
i_rolname = PQfnumber(res, "rolname");
i_fdwvalidator = PQfnumber(res, "fdwvalidator");
+ i_fdwhandler = PQfnumber(res, "fdwhandler");
i_fdwacl = PQfnumber(res, "fdwacl");
i_fdwoptions = PQfnumber(res, "fdwoptions");
*************** getForeignDataWrappers(int *numForeignDa
*** 5933,5938 ****
--- 5975,5981 ----
fdwinfo[i].dobj.namespace = NULL;
fdwinfo[i].rolname = strdup(PQgetvalue(res, i, i_rolname));
fdwinfo[i].fdwvalidator = strdup(PQgetvalue(res, i, i_fdwvalidator));
+ fdwinfo[i].fdwhandler = strdup(PQgetvalue(res, i, i_fdwhandler));
fdwinfo[i].fdwoptions = strdup(PQgetvalue(res, i, i_fdwoptions));
fdwinfo[i].fdwacl = strdup(PQgetvalue(res, i, i_fdwacl));
*************** dumpForeignDataWrapper(Archive *fout, Fd
*** 10238,10243 ****
--- 10281,10290 ----
appendPQExpBuffer(q, " VALIDATOR %s",
fdwinfo->fdwvalidator);
+ if (fdwinfo->fdwhandler && strcmp(fdwinfo->fdwhandler, "-") != 0)
+ appendPQExpBuffer(q, " HANDLER %s",
+ fdwinfo->fdwhandler);
+
if (fdwinfo->fdwoptions && strlen(fdwinfo->fdwoptions) > 0)
appendPQExpBuffer(q, " OPTIONS (%s)", fdwinfo->fdwoptions);
*************** dumpTable(Archive *fout, TableInfo *tbin
*** 10893,10899 ****
/* Handle the ACL here */
namecopy = strdup(fmtId(tbinfo->dobj.name));
dumpACL(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId,
! (tbinfo->relkind == RELKIND_SEQUENCE) ? "SEQUENCE" : "TABLE",
namecopy, NULL, tbinfo->dobj.name,
tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
tbinfo->relacl);
--- 10940,10948 ----
/* Handle the ACL here */
namecopy = strdup(fmtId(tbinfo->dobj.name));
dumpACL(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId,
! (tbinfo->relkind == RELKIND_SEQUENCE) ? "SEQUENCE" :
! (tbinfo->relkind == RELKIND_FOREIGN_TABLE) ? "FOREIGN TABLE" :
! "TABLE",
namecopy, NULL, tbinfo->dobj.name,
tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
tbinfo->relacl);
*************** dumpTableSchema(Archive *fout, TableInfo
*** 10962,10967 ****
--- 11011,11018 ----
int j,
k;
bool toast_set = false;
+ char *srvname;
+ char *ftoptions = NULL;
/* Make sure we are in proper schema */
selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
*************** dumpTableSchema(Archive *fout, TableInfo
*** 11035,11041 ****
}
else
{
! reltypename = "TABLE";
numParents = tbinfo->numParents;
parents = tbinfo->parents;
--- 11086,11120 ----
}
else
{
! if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
! {
! int i_srvname;
! int i_ftoptions;
!
! reltypename = "FOREIGN TABLE";
!
! /* retrieve name of foreign server and generic options */
! appendPQExpBuffer(query,
! "SELECT fs.srvname, array_to_string(ARRAY("
! " SELECT option_name || ' ' || quote_literal(option_value)"
! " FROM pg_options_to_table(ftoptions)), ', ') AS ftoptions "
! "FROM pg_foreign_table ft JOIN pg_foreign_server fs "
! " ON (fs.oid = ft.ftserver) "
! "WHERE ft.ftrelid = %u", tbinfo->dobj.catId.oid);
! res = PQexec(g_conn, query->data);
! check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK);
! i_srvname = PQfnumber(res, "srvname");
! i_ftoptions = PQfnumber(res, "ftoptions");
! srvname = strdup(PQgetvalue(res, 0, i_srvname));
! ftoptions = strdup(PQgetvalue(res, 0, i_ftoptions));
! PQclear(res);
! }
! else
! {
! reltypename = "TABLE";
! srvname = NULL;
! ftoptions = NULL;
! }
numParents = tbinfo->numParents;
parents = tbinfo->parents;
*************** dumpTableSchema(Archive *fout, TableInfo
*** 11043,11049 ****
* DROP must be fully qualified in case same name appears in
* pg_catalog
*/
! appendPQExpBuffer(delq, "DROP TABLE %s.",
fmtId(tbinfo->dobj.namespace->dobj.name));
appendPQExpBuffer(delq, "%s;\n",
fmtId(tbinfo->dobj.name));
--- 11122,11128 ----
* DROP must be fully qualified in case same name appears in
* pg_catalog
*/
! appendPQExpBuffer(delq, "DROP %s %s.", reltypename,
fmtId(tbinfo->dobj.namespace->dobj.name));
appendPQExpBuffer(delq, "%s;\n",
fmtId(tbinfo->dobj.name));
*************** dumpTableSchema(Archive *fout, TableInfo
*** 11051,11057 ****
if (binary_upgrade)
binary_upgrade_set_relfilenodes(q, tbinfo->dobj.catId.oid, false);
! appendPQExpBuffer(q, "CREATE TABLE %s",
fmtId(tbinfo->dobj.name));
if (tbinfo->reloftype)
appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
--- 11130,11136 ----
if (binary_upgrade)
binary_upgrade_set_relfilenodes(q, tbinfo->dobj.catId.oid, false);
! appendPQExpBuffer(q, "CREATE %s %s", reltypename,
fmtId(tbinfo->dobj.name));
if (tbinfo->reloftype)
appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
*************** dumpTableSchema(Archive *fout, TableInfo
*** 11186,11191 ****
--- 11265,11273 ----
appendPQExpBuffer(q, ")");
}
+ if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ appendPQExpBuffer(q, "\nSERVER %s", srvname);
+
if ((tbinfo->reloptions && strlen(tbinfo->reloptions) > 0) ||
(tbinfo->toast_reloptions && strlen(tbinfo->toast_reloptions) > 0))
{
*************** dumpTableSchema(Archive *fout, TableInfo
*** 11205,11210 ****
--- 11287,11296 ----
appendPQExpBuffer(q, ")");
}
+ /* Dump generic options if any */
+ if (ftoptions && ftoptions[0])
+ appendPQExpBuffer(q, "\nOPTIONS (%s)", ftoptions);
+
appendPQExpBuffer(q, ";\n");
/*
*************** dumpTableSchema(Archive *fout, TableInfo
*** 11219,11225 ****
* order. That also means we have to take care about setting
* attislocal correctly, plus fix up any inherited CHECK constraints.
*/
! if (binary_upgrade)
{
for (j = 0; j < tbinfo->numatts; j++)
{
--- 11305,11311 ----
* order. That also means we have to take care about setting
* attislocal correctly, plus fix up any inherited CHECK constraints.
*/
! if (binary_upgrade && tbinfo->relkind == RELKIND_RELATION)
{
for (j = 0; j < tbinfo->numatts; j++)
{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 7885535..5905d0f 100644
*** a/src/bin/pg_dump/pg_dump.h
--- b/src/bin/pg_dump/pg_dump.h
*************** typedef struct _fdwInfo
*** 420,425 ****
--- 420,426 ----
DumpableObject dobj;
char *rolname;
char *fdwvalidator;
+ char *fdwhandler;
char *fdwoptions;
char *fdwacl;
} FdwInfo;
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index c1edf44..a05af39 100644
*** a/src/bin/psql/command.c
--- b/src/bin/psql/command.c
*************** exec_command(const char *cmd,
*** 369,375 ****
success = describeTableDetails(pattern, show_verbose, show_system);
else
/* standard listing of interesting things */
! success = listTables("tvs", NULL, show_verbose, show_system);
break;
case 'a':
success = describeAggregates(pattern, show_verbose, show_system);
--- 369,375 ----
success = describeTableDetails(pattern, show_verbose, show_system);
else
/* standard listing of interesting things */
! success = listTables("tvsE", NULL, show_verbose, show_system);
break;
case 'a':
success = describeAggregates(pattern, show_verbose, show_system);
*************** exec_command(const char *cmd,
*** 432,437 ****
--- 432,438 ----
case 'v':
case 'i':
case 's':
+ case 'E':
success = listTables(&cmd[1], pattern, show_verbose, show_system);
break;
case 'r':
*************** exec_command(const char *cmd,
*** 483,488 ****
--- 484,492 ----
case 'w':
success = listForeignDataWrappers(pattern, show_verbose);
break;
+ case 't':
+ success = listForeignTables(pattern, show_verbose);
+ break;
default:
status = PSQL_CMD_UNKNOWN;
break;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index c4370a1..b7b722a 100644
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
*************** permissionsList(const char *pattern)
*** 687,697 ****
printfPQExpBuffer(&buf,
"SELECT n.nspname as \"%s\",\n"
" c.relname as \"%s\",\n"
! " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'S' THEN '%s' END as \"%s\",\n"
" ",
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"), gettext_noop("view"), gettext_noop("sequence"),
gettext_noop("Type"));
printACLColumn(&buf, "c.relacl");
--- 687,698 ----
printfPQExpBuffer(&buf,
"SELECT n.nspname as \"%s\",\n"
" c.relname as \"%s\",\n"
! " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'S' THEN '%s' WHEN 'f' THEN '%s' END as \"%s\",\n"
" ",
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"), gettext_noop("view"), gettext_noop("sequence"),
+ gettext_noop("foreign table"),
gettext_noop("Type"));
printACLColumn(&buf, "c.relacl");
*************** permissionsList(const char *pattern)
*** 707,713 ****
appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
! "WHERE c.relkind IN ('r', 'v', 'S')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
--- 708,714 ----
appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
! "WHERE c.relkind IN ('r', 'v', 'S', 'f')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
*************** objectDescription(const char *pattern, b
*** 921,935 ****
" n.nspname as nspname,\n"
" CAST(c.relname AS pg_catalog.text) as name,\n"
" CAST(\n"
! " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' END"
" AS pg_catalog.text) as object\n"
" FROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
! " WHERE c.relkind IN ('r', 'v', 'i', 'S')\n",
gettext_noop("table"),
gettext_noop("view"),
gettext_noop("index"),
! gettext_noop("sequence"));
if (!showSystem && !pattern)
appendPQExpBuffer(&buf, " AND n.nspname <> 'pg_catalog'\n"
--- 922,937 ----
" n.nspname as nspname,\n"
" CAST(c.relname AS pg_catalog.text) as name,\n"
" CAST(\n"
! " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' WHEN 'f' THEN '%s' END"
" AS pg_catalog.text) as object\n"
" FROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
! " WHERE c.relkind IN ('r', 'v', 'i', 'S', 'f')\n",
gettext_noop("table"),
gettext_noop("view"),
gettext_noop("index"),
! gettext_noop("sequence"),
! gettext_noop("foreign table"));
if (!showSystem && !pattern)
appendPQExpBuffer(&buf, " AND n.nspname <> 'pg_catalog'\n"
*************** describeOneTableDetails(const char *sche
*** 1256,1261 ****
--- 1258,1265 ----
appendPQExpBuffer(&buf, ",\n pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef");
if (verbose)
appendPQExpBuffer(&buf, ",\n a.attstorage, pg_catalog.col_description(a.attrelid, a.attnum)");
+ if (pset.sversion >= 90100 && tableinfo.relkind == 'f' && verbose)
+ appendPQExpBuffer(&buf, ",\n a.attgenoptions");
appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_attribute a");
appendPQExpBuffer(&buf, "\nWHERE a.attrelid = '%s' AND a.attnum > 0 AND NOT a.attisdropped", oid);
appendPQExpBuffer(&buf, "\nORDER BY a.attnum");
*************** describeOneTableDetails(const char *sche
*** 1297,1302 ****
--- 1301,1310 ----
printfPQExpBuffer(&title, _("Composite type \"%s.%s\""),
schemaname, relationname);
break;
+ case 'f':
+ printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
*************** describeOneTableDetails(const char *sche
*** 1309,1315 ****
headers[1] = gettext_noop("Type");
cols = 2;
! if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v')
{
show_modifiers = true;
headers[cols++] = gettext_noop("Modifiers");
--- 1317,1324 ----
headers[1] = gettext_noop("Type");
cols = 2;
! if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
! tableinfo.relkind == 'f')
{
show_modifiers = true;
headers[cols++] = gettext_noop("Modifiers");
*************** describeOneTableDetails(const char *sche
*** 1328,1333 ****
--- 1337,1345 ----
headers[cols++] = gettext_noop("Description");
}
+ if (pset.sversion >= 90100 && tableinfo.relkind == 'f' && verbose)
+ headers[cols++] = gettext_noop("Options");
+
printTableInit(&cont, &myopt, title.data, cols, numrows);
printTableInitialized = true;
*************** describeOneTableDetails(const char *sche
*** 1406,1411 ****
--- 1418,1430 ----
false, false);
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
+
+ /* Options: for foreign tables only */
+ if (pset.sversion >= 90100 && tableinfo.relkind == 'f')
+ {
+ char *options = PQgetvalue(res, i, firstvcol + 2);
+ printTableAddCell(&cont, options, false, false);
+ }
}
}
*************** describeOneTableDetails(const char *sche
*** 1537,1543 ****
PQclear(result);
}
}
! else if (tableinfo.relkind == 'r')
{
/* Footer information about a table */
PGresult *result = NULL;
--- 1556,1562 ----
PQclear(result);
}
}
! else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
{
/* Footer information about a table */
PGresult *result = NULL;
*************** describeOneTableDetails(const char *sche
*** 1956,1966 ****
/*
* Finish printing the footer information about a table.
*/
! if (tableinfo.relkind == 'r')
{
PGresult *result;
int tuples;
/* print inherited tables */
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno", oid);
--- 1975,2010 ----
/*
* Finish printing the footer information about a table.
*/
! if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
{
PGresult *result;
int tuples;
+ /* print foreign server name */
+ if (tableinfo.relkind == 'f')
+ {
+ /* Footer information about foreign table */
+ printfPQExpBuffer(&buf,
+ "SELECT s.srvname\n"
+ "FROM pg_catalog.pg_foreign_table f,\n"
+ " pg_catalog.pg_foreign_server s\n"
+ "WHERE f.ftrelid = %s AND s.oid = f.ftserver",
+ oid);
+ result = PSQLexec(buf.data, false);
+ if (!result)
+ goto error_return;
+ else if (PQntuples(result) != 1)
+ {
+ PQclear(result);
+ goto error_return;
+ }
+
+ printfPQExpBuffer(&buf, "Server: %s",
+ PQgetvalue(result, 0, 0));
+ printTableAddFooter(&cont, buf.data);
+ PQclear(result);
+ }
+
/* print inherited tables */
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno", oid);
*************** listDbRoleSettings(const char *pattern,
*** 2370,2377 ****
* i - indexes
* v - views
* s - sequences
* (any order of the above is fine)
! * If tabtypes is empty, we default to \dtvs.
*/
bool
listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSystem)
--- 2414,2422 ----
* i - indexes
* v - views
* s - sequences
+ * E - foreign table (Note: different from 'f', the relkind value)
* (any order of the above is fine)
! * If tabtypes is empty, we default to \dtvsE.
*/
bool
listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSystem)
*************** listTables(const char *tabtypes, const c
*** 2380,2393 ****
bool showIndexes = strchr(tabtypes, 'i') != NULL;
bool showViews = strchr(tabtypes, 'v') != NULL;
bool showSeq = strchr(tabtypes, 's') != NULL;
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
static const bool translate_columns[] = {false, false, true, false, false, false, false};
! if (!(showTables || showIndexes || showViews || showSeq))
! showTables = showViews = showSeq = true;
initPQExpBuffer(&buf);
--- 2425,2439 ----
bool showIndexes = strchr(tabtypes, 'i') != NULL;
bool showViews = strchr(tabtypes, 'v') != NULL;
bool showSeq = strchr(tabtypes, 's') != NULL;
+ bool showForeign = strchr(tabtypes, 'E') != NULL;
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
static const bool translate_columns[] = {false, false, true, false, false, false, false};
! if (!(showTables || showIndexes || showViews || showSeq || showForeign))
! showTables = showViews = showSeq = showForeign = true;
initPQExpBuffer(&buf);
*************** listTables(const char *tabtypes, const c
*** 2398,2404 ****
printfPQExpBuffer(&buf,
"SELECT n.nspname as \"%s\",\n"
" c.relname as \"%s\",\n"
! " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' WHEN 's' THEN '%s' END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
gettext_noop("Name"),
--- 2444,2450 ----
printfPQExpBuffer(&buf,
"SELECT n.nspname as \"%s\",\n"
" c.relname as \"%s\",\n"
! " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' WHEN 's' THEN '%s' WHEN 'f' THEN '%s' END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
gettext_noop("Name"),
*************** listTables(const char *tabtypes, const c
*** 2407,2412 ****
--- 2453,2459 ----
gettext_noop("index"),
gettext_noop("sequence"),
gettext_noop("special"),
+ gettext_noop("foreign table"),
gettext_noop("Type"),
gettext_noop("Owner"));
*************** listTables(const char *tabtypes, const c
*** 2444,2449 ****
--- 2491,2499 ----
if (showSystem || pattern)
appendPQExpBuffer(&buf, "'s',"); /* was RELKIND_SPECIAL in <=
* 8.1 */
+ if (showForeign)
+ appendPQExpBuffer(&buf, "'f',");
+
appendPQExpBuffer(&buf, "''"); /* dummy */
appendPQExpBuffer(&buf, ")\n");
*************** listForeignDataWrappers(const char *patt
*** 3338,3346 ****
--- 3388,3398 ----
printfPQExpBuffer(&buf,
"SELECT fdwname AS \"%s\",\n"
" pg_catalog.pg_get_userbyid(fdwowner) AS \"%s\",\n"
+ " fdwhandler::pg_catalog.regproc AS \"%s\",\n"
" fdwvalidator::pg_catalog.regproc AS \"%s\"",
gettext_noop("Name"),
gettext_noop("Owner"),
+ gettext_noop("Handler"),
gettext_noop("Validator"));
if (verbose)
*************** listUserMappings(const char *pattern, bo
*** 3494,3499 ****
--- 3546,3612 ----
}
/*
+ * \det
+ *
+ * Describes foreign tables.
+ */
+ bool
+ listForeignTables(const char *pattern, bool verbose)
+ {
+ PQExpBufferData buf;
+ PGresult *res;
+ printQueryOpt myopt = pset.popt;
+
+ if (pset.sversion < 90100)
+ {
+ fprintf(stderr, _("The server (version %d.%d) does not support foreign table.\n"),
+ pset.sversion / 10000, (pset.sversion / 100) % 100);
+ return true;
+ }
+
+ initPQExpBuffer(&buf);
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname AS \"%s\",\n"
+ " c.relname AS \"%s\",\n"
+ " s.srvname AS \"%s\"",
+ gettext_noop("Schema"),
+ gettext_noop("Table"),
+ gettext_noop("Server"));
+
+ if (verbose)
+ appendPQExpBuffer(&buf,
+ ",\n ft.ftoptions AS \"%s\"",
+ gettext_noop("Options"));
+
+ appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_foreign_table ft,");
+ appendPQExpBuffer(&buf, "\n pg_catalog.pg_class c,");
+ appendPQExpBuffer(&buf, "\n pg_catalog.pg_namespace n,");
+ appendPQExpBuffer(&buf, "\n pg_catalog.pg_foreign_server s\n");
+ appendPQExpBuffer(&buf, "\nWHERE c.oid = ft.ftrelid");
+ appendPQExpBuffer(&buf, "\nAND s.oid = ft.ftserver\n");
+ appendPQExpBuffer(&buf, "\nAND n.oid = c.relnamespace\n");
+
+ processSQLNamePattern(pset.db, &buf, pattern, true, false,
+ NULL, "n.nspname", "c.relname", NULL);
+
+ appendPQExpBuffer(&buf, "ORDER BY 1, 2;");
+
+ res = PSQLexec(buf.data, false);
+ termPQExpBuffer(&buf);
+ if (!res)
+ return false;
+
+ myopt.nullPrint = NULL;
+ myopt.title = _("List of foreign tables");
+ myopt.translate_header = true;
+
+ printQuery(res, &myopt, pset.queryFout, pset.logfile);
+
+ PQclear(res);
+ return true;
+ }
+
+ /*
* printACLColumn
*
* Helper function for consistently formatting ACL (privilege) columns.
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 6a6abdb..486babc 100644
*** a/src/bin/psql/describe.h
--- b/src/bin/psql/describe.h
*************** extern bool listForeignServers(const cha
*** 81,85 ****
--- 81,88 ----
/* \deu */
extern bool listUserMappings(const char *pattern, bool verbose);
+ /* \det */
+ extern bool listForeignTables(const char *pattern, bool verbose);
+
#endif /* DESCRIBE_H */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 4458d63..463438d 100644
*** a/src/bin/psql/help.c
--- b/src/bin/psql/help.c
*************** slashUsage(unsigned short int pager)
*** 158,164 ****
{
FILE *output;
! output = PageOutput(90, pager);
/* if you add/remove a line here, change the row count above */
--- 158,164 ----
{
FILE *output;
! output = PageOutput(92, pager);
/* if you add/remove a line here, change the row count above */
*************** slashUsage(unsigned short int pager)
*** 199,204 ****
--- 199,205 ----
fprintf(output, _(" \\dd[S] [PATTERN] show comments on objects\n"));
fprintf(output, _(" \\ddp [PATTERN] list default privileges\n"));
fprintf(output, _(" \\dD[S] [PATTERN] list domains\n"));
+ fprintf(output, _(" \\det[+] [PATTERN] list foreign tables\n"));
fprintf(output, _(" \\des[+] [PATTERN] list foreign servers\n"));
fprintf(output, _(" \\deu[+] [PATTERN] list user mappings\n"));
fprintf(output, _(" \\dew[+] [PATTERN] list foreign-data wrappers\n"));
*************** slashUsage(unsigned short int pager)
*** 219,224 ****
--- 220,226 ----
fprintf(output, _(" \\dT[S+] [PATTERN] list data types\n"));
fprintf(output, _(" \\du[+] [PATTERN] list roles\n"));
fprintf(output, _(" \\dv[S+] [PATTERN] list views\n"));
+ fprintf(output, _(" \\dE[S+] [PATTERN] list foreign tables\n"));
fprintf(output, _(" \\l[+] list all databases\n"));
fprintf(output, _(" \\sf[+] FUNCNAME show a function's definition\n"));
fprintf(output, _(" \\z [PATTERN] same as \\dp\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2c36850..2363744 100644
*** a/src/bin/psql/tab-complete.c
--- b/src/bin/psql/tab-complete.c
*************** static const SchemaQuery Query_for_list_
*** 288,293 ****
--- 288,308 ----
NULL
};
+ static const SchemaQuery Query_for_list_of_foreign_tables = {
+ /* catname */
+ "pg_catalog.pg_class c",
+ /* selcondition */
+ "c.relkind IN ('f')",
+ /* viscondition */
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ /* namespace */
+ "c.relnamespace",
+ /* result */
+ "pg_catalog.quote_ident(c.relname)",
+ /* qualresult */
+ NULL
+ };
+
static const SchemaQuery Query_for_list_of_tables = {
/* catname */
"pg_catalog.pg_class c",
*************** static const SchemaQuery Query_for_list_
*** 303,313 ****
NULL
};
! static const SchemaQuery Query_for_list_of_tisv = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
! "c.relkind IN ('r', 'i', 'S', 'v')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
--- 318,328 ----
NULL
};
! static const SchemaQuery Query_for_list_of_tisvf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
! "c.relkind IN ('r', 'i', 'S', 'v', 'f')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
*************** static const SchemaQuery Query_for_list_
*** 318,328 ****
NULL
};
! static const SchemaQuery Query_for_list_of_tsv = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
! "c.relkind IN ('r', 'S', 'v')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
--- 333,343 ----
NULL
};
! static const SchemaQuery Query_for_list_of_tsvf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
! "c.relkind IN ('r', 'S', 'v', 'f')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
*************** static const pgsql_thing_t words_after_c
*** 541,546 ****
--- 556,562 ----
{"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, true},
{"DOMAIN", NULL, &Query_for_list_of_domains},
{"FOREIGN DATA WRAPPER", NULL, NULL},
+ {"FOREIGN TABLE", NULL, NULL},
{"FUNCTION", NULL, &Query_for_list_of_functions},
{"GROUP", Query_for_list_of_roles},
{"LANGUAGE", Query_for_list_of_languages},
*************** psql_completion(char *text, int start, i
*** 645,651 ****
static const char *const backslash_commands[] = {
"\\a", "\\connect", "\\conninfo", "\\C", "\\cd", "\\copy", "\\copyright",
! "\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\des", "\\deu", "\\dew", "\\df",
"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl",
"\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du",
"\\e", "\\echo", "\\ef", "\\encoding",
--- 661,667 ----
static const char *const backslash_commands[] = {
"\\a", "\\connect", "\\conninfo", "\\C", "\\cd", "\\copy", "\\copyright",
! "\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\des", "\\det", "\\deu", "\\dew", "\\df",
"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl",
"\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du",
"\\e", "\\echo", "\\ef", "\\encoding",
*************** psql_completion(char *text, int start, i
*** 708,714 ****
pg_strcasecmp(prev3_wd, "TABLE") != 0)
{
static const char *const list_ALTER[] =
! {"AGGREGATE", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN", "FOREIGN DATA WRAPPER", "FUNCTION",
"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "OPERATOR", "ROLE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE",
"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", "USER", "USER MAPPING FOR", "VIEW", NULL};
--- 724,730 ----
pg_strcasecmp(prev3_wd, "TABLE") != 0)
{
static const char *const list_ALTER[] =
! {"AGGREGATE", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "OPERATOR", "ROLE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE",
"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", "USER", "USER MAPPING FOR", "VIEW", NULL};
*************** psql_completion(char *text, int start, i
*** 762,767 ****
--- 778,793 ----
COMPLETE_WITH_LIST(list_ALTERDATABASE);
}
+ /* ALTER FOREIGN */
+ else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
+ pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+ {
+ static const char *const list_ALTER_FOREIGN[] =
+ {"DATA WRAPPER", "TABLE", NULL};
+
+ COMPLETE_WITH_LIST(list_ALTER_FOREIGN);
+ }
+
/* ALTER FOREIGN DATA WRAPPER */
else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
*************** psql_completion(char *text, int start, i
*** 774,779 ****
--- 800,817 ----
COMPLETE_WITH_LIST(list_ALTER_FDW);
}
+ /* ALTER FOREIGN TABLE */
+ else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+ pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
+ pg_strcasecmp(prev2_wd, "TABLE") == 0)
+ {
+ static const char *const list_ALTER_FOREIGN_TABLE[] =
+ {"ADD", "ALTER", "DISABLE", "DROP", "ENABLE", "INHERIT",
+ "NO INHERIT", "RENAME", "RESET", "OWNER TO", NULL};
+
+ COMPLETE_WITH_LIST(list_ALTER_FOREIGN_TABLE);
+ }
+
/* ALTER INDEX */
else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
pg_strcasecmp(prev2_wd, "INDEX") == 0)
*************** psql_completion(char *text, int start, i
*** 1383,1389 ****
pg_strcasecmp(prev_wd, "ON") == 0)
{
static const char *const list_COMMENT[] =
! {"CAST", "CONVERSION", "DATABASE", "INDEX", "LANGUAGE", "RULE", "SCHEMA",
"SEQUENCE", "TABLE", "TYPE", "VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
"OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
"TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
--- 1421,1427 ----
pg_strcasecmp(prev_wd, "ON") == 0)
{
static const char *const list_COMMENT[] =
! {"CAST", "CONVERSION", "DATABASE", "FOREIGN TABLE", "INDEX", "LANGUAGE", "RULE", "SCHEMA",
"SEQUENCE", "TABLE", "TYPE", "VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
"OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
"TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
*************** psql_completion(char *text, int start, i
*** 1476,1481 ****
--- 1514,1529 ----
pg_strcasecmp(prev_wd, "TEMPLATE") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
+ /* CREATE FOREIGN */
+ else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
+ pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+ {
+ static const char *const list_CREATE_FOREIGN[] =
+ {"DATA WRAPPER", "TABLE", NULL};
+
+ COMPLETE_WITH_LIST(list_CREATE_FOREIGN);
+ }
+
/* CREATE FOREIGN DATA WRAPPER */
else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
*************** psql_completion(char *text, int start, i
*** 1483,1488 ****
--- 1531,1548 ----
pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
COMPLETE_WITH_CONST("VALIDATOR");
+ /* CREATE FOREIGN TABLE name (...) */
+ else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
+ pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
+ pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
+ prev_wd[strlen(prev_wd) - 1] == ')')
+ {
+ static const char *const list_CREATE_FOREIGN_TABLE[] =
+ {"INHERTIS", "SERVER", NULL};
+
+ COMPLETE_WITH_LIST(list_CREATE_FOREIGN_TABLE);
+ }
+
/* CREATE INDEX */
/* First off we complete CREATE UNIQUE with "INDEX" */
else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
*************** psql_completion(char *text, int start, i
*** 1846,1851 ****
--- 1906,1919 ----
COMPLETE_WITH_LIST(list_DROPCR);
}
}
+ else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
+ pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+ {
+ static const char *const drop_CREATE_FOREIGN[] =
+ {"DATA WRAPPER", "TABLE", NULL};
+
+ COMPLETE_WITH_LIST(drop_CREATE_FOREIGN);
+ }
else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
(pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
pg_strcasecmp(prev3_wd, "FUNCTION") == 0) &&
*************** psql_completion(char *text, int start, i
*** 1950,1955 ****
--- 2018,2029 ----
pg_strcasecmp(prev_wd, "WRAPPER") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+ /* FOREIGN TABLE */
+ else if (pg_strcasecmp(prev3_wd, "CREATE") != 0 &&
+ pg_strcasecmp(prev2_wd, "FOREIGN") == 0 &&
+ pg_strcasecmp(prev_wd, "TABLE") == 0)
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+
/* GRANT && REVOKE */
/* Complete GRANT/REVOKE with a list of privileges */
else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
*************** psql_completion(char *text, int start, i
*** 1981,1990 ****
else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
pg_strcasecmp(prev_wd, "ON") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv,
" UNION SELECT 'DATABASE'"
" UNION SELECT 'FOREIGN DATA WRAPPER'"
" UNION SELECT 'FOREIGN SERVER'"
" UNION SELECT 'FUNCTION'"
" UNION SELECT 'LANGUAGE'"
" UNION SELECT 'LARGE OBJECT'"
--- 2055,2065 ----
else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
pg_strcasecmp(prev_wd, "ON") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf,
" UNION SELECT 'DATABASE'"
" UNION SELECT 'FOREIGN DATA WRAPPER'"
" UNION SELECT 'FOREIGN SERVER'"
+ " UNION SELECT 'FOREIGN TABLE'"
" UNION SELECT 'FUNCTION'"
" UNION SELECT 'LANGUAGE'"
" UNION SELECT 'LARGE OBJECT'"
*************** psql_completion(char *text, int start, i
*** 1996,2002 ****
pg_strcasecmp(prev_wd, "FOREIGN") == 0)
{
static const char *const list_privilege_foreign[] =
! {"DATA WRAPPER", "SERVER", NULL};
COMPLETE_WITH_LIST(list_privilege_foreign);
}
--- 2071,2077 ----
pg_strcasecmp(prev_wd, "FOREIGN") == 0)
{
static const char *const list_privilege_foreign[] =
! {"DATA WRAPPER", "SERVER", "TABLE", NULL};
COMPLETE_WITH_LIST(list_privilege_foreign);
}
*************** psql_completion(char *text, int start, i
*** 2517,2523 ****
else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
pg_strcasecmp(prev3_wd, "COPY") != 0 &&
pg_strcasecmp(prev3_wd, "\\copy") != 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL);
/* Backslash commands */
/* TODO: \dc \dd \dl */
--- 2592,2598 ----
else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
pg_strcasecmp(prev3_wd, "COPY") != 0 &&
pg_strcasecmp(prev3_wd, "\\copy") != 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, NULL);
/* Backslash commands */
/* TODO: \dc \dd \dl */
*************** psql_completion(char *text, int start, i
*** 2555,2561 ****
COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
|| strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL);
else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
--- 2630,2636 ----
COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
|| strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, NULL);
else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
*************** psql_completion(char *text, int start, i
*** 2570,2576 ****
/* must be at end of \d list */
else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tisv, NULL);
else if (strcmp(prev_wd, "\\ef") == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
--- 2645,2651 ----
/* must be at end of \d list */
else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tisvf, NULL);
else if (strcmp(prev_wd, "\\ef") == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 87f853d..a279260 100644
*** a/src/include/catalog/dependency.h
--- b/src/include/catalog/dependency.h
*************** typedef enum ObjectClass
*** 137,142 ****
--- 137,143 ----
OCLASS_FDW, /* pg_foreign_data_wrapper */
OCLASS_FOREIGN_SERVER, /* pg_foreign_server */
OCLASS_USER_MAPPING, /* pg_user_mapping */
+ OCLASS_FOREIGN_TABLE, /* pg_foreign_table */
OCLASS_DEFACL, /* pg_default_acl */
MAX_OCLASS /* MUST BE LAST */
} ObjectClass;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 1542c8d..938add5 100644
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
*************** DECLARE_UNIQUE_INDEX(pg_user_mapping_oid
*** 275,280 ****
--- 275,283 ----
DECLARE_UNIQUE_INDEX(pg_user_mapping_user_server_index, 175, on pg_user_mapping using btree(umuser oid_ops, umserver oid_ops));
#define UserMappingUserServerIndexId 175
+ DECLARE_UNIQUE_INDEX(pg_foreign_table_relid_index, 3119, on pg_foreign_table using btree(ftrelid oid_ops));
+ #define ForeignTableRelidIndexId 3119
+
DECLARE_UNIQUE_INDEX(pg_default_acl_role_nsp_obj_index, 827, on pg_default_acl using btree(defaclrole oid_ops, defaclnamespace oid_ops, defaclobjtype char_ops));
#define DefaultAclRoleNspObjIndexId 827
DECLARE_UNIQUE_INDEX(pg_default_acl_oid_index, 828, on pg_default_acl using btree(oid oid_ops));
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 2a1b593..866fb49 100644
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
*************** CATALOG(pg_attribute,1249) BKI_BOOTSTRAP
*** 153,158 ****
--- 153,161 ----
/* Column-level options */
text attoptions[1];
+
+ /* Column-level generic options */
+ text attgenoptions[1];
} FormData_pg_attribute;
/*
*************** typedef FormData_pg_attribute *Form_pg_a
*** 176,182 ****
* ----------------
*/
! #define Natts_pg_attribute 19
#define Anum_pg_attribute_attrelid 1
#define Anum_pg_attribute_attname 2
#define Anum_pg_attribute_atttypid 3
--- 179,185 ----
* ----------------
*/
! #define Natts_pg_attribute 20
#define Anum_pg_attribute_attrelid 1
#define Anum_pg_attribute_attname 2
#define Anum_pg_attribute_atttypid 3
*************** typedef FormData_pg_attribute *Form_pg_a
*** 196,201 ****
--- 199,205 ----
#define Anum_pg_attribute_attinhcount 17
#define Anum_pg_attribute_attacl 18
#define Anum_pg_attribute_attoptions 19
+ #define Anum_pg_attribute_attgenoptions 20
/* ----------------
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index f50cf9d..c2614cd 100644
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
*************** typedef FormData_pg_class *Form_pg_class
*** 134,140 ****
/* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 28 0 t f f f f f 3 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f f r 25 0 t f f f f f 3 _null_ _null_ ));
DESCR("");
--- 134,140 ----
/* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f f r 28 0 t f f f f f 3 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f f r 20 0 f f f f f f 3 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f f r 25 0 t f f f f f 3 _null_ _null_ ));
DESCR("");
*************** DESCR("");
*** 148,152 ****
--- 148,153 ----
#define RELKIND_TOASTVALUE 't' /* moved off huge values */
#define RELKIND_VIEW 'v' /* view */
#define RELKIND_COMPOSITE_TYPE 'c' /* composite type */
+ #define RELKIND_FOREIGN_TABLE 'f' /* foreign table */
#endif /* PG_CLASS_H */
diff --git a/src/include/catalog/pg_foreign_data_wrapper.h b/src/include/catalog/pg_foreign_data_wrapper.h
index 6dd01e6..b1fc2ad 100644
*** a/src/include/catalog/pg_foreign_data_wrapper.h
--- b/src/include/catalog/pg_foreign_data_wrapper.h
*************** CATALOG(pg_foreign_data_wrapper,2328)
*** 33,38 ****
--- 33,39 ----
NameData fdwname; /* foreign-data wrapper name */
Oid fdwowner; /* FDW owner */
Oid fdwvalidator; /* optional validation function */
+ Oid fdwhandler; /* foreign-data routines function */
/* VARIABLE LENGTH FIELDS start here. */
*************** typedef FormData_pg_foreign_data_wrapper
*** 52,62 ****
* ----------------
*/
! #define Natts_pg_foreign_data_wrapper 5
#define Anum_pg_foreign_data_wrapper_fdwname 1
#define Anum_pg_foreign_data_wrapper_fdwowner 2
#define Anum_pg_foreign_data_wrapper_fdwvalidator 3
! #define Anum_pg_foreign_data_wrapper_fdwacl 4
! #define Anum_pg_foreign_data_wrapper_fdwoptions 5
#endif /* PG_FOREIGN_DATA_WRAPPER_H */
--- 53,64 ----
* ----------------
*/
! #define Natts_pg_foreign_data_wrapper 6
#define Anum_pg_foreign_data_wrapper_fdwname 1
#define Anum_pg_foreign_data_wrapper_fdwowner 2
#define Anum_pg_foreign_data_wrapper_fdwvalidator 3
! #define Anum_pg_foreign_data_wrapper_fdwhandler 4
! #define Anum_pg_foreign_data_wrapper_fdwacl 5
! #define Anum_pg_foreign_data_wrapper_fdwoptions 6
#endif /* PG_FOREIGN_DATA_WRAPPER_H */
diff --git a/src/include/catalog/pg_foreign_table.h b/src/include/catalog/pg_foreign_table.h
index ...2492ca1 .
*** a/src/include/catalog/pg_foreign_table.h
--- b/src/include/catalog/pg_foreign_table.h
***************
*** 0 ****
--- 1,53 ----
+ /*-------------------------------------------------------------------------
+ *
+ * pg_foreign_table.h
+ * definition of the system "foreign table" relation (pg_foreign_table)
+ *
+ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL$
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef PG_FOREIGN_TABLE_H
+ #define PG_FOREIGN_TABLE_H
+
+ #include "catalog/genbki.h"
+
+ /* ----------------
+ * pg_foreign_table definition. cpp turns this into
+ * typedef struct FormData_pg_foreign_table
+ * ----------------
+ */
+ #define ForeignTableRelationId 3118
+
+ CATALOG(pg_foreign_table,3118) BKI_WITHOUT_OIDS
+ {
+ Oid ftrelid; /* OID of foreign table */
+ Oid ftserver; /* OID of foreign server */
+ text ftoptions[1]; /* FDW-specific options */
+ } FormData_pg_foreign_table;
+
+ /* ----------------
+ * Form_pg_foreign_table corresponds to a pointer to a tuple with
+ * the format of pg_foreign_table relation.
+ * ----------------
+ */
+ typedef FormData_pg_foreign_table *Form_pg_foreign_table;
+
+ /* ----------------
+ * compiler constants for pg_foreign_table
+ * ----------------
+ */
+
+ #define Natts_pg_foreign_table 3
+ #define Anum_pg_foreign_table_ftrelid 1
+ #define Anum_pg_foreign_table_ftserver 2
+ #define Anum_pg_foreign_table_ftoptions 3
+
+ #endif /* PG_FOREIGN_TABLE_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 25a3912..4cf4f72 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2777 ( anynonarray_in
*** 3871,3876 ****
--- 3871,3880 ----
DESCR("I/O");
DATA(insert OID = 2778 ( anynonarray_out PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2275 "2776" _null_ _null_ _null_ _null_ anynonarray_out _null_ _null_ _null_ ));
DESCR("I/O");
+ DATA(insert OID = 3116 ( fdw_handler_in PGNSP PGUID 12 1 0 0 f f f f f i 1 0 3115 "2275" _null_ _null_ _null_ _null_ fdw_handler_in _null_ _null_ _null_ ));
+ DESCR("I/O");
+ DATA(insert OID = 3117 ( fdw_handler_out PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2275 "3115" _null_ _null_ _null_ _null_ fdw_handler_out _null_ _null_ _null_ ));
+ DESCR("I/O");
/* cryptographic */
DATA(insert OID = 2311 ( md5 PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "25" _null_ _null_ _null_ _null_ md5_text _null_ _null_ _null_ ));
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 201e5db..f0241d4 100644
*** a/src/include/catalog/pg_type.h
--- b/src/include/catalog/pg_type.h
*************** DATA(insert OID = 2776 ( anynonarray PGN
*** 623,628 ****
--- 623,630 ----
#define ANYNONARRAYOID 2776
DATA(insert OID = 3500 ( anyenum PGNSP PGUID 4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 _null_ _null_ ));
#define ANYENUMOID 3500
+ DATA(insert OID = 3115 ( fdw_handler PGNSP PGUID 4 t p P f t \054 0 0 0 fdw_handler_in fdw_handler_out - - - - - i p f 0 -1 0 _null_ _null_ ));
+ #define FDW_HANDLEROID 3115
/*
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 1dc1a7d..1814c8d 100644
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
*************** extern void CreateUserMapping(CreateUser
*** 147,152 ****
--- 147,153 ----
extern void AlterUserMapping(AlterUserMappingStmt *stmt);
extern void RemoveUserMapping(DropUserMappingStmt *stmt);
extern void RemoveUserMappingById(Oid umId);
+ extern void CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid);
/* support routines in commands/define.c */
diff --git a/src/include/executor/nodeForeignscan.h b/src/include/executor/nodeForeignscan.h
index ...57da138 .
*** a/src/include/executor/nodeForeignscan.h
--- b/src/include/executor/nodeForeignscan.h
***************
*** 0 ****
--- 1,26 ----
+ /*-------------------------------------------------------------------------
+ *
+ * nodeForeignscan.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef NODEFOREIGNSCAN_H
+ #define NODEFOREIGNSCAN_H
+
+ #include "nodes/execnodes.h"
+
+ extern ForeignScanState *ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags);
+ extern TupleTableSlot *ExecForeignScan(ForeignScanState *node);
+ extern void ExecEndForeignScan(ForeignScanState *node);
+ extern void ExecForeignMarkPos(ForeignScanState *node);
+ extern void ExecForeignRestrPos(ForeignScanState *node);
+ extern void ExecForeignReScan(ForeignScanState *node);
+
+ #endif /* NODEFOREIGNSCAN_H */
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 2305929..e583573 100644
*** a/src/include/foreign/foreign.h
--- b/src/include/foreign/foreign.h
***************
*** 13,19 ****
#ifndef FOREIGN_H
#define FOREIGN_H
! #include "nodes/parsenodes.h"
/* Helper for obtaining username for user mapping */
--- 13,20 ----
#ifndef FOREIGN_H
#define FOREIGN_H
! #include "executor/tuptable.h"
! #include "utils/relcache.h"
/* Helper for obtaining username for user mapping */
*************** typedef enum
*** 29,35 ****
{
ServerOpt = 1, /* options applicable to SERVER */
UserMappingOpt = 2, /* options for USER MAPPING */
! FdwOpt = 4 /* options for FOREIGN DATA WRAPPER */
} GenericOptionFlags;
typedef struct ForeignDataWrapper
--- 30,37 ----
{
ServerOpt = 1, /* options applicable to SERVER */
UserMappingOpt = 2, /* options for USER MAPPING */
! FdwOpt = 4, /* options for FOREIGN DATA WRAPPER */
! ForeignTableOpt = 8, /* options for FOREIGN TABLE */
} GenericOptionFlags;
typedef struct ForeignDataWrapper
*************** typedef struct ForeignDataWrapper
*** 38,43 ****
--- 40,46 ----
Oid owner; /* FDW owner user Oid */
char *fdwname; /* Name of the FDW */
Oid fdwvalidator;
+ Oid fdwhandler;
List *options; /* fdwoptions as DefElem list */
} ForeignDataWrapper;
*************** typedef struct UserMapping
*** 59,65 ****
--- 62,79 ----
List *options; /* useoptions as DefElem list */
} UserMapping;
+ typedef struct ForeignTable
+ {
+ Oid relid; /* relation Oid */
+ Oid serverid; /* server Oid */
+ List *options; /* ftoptions as DefElem list */
+ } ForeignTable;
+
+ typedef struct FdwRoutine FdwRoutine;
+ typedef struct FSConnection FSConnection;
+ typedef struct FdwReply FdwReply;
+ /* catalog manipulation */
extern ForeignServer *GetForeignServer(Oid serverid);
extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
extern Oid GetForeignServerOidByName(const char *name, bool missing_ok);
*************** extern ForeignDataWrapper *GetForeignDat
*** 68,72 ****
--- 82,102 ----
extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
bool missing_ok);
extern Oid GetForeignDataWrapperOidByName(const char *name, bool missing_ok);
+ extern ForeignTable *GetForeignTable(Oid relid);
+ extern FdwRoutine *GetFdwRoutine(Oid fdwhandler);
+ extern FdwRoutine *GetFdwRoutineByRelId(Oid relid);
+ extern bool IsForeignTable(Oid relid);
+ extern Oid GetFdwValidator(Oid relid);
+ extern List *GetGenericOptionsPerColumn(Oid relid, int2 attnum);
+
+ extern int flatten_generic_options(List *options,
+ const char **keywords, const char **values);
+
+ /* ALTER FOREIGN TABLE ... OPTIONS (...) handlers */
+ extern void ATExecGenericOptions(Relation rel, List *options);
+ extern void ATExecColumnGenericOptions(Relation rel, const char *colname,
+ List *options);
+ extern void AlterColumnGenericOptions(Oid relid, const char *colname,
+ List *options);
#endif /* FOREIGN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 89f8e20..a47df10 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 17,24 ****
--- 17,26 ----
#include "access/genam.h"
#include "access/heapam.h"
#include "access/skey.h"
+ #include "foreign/foreign.h"
#include "nodes/params.h"
#include "nodes/plannodes.h"
+ #include "nodes/relation.h"
#include "nodes/tidbitmap.h"
#include "utils/hsearch.h"
#include "utils/rel.h"
*************** typedef struct WorkTableScanState
*** 1382,1387 ****
--- 1384,1464 ----
RecursiveUnionState *rustate;
} WorkTableScanState;
+ /* ----------------
+ * ForeignScanState information
+ *
+ * ForeignScan nodes are used to scan the foreign table managed by
+ * a foreign server.
+ * ----------------
+ */
+ typedef struct ForeignScanState
+ {
+ ScanState ss; /* its first field is NodeTag */
+
+ FdwRoutine *routine; /* set of FDW routines */
+ ForeignDataWrapper *wrapper;/* foreign data wrapper */
+ ForeignServer *server; /* foreign server */
+ FSConnection *conn; /* connection to the foreign server */
+ UserMapping *user; /* user mapping */
+ ForeignTable *table; /* foreign table */
+ FdwReply *reply; /* private data for each data wrapper */
+ } ForeignScanState;
+
+ /*
+ * Common interface routines of FDW baed on SQL/MED standard.
+ * A foreign-data wrapper can implement FDW-specific works with these callback
+ * routines. If there is nothing to do for a callback function other
+ * than Iterate, it can be NULL.
+ */
+ struct FdwRoutine
+ {
+ /*
+ * Connect to the foreign server identified by server and user.
+ * Each FDW also manages connection cache because the FDW only knows the
+ * acutal size of the concrete connection object.
+ */
+ FSConnection* (*ConnectServer)(ForeignServer *server, UserMapping *user);
+
+ /*
+ * Disconnect and free FSConnection.
+ */
+ void (*FreeFSConnection)(FSConnection *conn);
+
+ /*
+ * Estimate costs of a foreign path.
+ * FDW should update startup_cost and total_cost in the Path.
+ */
+ void (*EstimateCosts)(ForeignPath *path, PlannerInfo *root,
+ RelOptInfo *baserel);
+
+ /*
+ * Prepare to execute foreign access.
+ */
+ void (*Open)(ForeignScanState *scanstate);
+
+ /*
+ * Initiate scanning of a foreign table.
+ */
+ void (*BeginScan)(ForeignScanState *scanstate);
+
+ /*
+ * Fetch the next record and fill tupleslot with it.
+ * This routine can't be NULL.
+ */
+ void (*Iterate)(ForeignScanState *scanstate);
+
+ /*
+ * End the foreign scan and close cursor.
+ */
+ void (*Close)(ForeignScanState *scanstate);
+
+ /*
+ * Re-initialize the foreign scan, used when the INNER executor node is
+ * executed again.
+ */
+ void (*ReOpen)(ForeignScanState *scanstate);
+ };
+
/* ----------------------------------------------------------------
* Join State Information
* ----------------------------------------------------------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index bc96ebf..1afbbcb 100644
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 60,65 ****
--- 60,66 ----
T_ValuesScan,
T_CteScan,
T_WorkTableScan,
+ T_ForeignScan,
T_Join,
T_NestLoop,
T_MergeJoin,
*************** typedef enum NodeTag
*** 103,108 ****
--- 104,110 ----
T_ValuesScanState,
T_CteScanState,
T_WorkTableScanState,
+ T_ForeignScanState,
T_JoinState,
T_NestLoopState,
T_MergeJoinState,
*************** typedef enum NodeTag
*** 216,221 ****
--- 218,224 ----
T_MergePath,
T_HashPath,
T_TidPath,
+ T_ForeignPath,
T_AppendPath,
T_MergeAppendPath,
T_ResultPath,
*************** typedef enum NodeTag
*** 353,358 ****
--- 356,362 ----
T_DropUserMappingStmt,
T_AlterTableSpaceOptionsStmt,
T_SecLabelStmt,
+ T_CreateForeignTableStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8b34b76..7902328 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct ColumnDef
*** 482,487 ****
--- 482,488 ----
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
List *constraints; /* other constraints on column */
+ List *genoptions; /* generic options */
} ColumnDef;
/*
*************** typedef enum CreateStmtLikeOption
*** 501,507 ****
CREATE_TABLE_LIKE_INDEXES = 1 << 2,
CREATE_TABLE_LIKE_STORAGE = 1 << 3,
CREATE_TABLE_LIKE_COMMENTS = 1 << 4,
! CREATE_TABLE_LIKE_ALL = 0x7FFFFFFF
} CreateStmtLikeOption;
/*
--- 502,512 ----
CREATE_TABLE_LIKE_INDEXES = 1 << 2,
CREATE_TABLE_LIKE_STORAGE = 1 << 3,
CREATE_TABLE_LIKE_COMMENTS = 1 << 4,
! CREATE_TABLE_LIKE_ALL = 0x7FFFFFFF,
! /* FOREIGN TABLE supports only DEFALUT/CONSTRAINT/COMMENT. */
! CREATE_TABLE_LIKE_FOREIGN_ALL = ( CREATE_TABLE_LIKE_DEFAULTS |
! CREATE_TABLE_LIKE_CONSTRAINTS |
! CREATE_TABLE_LIKE_COMMENTS)
} CreateStmtLikeOption;
/*
*************** typedef enum ObjectType
*** 1066,1071 ****
--- 1071,1077 ----
OBJECT_DOMAIN,
OBJECT_FDW,
OBJECT_FOREIGN_SERVER,
+ OBJECT_FOREIGN_TABLE,
OBJECT_FUNCTION,
OBJECT_INDEX,
OBJECT_LANGUAGE,
*************** typedef enum AlterTableType
*** 1165,1171 ****
AT_EnableReplicaRule, /* ENABLE REPLICA RULE name */
AT_DisableRule, /* DISABLE RULE name */
AT_AddInherit, /* INHERIT parent */
! AT_DropInherit /* NO INHERIT parent */
} AlterTableType;
typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */
--- 1171,1179 ----
AT_EnableReplicaRule, /* ENABLE REPLICA RULE name */
AT_DisableRule, /* DISABLE RULE name */
AT_AddInherit, /* INHERIT parent */
! AT_DropInherit, /* NO INHERIT parent */
! AT_GenericOptions, /* OPTIONS (...) */
! AT_ColumnGenericOptions, /* ALTER COLUMN OPTIONS (...) */
} AlterTableType;
typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */
*************** typedef enum GrantObjectType
*** 1226,1231 ****
--- 1234,1240 ----
ACL_OBJECT_DATABASE, /* database */
ACL_OBJECT_FDW, /* foreign-data wrapper */
ACL_OBJECT_FOREIGN_SERVER, /* foreign server */
+ ACL_OBJECT_FOREIGN_TABLE, /* foreign table */
ACL_OBJECT_FUNCTION, /* function */
ACL_OBJECT_LANGUAGE, /* procedural language */
ACL_OBJECT_LARGEOBJECT, /* largeobject */
*************** typedef struct CreateFdwStmt
*** 1526,1532 ****
{
NodeTag type;
char *fdwname; /* foreign-data wrapper name */
! List *validator; /* optional validator function (qual. name) */
List *options; /* generic options to FDW */
} CreateFdwStmt;
--- 1535,1541 ----
{
NodeTag type;
char *fdwname; /* foreign-data wrapper name */
! List *func_options; /* VALIDATOR/HANDLER conbination */
List *options; /* generic options to FDW */
} CreateFdwStmt;
*************** typedef struct AlterFdwStmt
*** 1534,1541 ****
{
NodeTag type;
char *fdwname; /* foreign-data wrapper name */
! List *validator; /* optional validator function (qual. name) */
! bool change_validator;
List *options; /* generic options to FDW */
} AlterFdwStmt;
--- 1543,1549 ----
{
NodeTag type;
char *fdwname; /* foreign-data wrapper name */
! List *func_options; /* VALIDATOR/HANDLER conbination */
List *options; /* generic options to FDW */
} AlterFdwStmt;
*************** typedef struct DropForeignServerStmt
*** 1580,1585 ****
--- 1588,1605 ----
} DropForeignServerStmt;
/* ----------------------
+ * Create FOREIGN TABLE Statements
+ * ----------------------
+ */
+
+ typedef struct CreateForeignTableStmt
+ {
+ CreateStmt base;
+ char *servername;
+ List *options;
+ } CreateForeignTableStmt;
+
+ /* ----------------------
* Create/Drop USER MAPPING Statements
* ----------------------
*/
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fec4ace..6c634ce 100644
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
*************** typedef struct WorkTableScan
*** 416,421 ****
--- 416,430 ----
int wtParam; /* ID of Param representing work table */
} WorkTableScan;
+ /* ----------------
+ * ForeignScan node
+ * ----------------
+ */
+ typedef struct ForeignScan
+ {
+ Scan scan;
+ } ForeignScan;
+
/*
* ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 785acc9..9c4c8b5 100644
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct TidPath
*** 740,745 ****
--- 740,753 ----
} TidPath;
/*
+ * ForeignPath represents a scan on a foreign table
+ */
+ typedef struct ForeignPath
+ {
+ Path path;
+ } ForeignPath;
+
+ /*
* AppendPath represents an Append plan, ie, successive execution of
* several member plans.
*
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 8df1b95..f75e6e4 100644
*** a/src/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
*************** extern void cost_functionscan(Path *path
*** 81,86 ****
--- 81,87 ----
extern void cost_valuesscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel);
extern void cost_ctescan(Path *path, PlannerInfo *root, RelOptInfo *baserel);
+ extern void cost_foreignscan(ForeignPath *path, PlannerInfo *root, RelOptInfo *baserel);
extern void cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm);
extern void cost_sort(Path *path, PlannerInfo *root,
List *pathkeys, Cost input_cost, double tuples, int width,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 53ebe57..32ccd5e 100644
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern Path *create_functionscan_path(Pl
*** 60,65 ****
--- 60,66 ----
extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel);
extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel);
extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel);
+ extern Path *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel);
extern NestPath *create_nestloop_path(PlannerInfo *root,
RelOptInfo *joinrel,
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 430dc1f..b35f6ba 100644
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
*************** typedef ArrayType Acl;
*** 150,155 ****
--- 150,156 ----
#define ACL_ALL_RIGHTS_DATABASE (ACL_CREATE|ACL_CREATE_TEMP|ACL_CONNECT)
#define ACL_ALL_RIGHTS_FDW (ACL_USAGE)
#define ACL_ALL_RIGHTS_FOREIGN_SERVER (ACL_USAGE)
+ #define ACL_ALL_RIGHTS_FOREIGN_TABLE (ACL_SELECT)
#define ACL_ALL_RIGHTS_FUNCTION (ACL_EXECUTE)
#define ACL_ALL_RIGHTS_LANGUAGE (ACL_USAGE)
#define ACL_ALL_RIGHTS_LARGEOBJECT (ACL_SELECT|ACL_UPDATE)
*************** typedef enum AclObjectKind
*** 193,198 ****
--- 194,200 ----
ACL_KIND_TSCONFIGURATION, /* pg_ts_config */
ACL_KIND_FDW, /* pg_foreign_data_wrapper */
ACL_KIND_FOREIGN_SERVER, /* pg_foreign_server */
+ ACL_KIND_FOREIGN_TABLE, /* pg_foreign_table */
MAX_ACL_KIND /* MUST BE LAST */
} AclObjectKind;
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index a2fb749..f3f242f 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum pg_node_tree_in(PG_FUNCTION
*** 515,520 ****
--- 515,522 ----
extern Datum pg_node_tree_out(PG_FUNCTION_ARGS);
extern Datum pg_node_tree_recv(PG_FUNCTION_ARGS);
extern Datum pg_node_tree_send(PG_FUNCTION_ARGS);
+ extern Datum fdw_handler_in(PG_FUNCTION_ARGS);
+ extern Datum fdw_handler_out(PG_FUNCTION_ARGS);
/* regexp.c */
extern Datum nameregexeq(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/errcodes.h b/src/include/utils/errcodes.h
index 5cab47a..e6c422b 100644
*** a/src/include/utils/errcodes.h
--- b/src/include/utils/errcodes.h
***************
*** 343,348 ****
--- 343,377 ----
#define ERRCODE_CONFIG_FILE_ERROR MAKE_SQLSTATE('F','0', '0','0','0')
#define ERRCODE_LOCK_FILE_EXISTS MAKE_SQLSTATE('F','0', '0','0','1')
+ /* Class HV - Foreign data wrapper Error (SQL/MED-specific error class) */
+ #define ERRCODE_FDW_ERROR MAKE_SQLSTATE('H','V', '0','0','0')
+ #define ERRCODE_FDW_COLUMN_NAME_NOT_FOUND MAKE_SQLSTATE('H','V', '0','0','5')
+ #define ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED MAKE_SQLSTATE('H','V', '0','0','2')
+ #define ERRCODE_FDW_FUNCTION_SEQUENCE_ERROR MAKE_SQLSTATE('H','V', '0','1','0')
+ #define ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION MAKE_SQLSTATE('H','V', '0','2','1')
+ #define ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE MAKE_SQLSTATE('H','V', '0','2','4')
+ #define ERRCODE_FDW_INVALID_COLUMN_NAME MAKE_SQLSTATE('H','V', '0','0','7')
+ #define ERRCODE_FDW_INVALID_COLUMN_NUMBER MAKE_SQLSTATE('H','V', '0','0','8')
+ #define ERRCODE_FDW_INVALID_DATA_TYPE MAKE_SQLSTATE('H','V', '0','0','4')
+ #define ERRCODE_FDW_INVALID_DATA_TYPE_DESCRIPTORS MAKE_SQLSTATE('H','V', '0','0','6')
+ #define ERRCODE_FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER MAKE_SQLSTATE('H','V', '0','9','1')
+ #define ERRCODE_FDW_INVALID_HANDLE MAKE_SQLSTATE('H','V', '0','0','B')
+ #define ERRCODE_FDW_INVALID_OPTION_INDEX MAKE_SQLSTATE('H','V', '0','0','C')
+ #define ERRCODE_FDW_INVALID_OPTION_NAME MAKE_SQLSTATE('H','V', '0','0','D')
+ #define ERRCODE_FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH MAKE_SQLSTATE('H','V', '0','9','0')
+ #define ERRCODE_FDW_INVALID_STRING_FORMAT MAKE_SQLSTATE('H','V', '0','0','A')
+ #define ERRCODE_FDW_INVALID_USE_OF_NULL_POINTER MAKE_SQLSTATE('H','V', '0','0','9')
+ #define ERRCODE_FDW_TOO_MANY_HANDLES MAKE_SQLSTATE('H','V', '0','1','4')
+ #define ERRCODE_FDW_OUT_OF_MEMORY MAKE_SQLSTATE('H','V', '0','0','1')
+ #define ERRCODE_FDW_NO_SCHEMAS MAKE_SQLSTATE('H','V', '0','0','P')
+ #define ERRCODE_FDW_OPTION_NAME_NOT_FOUND MAKE_SQLSTATE('H','V', '0','0','J')
+ #define ERRCODE_FDW_REPLY_HANDLE MAKE_SQLSTATE('H','V', '0','0','K')
+ #define ERRCODE_FDW_SCHEMA_NOT_FOUND MAKE_SQLSTATE('H','V', '0','0','Q')
+ #define ERRCODE_FDW_TABLE_NOT_FOUND MAKE_SQLSTATE('H','V', '0','0','R')
+ #define ERRCODE_FDW_UNALBE_TO_CREATE_EXECUTION MAKE_SQLSTATE('H','V', '0','0','L')
+ #define ERRCODE_FDW_UNABLE_TO_CREATE_REPLY MAKE_SQLSTATE('H','V', '0','0','M')
+ #define ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION MAKE_SQLSTATE('H','V', '0','0','N')
+
/* Class P0 - PL/pgSQL Error (PostgreSQL-specific error class) */
#define ERRCODE_PLPGSQL_ERROR MAKE_SQLSTATE('P','0', '0','0','0')
#define ERRCODE_RAISE_EXCEPTION MAKE_SQLSTATE('P','0', '0','0','1')
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 30e0f8f..2204d0e 100644
*** a/src/include/utils/syscache.h
--- b/src/include/utils/syscache.h
*************** enum SysCacheIdentifier
*** 56,61 ****
--- 56,62 ----
FOREIGNDATAWRAPPEROID,
FOREIGNSERVERNAME,
FOREIGNSERVEROID,
+ FOREIGNTABLEREL,
INDEXRELID,
LANGNAME,
LANGOID,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index b68dfd4..24d5220 100644
*** a/src/test/regress/expected/alter_table.out
--- b/src/test/regress/expected/alter_table.out
*************** ERROR: cannot alter system column "oid"
*** 599,607 ****
-- try creating a view and altering that, should fail
create view myview as select * from atacc1;
alter table myview alter column test drop not null;
! ERROR: "myview" is not a table
alter table myview alter column test set not null;
! ERROR: "myview" is not a table
drop view myview;
drop table atacc1;
-- test inheritance
--- 599,607 ----
-- try creating a view and altering that, should fail
create view myview as select * from atacc1;
alter table myview alter column test drop not null;
! ERROR: "myview" is not a table or foreign table
alter table myview alter column test set not null;
! ERROR: "myview" is not a table or foreign table
drop view myview;
drop table atacc1;
-- test inheritance
*************** select * from myview;
*** 854,860 ****
(0 rows)
alter table myview drop d;
! ERROR: "myview" is not a table or composite type
drop view myview;
-- test some commands to make sure they fail on the dropped column
analyze atacc1(a);
--- 854,860 ----
(0 rows)
alter table myview drop d;
! ERROR: "myview" is not a table or composite type or foreign table
drop view myview;
-- test some commands to make sure they fail on the dropped column
analyze atacc1(a);
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index fcc1d7c..d6d1858 100644
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
*************** CREATE ROLE regress_test_role2;
*** 13,26 ****
CREATE ROLE regress_test_role_super SUPERUSER;
CREATE ROLE regress_test_indirect;
CREATE ROLE unprivileged_role;
CREATE FOREIGN DATA WRAPPER dummy;
CREATE FOREIGN DATA WRAPPER postgresql VALIDATOR postgresql_fdw_validator;
-- At this point we should have 2 built-in wrappers and no servers.
! SELECT fdwname, fdwvalidator::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3;
! fdwname | fdwvalidator | fdwoptions
! ------------+--------------------------+------------
! dummy | - |
! postgresql | postgresql_fdw_validator |
(2 rows)
SELECT srvname, srvoptions FROM pg_foreign_server;
--- 13,27 ----
CREATE ROLE regress_test_role_super SUPERUSER;
CREATE ROLE regress_test_indirect;
CREATE ROLE unprivileged_role;
+ CREATE SCHEMA foreign_schema;
CREATE FOREIGN DATA WRAPPER dummy;
CREATE FOREIGN DATA WRAPPER postgresql VALIDATOR postgresql_fdw_validator;
-- At this point we should have 2 built-in wrappers and no servers.
! SELECT fdwname, fdwvalidator::regproc, fdwhandler::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY fdwname;
! fdwname | fdwvalidator | fdwhandler | fdwoptions
! ------------+--------------------------+------------+------------
! dummy | - | - |
! postgresql | postgresql_fdw_validator | - |
(2 rows)
SELECT srvname, srvoptions FROM pg_foreign_server;
*************** CREATE FOREIGN DATA WRAPPER foo VALIDATO
*** 38,49 ****
ERROR: function bar(text[], oid) does not exist
CREATE FOREIGN DATA WRAPPER foo;
\dew
! List of foreign-data wrappers
! Name | Owner | Validator
! ------------+-------------------+--------------------------
! dummy | foreign_data_user | -
! foo | foreign_data_user | -
! postgresql | foreign_data_user | postgresql_fdw_validator
(3 rows)
CREATE FOREIGN DATA WRAPPER foo; -- duplicate
--- 39,50 ----
ERROR: function bar(text[], oid) does not exist
CREATE FOREIGN DATA WRAPPER foo;
\dew
! List of foreign-data wrappers
! Name | Owner | Handler | Validator
! ------------+-------------------+---------+--------------------------
! dummy | foreign_data_user | - | -
! foo | foreign_data_user | - | -
! postgresql | foreign_data_user | - | postgresql_fdw_validator
(3 rows)
CREATE FOREIGN DATA WRAPPER foo; -- duplicate
*************** ERROR: foreign-data wrapper "foo" alrea
*** 51,62 ****
DROP FOREIGN DATA WRAPPER foo;
CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1');
\dew+
! List of foreign-data wrappers
! Name | Owner | Validator | Access privileges | Options
! ------------+-------------------+--------------------------+-------------------+-------------
! dummy | foreign_data_user | - | |
! foo | foreign_data_user | - | | {testing=1}
! postgresql | foreign_data_user | postgresql_fdw_validator | |
(3 rows)
DROP FOREIGN DATA WRAPPER foo;
--- 52,63 ----
DROP FOREIGN DATA WRAPPER foo;
CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1');
\dew+
! List of foreign-data wrappers
! Name | Owner | Handler | Validator | Access privileges | Options
! ------------+-------------------+---------+--------------------------+-------------------+-------------
! dummy | foreign_data_user | - | - | |
! foo | foreign_data_user | - | - | | {testing=1}
! postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
DROP FOREIGN DATA WRAPPER foo;
*************** CREATE FOREIGN DATA WRAPPER foo OPTIONS
*** 64,75 ****
ERROR: option "testing" provided more than once
CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1', another '2');
\dew+
! List of foreign-data wrappers
! Name | Owner | Validator | Access privileges | Options
! ------------+-------------------+--------------------------+-------------------+-----------------------
! dummy | foreign_data_user | - | |
! foo | foreign_data_user | - | | {testing=1,another=2}
! postgresql | foreign_data_user | postgresql_fdw_validator | |
(3 rows)
DROP FOREIGN DATA WRAPPER foo;
--- 65,76 ----
ERROR: option "testing" provided more than once
CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1', another '2');
\dew+
! List of foreign-data wrappers
! Name | Owner | Handler | Validator | Access privileges | Options
! ------------+-------------------+---------+--------------------------+-------------------+-----------------------
! dummy | foreign_data_user | - | - | |
! foo | foreign_data_user | - | - | | {testing=1,another=2}
! postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
DROP FOREIGN DATA WRAPPER foo;
*************** HINT: Must be superuser to create a for
*** 80,91 ****
RESET ROLE;
CREATE FOREIGN DATA WRAPPER foo VALIDATOR postgresql_fdw_validator;
\dew+
! List of foreign-data wrappers
! Name | Owner | Validator | Access privileges | Options
! ------------+-------------------+--------------------------+-------------------+---------
! dummy | foreign_data_user | - | |
! foo | foreign_data_user | postgresql_fdw_validator | |
! postgresql | foreign_data_user | postgresql_fdw_validator | |
(3 rows)
-- ALTER FOREIGN DATA WRAPPER
--- 81,92 ----
RESET ROLE;
CREATE FOREIGN DATA WRAPPER foo VALIDATOR postgresql_fdw_validator;
\dew+
! List of foreign-data wrappers
! Name | Owner | Handler | Validator | Access privileges | Options
! ------------+-------------------+---------+--------------------------+-------------------+---------
! dummy | foreign_data_user | - | - | |
! foo | foreign_data_user | - | postgresql_fdw_validator | |
! postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
-- ALTER FOREIGN DATA WRAPPER
*************** ALTER FOREIGN DATA WRAPPER foo VALIDATOR
*** 97,108 ****
ERROR: function bar(text[], oid) does not exist
ALTER FOREIGN DATA WRAPPER foo NO VALIDATOR;
\dew+
! List of foreign-data wrappers
! Name | Owner | Validator | Access privileges | Options
! ------------+-------------------+--------------------------+-------------------+---------
! dummy | foreign_data_user | - | |
! foo | foreign_data_user | - | |
! postgresql | foreign_data_user | postgresql_fdw_validator | |
(3 rows)
ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '1', b '2');
--- 98,109 ----
ERROR: function bar(text[], oid) does not exist
ALTER FOREIGN DATA WRAPPER foo NO VALIDATOR;
\dew+
! List of foreign-data wrappers
! Name | Owner | Handler | Validator | Access privileges | Options
! ------------+-------------------+---------+--------------------------+-------------------+---------
! dummy | foreign_data_user | - | - | |
! foo | foreign_data_user | - | - | |
! postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '1', b '2');
*************** ALTER FOREIGN DATA WRAPPER foo OPTIONS (
*** 112,145 ****
ERROR: option "c" not found
ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD x '1', DROP x);
\dew+
! List of foreign-data wrappers
! Name | Owner | Validator | Access privileges | Options
! ------------+-------------------+--------------------------+-------------------+-----------
! dummy | foreign_data_user | - | |
! foo | foreign_data_user | - | | {a=1,b=2}
! postgresql | foreign_data_user | postgresql_fdw_validator | |
(3 rows)
ALTER FOREIGN DATA WRAPPER foo OPTIONS (DROP a, SET b '3', ADD c '4');
\dew+
! List of foreign-data wrappers
! Name | Owner | Validator | Access privileges | Options
! ------------+-------------------+--------------------------+-------------------+-----------
! dummy | foreign_data_user | - | |
! foo | foreign_data_user | - | | {b=3,c=4}
! postgresql | foreign_data_user | postgresql_fdw_validator | |
(3 rows)
ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '2');
ALTER FOREIGN DATA WRAPPER foo OPTIONS (b '4'); -- ERROR
ERROR: option "b" provided more than once
\dew+
! List of foreign-data wrappers
! Name | Owner | Validator | Access privileges | Options
! ------------+-------------------+--------------------------+-------------------+---------------
! dummy | foreign_data_user | - | |
! foo | foreign_data_user | - | | {b=3,c=4,a=2}
! postgresql | foreign_data_user | postgresql_fdw_validator | |
(3 rows)
SET ROLE regress_test_role;
--- 113,146 ----
ERROR: option "c" not found
ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD x '1', DROP x);
\dew+
! List of foreign-data wrappers
! Name | Owner | Handler | Validator | Access privileges | Options
! ------------+-------------------+---------+--------------------------+-------------------+-----------
! dummy | foreign_data_user | - | - | |
! foo | foreign_data_user | - | - | | {a=1,b=2}
! postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
ALTER FOREIGN DATA WRAPPER foo OPTIONS (DROP a, SET b '3', ADD c '4');
\dew+
! List of foreign-data wrappers
! Name | Owner | Handler | Validator | Access privileges | Options
! ------------+-------------------+---------+--------------------------+-------------------+-----------
! dummy | foreign_data_user | - | - | |
! foo | foreign_data_user | - | - | | {b=3,c=4}
! postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '2');
ALTER FOREIGN DATA WRAPPER foo OPTIONS (b '4'); -- ERROR
ERROR: option "b" provided more than once
\dew+
! List of foreign-data wrappers
! Name | Owner | Handler | Validator | Access privileges | Options
! ------------+-------------------+---------+--------------------------+-------------------+---------------
! dummy | foreign_data_user | - | - | |
! foo | foreign_data_user | - | - | | {b=3,c=4,a=2}
! postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
SET ROLE regress_test_role;
*************** HINT: Must be superuser to alter a fore
*** 149,160 ****
SET ROLE regress_test_role_super;
ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD d '5');
\dew+
! List of foreign-data wrappers
! Name | Owner | Validator | Access privileges | Options
! ------------+-------------------+--------------------------+-------------------+-------------------
! dummy | foreign_data_user | - | |
! foo | foreign_data_user | - | | {b=3,c=4,a=2,d=5}
! postgresql | foreign_data_user | postgresql_fdw_validator | |
(3 rows)
ALTER FOREIGN DATA WRAPPER foo OWNER TO regress_test_role; -- ERROR
--- 150,161 ----
SET ROLE regress_test_role_super;
ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD d '5');
\dew+
! List of foreign-data wrappers
! Name | Owner | Handler | Validator | Access privileges | Options
! ------------+-------------------+---------+--------------------------+-------------------+-------------------
! dummy | foreign_data_user | - | - | |
! foo | foreign_data_user | - | - | | {b=3,c=4,a=2,d=5}
! postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
ALTER FOREIGN DATA WRAPPER foo OWNER TO regress_test_role; -- ERROR
*************** ERROR: permission denied to alter forei
*** 168,179 ****
HINT: Must be superuser to alter a foreign-data wrapper.
RESET ROLE;
\dew+
! List of foreign-data wrappers
! Name | Owner | Validator | Access privileges | Options
! ------------+-------------------------+--------------------------+-------------------+-------------------
! dummy | foreign_data_user | - | |
! foo | regress_test_role_super | - | | {b=3,c=4,a=2,d=5}
! postgresql | foreign_data_user | postgresql_fdw_validator | |
(3 rows)
-- DROP FOREIGN DATA WRAPPER
--- 169,180 ----
HINT: Must be superuser to alter a foreign-data wrapper.
RESET ROLE;
\dew+
! List of foreign-data wrappers
! Name | Owner | Handler | Validator | Access privileges | Options
! ------------+-------------------------+---------+--------------------------+-------------------+-------------------
! dummy | foreign_data_user | - | - | |
! foo | regress_test_role_super | - | - | | {b=3,c=4,a=2,d=5}
! postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
-- DROP FOREIGN DATA WRAPPER
*************** ERROR: foreign-data wrapper "nonexisten
*** 182,193 ****
DROP FOREIGN DATA WRAPPER IF EXISTS nonexistent;
NOTICE: foreign-data wrapper "nonexistent" does not exist, skipping
\dew+
! List of foreign-data wrappers
! Name | Owner | Validator | Access privileges | Options
! ------------+-------------------------+--------------------------+-------------------+-------------------
! dummy | foreign_data_user | - | |
! foo | regress_test_role_super | - | | {b=3,c=4,a=2,d=5}
! postgresql | foreign_data_user | postgresql_fdw_validator | |
(3 rows)
DROP ROLE regress_test_role_super; -- ERROR
--- 183,194 ----
DROP FOREIGN DATA WRAPPER IF EXISTS nonexistent;
NOTICE: foreign-data wrapper "nonexistent" does not exist, skipping
\dew+
! List of foreign-data wrappers
! Name | Owner | Handler | Validator | Access privileges | Options
! ------------+-------------------------+---------+--------------------------+-------------------+-------------------
! dummy | foreign_data_user | - | - | |
! foo | regress_test_role_super | - | - | | {b=3,c=4,a=2,d=5}
! postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
DROP ROLE regress_test_role_super; -- ERROR
*************** ALTER ROLE regress_test_role_super SUPER
*** 202,224 ****
DROP FOREIGN DATA WRAPPER foo;
DROP ROLE regress_test_role_super;
\dew+
! List of foreign-data wrappers
! Name | Owner | Validator | Access privileges | Options
! ------------+-------------------+--------------------------+-------------------+---------
! dummy | foreign_data_user | - | |
! postgresql | foreign_data_user | postgresql_fdw_validator | |
(2 rows)
CREATE FOREIGN DATA WRAPPER foo;
CREATE SERVER s1 FOREIGN DATA WRAPPER foo;
CREATE USER MAPPING FOR current_user SERVER s1;
\dew+
! List of foreign-data wrappers
! Name | Owner | Validator | Access privileges | Options
! ------------+-------------------+--------------------------+-------------------+---------
! dummy | foreign_data_user | - | |
! foo | foreign_data_user | - | |
! postgresql | foreign_data_user | postgresql_fdw_validator | |
(3 rows)
\des+
--- 203,225 ----
DROP FOREIGN DATA WRAPPER foo;
DROP ROLE regress_test_role_super;
\dew+
! List of foreign-data wrappers
! Name | Owner | Handler | Validator | Access privileges | Options
! ------------+-------------------+---------+--------------------------+-------------------+---------
! dummy | foreign_data_user | - | - | |
! postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(2 rows)
CREATE FOREIGN DATA WRAPPER foo;
CREATE SERVER s1 FOREIGN DATA WRAPPER foo;
CREATE USER MAPPING FOR current_user SERVER s1;
\dew+
! List of foreign-data wrappers
! Name | Owner | Handler | Validator | Access privileges | Options
! ------------+-------------------+---------+--------------------------+-------------------+---------
! dummy | foreign_data_user | - | - | |
! foo | foreign_data_user | - | - | |
! postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(3 rows)
\des+
*************** NOTICE: drop cascades to 2 other object
*** 250,260 ****
DETAIL: drop cascades to server s1
drop cascades to user mapping for foreign_data_user
\dew+
! List of foreign-data wrappers
! Name | Owner | Validator | Access privileges | Options
! ------------+-------------------+--------------------------+-------------------+---------
! dummy | foreign_data_user | - | |
! postgresql | foreign_data_user | postgresql_fdw_validator | |
(2 rows)
\des+
--- 251,261 ----
DETAIL: drop cascades to server s1
drop cascades to user mapping for foreign_data_user
\dew+
! List of foreign-data wrappers
! Name | Owner | Handler | Validator | Access privileges | Options
! ------------+-------------------+---------+--------------------------+-------------------+---------
! dummy | foreign_data_user | - | - | |
! postgresql | foreign_data_user | - | postgresql_fdw_validator | |
(2 rows)
\des+
*************** CREATE SERVER t2 FOREIGN DATA WRAPPER fo
*** 350,355 ****
--- 351,357 ----
RESET ROLE;
REVOKE regress_test_indirect FROM regress_test_role;
+ CREATE SERVER sc FOREIGN DATA WRAPPER dummy;
-- ALTER SERVER
ALTER SERVER s0; -- ERROR
ERROR: syntax error at or near ";"
*************** GRANT USAGE ON FOREIGN SERVER s6 TO regr
*** 376,384 ****
| | | regress_test_role2=U*/foreign_data_user | | |
s7 | foreign_data_user | foo | | oracle | 17.0 | {host=a,dbname=b}
s8 | foreign_data_user | postgresql | | | | {host=localhost,dbname=s8db}
t1 | regress_test_role | foo | | | |
t2 | regress_test_role | foo | | | |
! (10 rows)
SET ROLE regress_test_role;
ALTER SERVER s1 VERSION '1.1'; -- ERROR
--- 378,387 ----
| | | regress_test_role2=U*/foreign_data_user | | |
s7 | foreign_data_user | foo | | oracle | 17.0 | {host=a,dbname=b}
s8 | foreign_data_user | postgresql | | | | {host=localhost,dbname=s8db}
+ sc | foreign_data_user | dummy | | | |
t1 | regress_test_role | foo | | | |
t2 | regress_test_role | foo | | | |
! (11 rows)
SET ROLE regress_test_role;
ALTER SERVER s1 VERSION '1.1'; -- ERROR
*************** privileges for foreign-data wrapper foo
*** 427,435 ****
| | | regress_test_role2=U*/foreign_data_user | | |
s7 | foreign_data_user | foo | | oracle | 17.0 | {host=a,dbname=b}
s8 | foreign_data_user | postgresql | | | | {dbname=db1,connect_timeout=30}
t1 | regress_test_role | foo | | | |
t2 | regress_test_role | foo | | | |
! (10 rows)
-- DROP SERVER
DROP SERVER nonexistent; -- ERROR
--- 430,439 ----
| | | regress_test_role2=U*/foreign_data_user | | |
s7 | foreign_data_user | foo | | oracle | 17.0 | {host=a,dbname=b}
s8 | foreign_data_user | postgresql | | | | {dbname=db1,connect_timeout=30}
+ sc | foreign_data_user | dummy | | | |
t1 | regress_test_role | foo | | | |
t2 | regress_test_role | foo | | | |
! (11 rows)
-- DROP SERVER
DROP SERVER nonexistent; -- ERROR
*************** NOTICE: server "nonexistent" does not e
*** 448,456 ****
s6 | foreign_data_user | foo
s7 | foreign_data_user | foo
s8 | foreign_data_user | postgresql
t1 | regress_test_role | foo
t2 | regress_test_role | foo
! (10 rows)
SET ROLE regress_test_role;
DROP SERVER s2; -- ERROR
--- 452,461 ----
s6 | foreign_data_user | foo
s7 | foreign_data_user | foo
s8 | foreign_data_user | postgresql
+ sc | foreign_data_user | dummy
t1 | regress_test_role | foo
t2 | regress_test_role | foo
! (11 rows)
SET ROLE regress_test_role;
DROP SERVER s2; -- ERROR
*************** RESET ROLE;
*** 468,476 ****
s6 | foreign_data_user | foo
s7 | foreign_data_user | foo
s8 | foreign_data_user | postgresql
t1 | regress_test_role | foo
t2 | regress_test_role | foo
! (9 rows)
ALTER SERVER s2 OWNER TO regress_test_role;
SET ROLE regress_test_role;
--- 473,482 ----
s6 | foreign_data_user | foo
s7 | foreign_data_user | foo
s8 | foreign_data_user | postgresql
+ sc | foreign_data_user | dummy
t1 | regress_test_role | foo
t2 | regress_test_role | foo
! (10 rows)
ALTER SERVER s2 OWNER TO regress_test_role;
SET ROLE regress_test_role;
*************** RESET ROLE;
*** 486,494 ****
s6 | foreign_data_user | foo
s7 | foreign_data_user | foo
s8 | foreign_data_user | postgresql
t1 | regress_test_role | foo
t2 | regress_test_role | foo
! (8 rows)
CREATE USER MAPPING FOR current_user SERVER s3;
\deu
--- 492,501 ----
s6 | foreign_data_user | foo
s7 | foreign_data_user | foo
s8 | foreign_data_user | postgresql
+ sc | foreign_data_user | dummy
t1 | regress_test_role | foo
t2 | regress_test_role | foo
! (9 rows)
CREATE USER MAPPING FOR current_user SERVER s3;
\deu
*************** NOTICE: drop cascades to user mapping f
*** 513,521 ****
s6 | foreign_data_user | foo
s7 | foreign_data_user | foo
s8 | foreign_data_user | postgresql
t1 | regress_test_role | foo
t2 | regress_test_role | foo
! (7 rows)
\deu
List of user mappings
--- 520,529 ----
s6 | foreign_data_user | foo
s7 | foreign_data_user | foo
s8 | foreign_data_user | postgresql
+ sc | foreign_data_user | dummy
t1 | regress_test_role | foo
t2 | regress_test_role | foo
! (8 rows)
\deu
List of user mappings
*************** ERROR: permission denied for foreign se
*** 546,551 ****
--- 554,560 ----
CREATE USER MAPPING FOR public SERVER s8; -- ERROR
ERROR: must be owner of foreign server s8
RESET ROLE;
+ CREATE USER MAPPING FOR public SERVER sc;
ALTER SERVER t1 OWNER TO regress_test_indirect;
SET ROLE regress_test_role;
CREATE USER MAPPING FOR current_user SERVER t1 OPTIONS (username 'bob', password 'boo');
*************** RESET ROLE;
*** 560,568 ****
s5 | regress_test_role
s6 | regress_test_role
s8 | foreign_data_user
t1 | public
t1 | regress_test_role
! (7 rows)
-- ALTER USER MAPPING
ALTER USER MAPPING FOR regress_test_missing_role SERVER s4 OPTIONS (gotcha 'true'); -- ERROR
--- 569,578 ----
s5 | regress_test_role
s6 | regress_test_role
s8 | foreign_data_user
+ sc | public
t1 | public
t1 | regress_test_role
! (8 rows)
-- ALTER USER MAPPING
ALTER USER MAPPING FOR regress_test_missing_role SERVER s4 OPTIONS (gotcha 'true'); -- ERROR
*************** RESET ROLE;
*** 590,598 ****
s5 | regress_test_role | {modified=1}
s6 | regress_test_role | {username=test}
s8 | foreign_data_user | {password=public}
t1 | public | {modified=1}
t1 | regress_test_role | {username=bob,password=boo}
! (7 rows)
-- DROP USER MAPPING
DROP USER MAPPING FOR regress_test_missing_role SERVER s4; -- ERROR
--- 600,609 ----
s5 | regress_test_role | {modified=1}
s6 | regress_test_role | {username=test}
s8 | foreign_data_user | {password=public}
+ sc | public |
t1 | public | {modified=1}
t1 | regress_test_role | {username=bob,password=boo}
! (8 rows)
-- DROP USER MAPPING
DROP USER MAPPING FOR regress_test_missing_role SERVER s4; -- ERROR
*************** DROP SERVER s7;
*** 623,632 ****
s6 | regress_test_role
s8 | foreign_data_user
s8 | public
t1 | public
t1 | regress_test_role
! (8 rows)
-- Information schema
SELECT * FROM information_schema.foreign_data_wrappers ORDER BY 1, 2;
foreign_data_wrapper_catalog | foreign_data_wrapper_name | authorization_identifier | library_name | foreign_data_wrapper_language
--- 634,804 ----
s6 | regress_test_role
s8 | foreign_data_user
s8 | public
+ sc | public
t1 | public
t1 | regress_test_role
! (9 rows)
+ -- CREATE FOREIGN TABLE
+ CREATE TABLE t1 (
+ c1 integer NOT NULL,
+ c2 text DEFAULT 'foo',
+ c3 date CHECK (c3 > '2000-01-01'::date),
+ CONSTRAINT t1_c1_check CHECK (c1 > 0)
+ );
+ CREATE TABLE t2 ();
+ CREATE TABLE f1
+ CREATE FOREIGN TABLE ft1 (); -- ERROR
+ ERROR: syntax error at or near "CREATE"
+ LINE 2: CREATE FOREIGN TABLE ft1 ();
+ ^
+ CREATE FOREIGN TABLE ft1 () SERVER no_server; -- ERROR
+ ERROR: server "no_server" does not exist
+ CREATE FOREIGN TABLE ft1 () INHERITS () SERVER sc; -- ERROR
+ ERROR: syntax error at or near ")"
+ LINE 1: CREATE FOREIGN TABLE ft1 () INHERITS () SERVER sc;
+ ^
+ CREATE FOREIGN TABLE ft1 () INHERITS (no_table) SERVER sc; -- ERROR
+ ERROR: relation "no_table" does not exist
+ CREATE FOREIGN TABLE ft1 (c1 serial) SERVER sc; -- ERROR
+ NOTICE: CREATE TABLE will create implicit sequence "ft1_c1_seq" for serial column "ft1.c1"
+ ERROR: referenced relation "ft1" is not a table
+ CREATE FOREIGN TABLE ft1 () SERVER sc WITH OIDS; -- ERROR
+ ERROR: syntax error at or near "WITH OIDS"
+ LINE 1: CREATE FOREIGN TABLE ft1 () SERVER sc WITH OIDS;
+ ^
+ CREATE FOREIGN TABLE ft1 (
+ c1 integer OPTIONS (force_not_null 'true') NOT NULL,
+ c2 text DEFAULT 'foo',
+ c3 date CHECK (c3 > '2000-01-01'::date),
+ CONSTRAINT ft1_c1_check CHECK (c1 > 0)
+ ) SERVER sc OPTIONS (delimiter ',', quote '"');
+ CREATE RULE ft1_insert_rule AS ON INSERT TO ft1 DO INSTEAD
+ INSERT INTO t1 VALUES (new.c1, new.c2, new.c3);
+ \d+ ft1
+ Foreign table "public.ft1"
+ Column | Type | Modifiers | Storage | Description | Options
+ --------+---------+---------------------+----------+-------------+-----------------------
+ c1 | integer | not null | plain | | {force_not_null=true}
+ c2 | text | default 'foo'::text | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "ft1_c1_check" CHECK (c1 > 0)
+ "ft1_c3_check" CHECK (c3 > '01-01-2000'::date)
+ Rules:
+ ft1_insert_rule AS
+ ON INSERT TO ft1 DO INSTEAD INSERT INTO t1 (c1, c2, c3)
+ VALUES (new.c1, new.c2, new.c3)
+ Server: sc
+ Has OIDs: no
+
+ CREATE FOREIGN TABLE ft2 () INHERITS (t1) SERVER sc OPTIONS (delimiter ' ', quote '`');
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | Storage | Description | Options
+ --------+---------+---------------------+----------+-------------+---------
+ c1 | integer | not null | plain | |
+ c2 | text | default 'foo'::text | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "t1_c1_check" CHECK (c1 > 0)
+ "t1_c3_check" CHECK (c3 > '01-01-2000'::date)
+ Server: sc
+ Inherits: t1
+ Has OIDs: no
+
+ \det+
+ List of foreign tables
+ Schema | Table | Server | Options
+ --------+-------+--------+----------------------------
+ public | ft1 | sc | {"delimiter=,","quote=\""}
+ public | ft2 | sc | {"delimiter= ",quote=`}
+ (2 rows)
+
+ CREATE INDEX id_ft1_c2 ON ft1 (c2); -- ERROR
+ ERROR: "ft1" is not a table
+ -- ALTER FOREIGN TABLE
+ COMMENT ON FOREIGN TABLE ft1 IS 'foreign table';
+ COMMENT ON FOREIGN TABLE ft1 IS NULL;
+ COMMENT ON COLUMN ft1.c1 IS 'foreign column';
+ COMMENT ON COLUMN ft1.c1 IS NULL;
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c4 integer;
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c5 integer DEFAULT 0;
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c6 integer;
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c7 integer NOT NULL;
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c8 integer;
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c9 integer;
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c10 integer;
+ ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (ADD force_not_null 'true');
+ ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (SET force_not_null 'false');
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (DROP force_not_null);
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c4 SET DEFAULT 0;
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c5 DROP DEFAULT;
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c6 SET NOT NULL;
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c7 DROP NOT NULL;
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE char(10) using '0'; -- ERROR
+ ERROR: syntax error at or near "using"
+ LINE 1: ...R FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE char(10) using '0';
+ ^
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE char(10);
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE text;
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0);
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR
+ ERROR: constraint "no_const" of relation "ft1" does not exist
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
+ NOTICE: constraint "no_const" of relation "ft1" does not exist, skipping
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check;
+ ALTER FOREIGN TABLE ft1 SET WITH OIDS; -- ERROR
+ ERROR: syntax error at or near "WITH OIDS"
+ LINE 1: ALTER FOREIGN TABLE ft1 SET WITH OIDS;
+ ^
+ ALTER FOREIGN TABLE ft1 ENABLE RULE ft1_insert_rule;
+ ALTER FOREIGN TABLE ft1 ENABLE ALWAYS RULE ft1_insert_rule;
+ ALTER FOREIGN TABLE ft1 ENABLE REPLICA RULE ft1_insert_rule;
+ ALTER FOREIGN TABLE ft1 DISABLE RULE ft1_insert_rule;
+ ALTER FOREIGN TABLE ft1 INHERIT t2;
+ ALTER FOREIGN TABLE ft1 NO INHERIT t2;
+ ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
+ ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@');
+ ALTER FOREIGN TABLE ft1 DROP COLUMN no_column; -- ERROR
+ ERROR: column "no_column" of relation "ft1" does not exist
+ ALTER FOREIGN TABLE ft1 DROP COLUMN IF EXISTS no_column;
+ NOTICE: column "no_column" of relation "ft1" does not exist, skipping
+ ALTER FOREIGN TABLE ft1 DROP COLUMN c9;
+ ALTER FOREIGN TABLE ft1 SET SCHEMA foreign_schema;
+ ALTER FOREIGN TABLE ft1 SET TABLESPACE ts; -- ERROR
+ ERROR: syntax error at or near "TABLESPACE"
+ LINE 1: ALTER FOREIGN TABLE ft1 SET TABLESPACE ts;
+ ^
+ ALTER FOREIGN TABLE foreign_schema.ft1 RENAME c1 TO foreign_column_1;
+ ALTER FOREIGN TABLE foreign_schema.ft1 RENAME TO foreign_table_1;
+ \d foreign_schema.foreign_table_1
+ Foreign table "foreign_schema.foreign_table_1"
+ Column | Type | Modifiers
+ ------------------+---------+---------------------
+ foreign_column_1 | integer | not null
+ c2 | text | default 'foo'::text
+ c3 | date |
+ c4 | integer | default 0
+ c5 | integer |
+ c6 | integer | not null
+ c7 | integer |
+ c8 | text |
+ c10 | integer |
+ Check constraints:
+ "ft1_c3_check" CHECK (c3 > '01-01-2000'::date)
+ Disabled rules:
+ ft1_insert_rule AS
+ ON INSERT TO foreign_schema.foreign_table_1 DO INSTEAD INSERT INTO t1 (c1, c2, c3)
+ VALUES (new.foreign_column_1, new.c2, new.c3)
+ Server: sc
+
+ -- DROP FOREIGN TABLE
+ DROP FOREIGN TABLE no_table; -- ERROR
+ ERROR: foreign table "no_table" does not exist
+ DROP FOREIGN TABLE IF EXISTS no_table;
+ NOTICE: foreign table "no_table" does not exist, skipping
+ DROP FOREIGN TABLE foreign_schema.foreign_table_1;
-- Information schema
SELECT * FROM information_schema.foreign_data_wrappers ORDER BY 1, 2;
foreign_data_wrapper_catalog | foreign_data_wrapper_name | authorization_identifier | library_name | foreign_data_wrapper_language
*************** SELECT * FROM information_schema.foreign
*** 649,657 ****
regression | s5 | regression | foo | | 15.0 | regress_test_role
regression | s6 | regression | foo | | 16.0 | regress_test_indirect
regression | s8 | regression | postgresql | | | foreign_data_user
regression | t1 | regression | foo | | | regress_test_indirect
regression | t2 | regression | foo | | | regress_test_role
! (6 rows)
SELECT * FROM information_schema.foreign_server_options ORDER BY 1, 2, 3;
foreign_server_catalog | foreign_server_name | option_name | option_value
--- 821,830 ----
regression | s5 | regression | foo | | 15.0 | regress_test_role
regression | s6 | regression | foo | | 16.0 | regress_test_indirect
regression | s8 | regression | postgresql | | | foreign_data_user
+ regression | sc | regression | dummy | | | foreign_data_user
regression | t1 | regression | foo | | | regress_test_indirect
regression | t2 | regression | foo | | | regress_test_role
! (7 rows)
SELECT * FROM information_schema.foreign_server_options ORDER BY 1, 2, 3;
foreign_server_catalog | foreign_server_name | option_name | option_value
*************** SELECT * FROM information_schema.user_ma
*** 671,681 ****
foreign_data_user | regression | s8
PUBLIC | regression | s4
PUBLIC | regression | s8
PUBLIC | regression | t1
regress_test_role | regression | s5
regress_test_role | regression | s6
regress_test_role | regression | t1
! (8 rows)
SELECT * FROM information_schema.user_mapping_options ORDER BY lower(authorization_identifier), 2, 3, 4;
authorization_identifier | foreign_server_catalog | foreign_server_name | option_name | option_value
--- 844,855 ----
foreign_data_user | regression | s8
PUBLIC | regression | s4
PUBLIC | regression | s8
+ PUBLIC | regression | sc
PUBLIC | regression | t1
regress_test_role | regression | s5
regress_test_role | regression | s6
regress_test_role | regression | t1
! (9 rows)
SELECT * FROM information_schema.user_mapping_options ORDER BY lower(authorization_identifier), 2, 3, 4;
authorization_identifier | foreign_server_catalog | foreign_server_name | option_name | option_value
*************** SELECT * FROM information_schema.role_us
*** 707,712 ****
--- 881,905 ----
foreign_data_user | regress_test_role2 | regression | | s6 | FOREIGN SERVER | USAGE | YES
(4 rows)
+ SELECT * FROM information_schema.foreign_tables ORDER BY 1, 2, 3;
+ foreign_table_catalog | foreign_table_schema | foreign_table_name | foreign_server_catalog | foreign_server_name
+ -----------------------+----------------------+--------------------+------------------------+---------------------
+ regression | public | ft2 | regression | sc
+ (1 row)
+
+ SELECT * FROM information_schema.foreign_table_options ORDER BY 1, 2, 3, 4;
+ foreign_table_catalog | foreign_table_schema | foreign_table_name | option_name | option_value
+ -----------------------+----------------------+--------------------+-------------+--------------
+ regression | public | ft2 | delimiter |
+ regression | public | ft2 | quote | `
+ (2 rows)
+
+ SELECT * FROM information_schema.column_options ORDER BY 1, 2, 3, 4, 5;
+ table_catalog | table_schema | table_name | column_name | option_name | option_value
+ ---------------+--------------+------------+-------------+----------------+--------------
+ regression | public | ft2 | c1 | force_not_null | false
+ (1 row)
+
SET ROLE regress_test_role;
SELECT * FROM information_schema.user_mapping_options ORDER BY 1, 2, 3, 4;
authorization_identifier | foreign_server_catalog | foreign_server_name | option_name | option_value
*************** DROP SERVER s9 CASCADE;
*** 939,944 ****
--- 1132,1141 ----
ERROR: must be owner of foreign server s9
RESET ROLE;
-- Cleanup
+ DROP SCHEMA foreign_schema CASCADE;
+ DROP TABLE t1 CASCADE;
+ NOTICE: drop cascades to foreign table ft2
+ DROP TABLE t2 CASCADE;
DROP ROLE regress_test_role; -- ERROR
ERROR: role "regress_test_role" cannot be dropped because some objects depend on it
DETAIL: privileges for server s4
*************** DROP ROLE unprivileged_role;
*** 974,985 ****
DROP ROLE regress_test_role2;
DROP FOREIGN DATA WRAPPER postgresql CASCADE;
DROP FOREIGN DATA WRAPPER dummy CASCADE;
\c
DROP ROLE foreign_data_user;
-- At this point we should have no wrappers, no servers, and no mappings.
! SELECT fdwname, fdwvalidator, fdwoptions FROM pg_foreign_data_wrapper;
! fdwname | fdwvalidator | fdwoptions
! ---------+--------------+------------
(0 rows)
SELECT srvname, srvoptions FROM pg_foreign_server;
--- 1171,1185 ----
DROP ROLE regress_test_role2;
DROP FOREIGN DATA WRAPPER postgresql CASCADE;
DROP FOREIGN DATA WRAPPER dummy CASCADE;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to server sc
+ drop cascades to user mapping for public
\c
DROP ROLE foreign_data_user;
-- At this point we should have no wrappers, no servers, and no mappings.
! SELECT fdwname, fdwvalidator, fdwhandler, fdwoptions FROM pg_foreign_data_wrapper;
! fdwname | fdwvalidator | fdwhandler | fdwoptions
! ---------+--------------+------------+------------
(0 rows)
SELECT srvname, srvoptions FROM pg_foreign_server;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 9596b0b..1ee820f 100644
*** a/src/test/regress/expected/sanity_check.out
--- b/src/test/regress/expected/sanity_check.out
*************** SELECT relname, relhasindex
*** 102,107 ****
--- 102,108 ----
pg_enum | t
pg_foreign_data_wrapper | t
pg_foreign_server | t
+ pg_foreign_table | t
pg_index | t
pg_inherits | t
pg_language | t
*************** SELECT relname, relhasindex
*** 154,160 ****
timetz_tbl | f
tinterval_tbl | f
varchar_tbl | f
! (143 rows)
--
-- another sanity check: every system catalog that has OIDs should have
--- 155,161 ----
timetz_tbl | f
tinterval_tbl | f
varchar_tbl | f
! (144 rows)
--
-- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 556672d..e30ecbc 100644
*** a/src/test/regress/expected/type_sanity.out
--- b/src/test/regress/expected/type_sanity.out
*************** WHERE p1.typanalyze = p2.oid AND p1.typt
*** 278,284 ****
-- Look for illegal values in pg_class fields
SELECT p1.oid, p1.relname
FROM pg_class as p1
! WHERE p1.relkind NOT IN ('r', 'i', 's', 'S', 'c', 't', 'v');
oid | relname
-----+---------
(0 rows)
--- 278,284 ----
-- Look for illegal values in pg_class fields
SELECT p1.oid, p1.relname
FROM pg_class as p1
! WHERE p1.relkind NOT IN ('r', 'i', 's', 'S', 'c', 't', 'v', 'f');
oid | relname
-----+---------
(0 rows)
diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql
index d89b26d..9f5c85f 100644
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
*************** CREATE ROLE regress_test_role_super SUPE
*** 20,30 ****
CREATE ROLE regress_test_indirect;
CREATE ROLE unprivileged_role;
CREATE FOREIGN DATA WRAPPER dummy;
CREATE FOREIGN DATA WRAPPER postgresql VALIDATOR postgresql_fdw_validator;
-- At this point we should have 2 built-in wrappers and no servers.
! SELECT fdwname, fdwvalidator::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3;
SELECT srvname, srvoptions FROM pg_foreign_server;
SELECT * FROM pg_user_mapping;
--- 20,32 ----
CREATE ROLE regress_test_indirect;
CREATE ROLE unprivileged_role;
+ CREATE SCHEMA foreign_schema;
+
CREATE FOREIGN DATA WRAPPER dummy;
CREATE FOREIGN DATA WRAPPER postgresql VALIDATOR postgresql_fdw_validator;
-- At this point we should have 2 built-in wrappers and no servers.
! SELECT fdwname, fdwvalidator::regproc, fdwhandler::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY fdwname;
SELECT srvname, srvoptions FROM pg_foreign_server;
SELECT * FROM pg_user_mapping;
*************** CREATE SERVER t2 FOREIGN DATA WRAPPER fo
*** 147,152 ****
--- 149,156 ----
RESET ROLE;
REVOKE regress_test_indirect FROM regress_test_role;
+ CREATE SERVER sc FOREIGN DATA WRAPPER dummy;
+
-- ALTER SERVER
ALTER SERVER s0; -- ERROR
ALTER SERVER s0 OPTIONS (a '1'); -- ERROR
*************** CREATE USER MAPPING FOR current_user SER
*** 219,224 ****
--- 223,229 ----
CREATE USER MAPPING FOR current_user SERVER s7; -- ERROR
CREATE USER MAPPING FOR public SERVER s8; -- ERROR
RESET ROLE;
+ CREATE USER MAPPING FOR public SERVER sc;
ALTER SERVER t1 OWNER TO regress_test_indirect;
SET ROLE regress_test_role;
*************** RESET ROLE;
*** 254,259 ****
--- 259,345 ----
DROP SERVER s7;
\deu
+ -- CREATE FOREIGN TABLE
+ CREATE TABLE t1 (
+ c1 integer NOT NULL,
+ c2 text DEFAULT 'foo',
+ c3 date CHECK (c3 > '2000-01-01'::date),
+ CONSTRAINT t1_c1_check CHECK (c1 > 0)
+ );
+ CREATE TABLE t2 ();
+ CREATE TABLE f1
+ CREATE FOREIGN TABLE ft1 (); -- ERROR
+ CREATE FOREIGN TABLE ft1 () SERVER no_server; -- ERROR
+ CREATE FOREIGN TABLE ft1 () INHERITS () SERVER sc; -- ERROR
+ CREATE FOREIGN TABLE ft1 () INHERITS (no_table) SERVER sc; -- ERROR
+ CREATE FOREIGN TABLE ft1 (c1 serial) SERVER sc; -- ERROR
+ CREATE FOREIGN TABLE ft1 () SERVER sc WITH OIDS; -- ERROR
+ CREATE FOREIGN TABLE ft1 (
+ c1 integer OPTIONS (force_not_null 'true') NOT NULL,
+ c2 text DEFAULT 'foo',
+ c3 date CHECK (c3 > '2000-01-01'::date),
+ CONSTRAINT ft1_c1_check CHECK (c1 > 0)
+ ) SERVER sc OPTIONS (delimiter ',', quote '"');
+ CREATE RULE ft1_insert_rule AS ON INSERT TO ft1 DO INSTEAD
+ INSERT INTO t1 VALUES (new.c1, new.c2, new.c3);
+ \d+ ft1
+ CREATE FOREIGN TABLE ft2 () INHERITS (t1) SERVER sc OPTIONS (delimiter ' ', quote '`');
+ \d+ ft2
+ \det+
+ CREATE INDEX id_ft1_c2 ON ft1 (c2); -- ERROR
+
+ -- ALTER FOREIGN TABLE
+ COMMENT ON FOREIGN TABLE ft1 IS 'foreign table';
+ COMMENT ON FOREIGN TABLE ft1 IS NULL;
+ COMMENT ON COLUMN ft1.c1 IS 'foreign column';
+ COMMENT ON COLUMN ft1.c1 IS NULL;
+
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c4 integer;
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c5 integer DEFAULT 0;
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c6 integer;
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c7 integer NOT NULL;
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c8 integer;
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c9 integer;
+ ALTER FOREIGN TABLE ft1 ADD COLUMN c10 integer;
+
+ ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (ADD force_not_null 'true');
+ ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (SET force_not_null 'false');
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (DROP force_not_null);
+
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c4 SET DEFAULT 0;
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c5 DROP DEFAULT;
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c6 SET NOT NULL;
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c7 DROP NOT NULL;
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE char(10) using '0'; -- ERROR
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE char(10);
+ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE text;
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0);
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check;
+ ALTER FOREIGN TABLE ft1 SET WITH OIDS; -- ERROR
+ ALTER FOREIGN TABLE ft1 ENABLE RULE ft1_insert_rule;
+ ALTER FOREIGN TABLE ft1 ENABLE ALWAYS RULE ft1_insert_rule;
+ ALTER FOREIGN TABLE ft1 ENABLE REPLICA RULE ft1_insert_rule;
+ ALTER FOREIGN TABLE ft1 DISABLE RULE ft1_insert_rule;
+ ALTER FOREIGN TABLE ft1 INHERIT t2;
+ ALTER FOREIGN TABLE ft1 NO INHERIT t2;
+ ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
+ ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@');
+ ALTER FOREIGN TABLE ft1 DROP COLUMN no_column; -- ERROR
+ ALTER FOREIGN TABLE ft1 DROP COLUMN IF EXISTS no_column;
+ ALTER FOREIGN TABLE ft1 DROP COLUMN c9;
+ ALTER FOREIGN TABLE ft1 SET SCHEMA foreign_schema;
+ ALTER FOREIGN TABLE ft1 SET TABLESPACE ts; -- ERROR
+ ALTER FOREIGN TABLE foreign_schema.ft1 RENAME c1 TO foreign_column_1;
+ ALTER FOREIGN TABLE foreign_schema.ft1 RENAME TO foreign_table_1;
+ \d foreign_schema.foreign_table_1
+
+ -- DROP FOREIGN TABLE
+ DROP FOREIGN TABLE no_table; -- ERROR
+ DROP FOREIGN TABLE IF EXISTS no_table;
+ DROP FOREIGN TABLE foreign_schema.foreign_table_1;
+
-- Information schema
SELECT * FROM information_schema.foreign_data_wrappers ORDER BY 1, 2;
*************** SELECT * FROM information_schema.user_ma
*** 264,269 ****
--- 350,358 ----
SELECT * FROM information_schema.user_mapping_options ORDER BY lower(authorization_identifier), 2, 3, 4;
SELECT * FROM information_schema.usage_privileges WHERE object_type LIKE 'FOREIGN%' ORDER BY 1, 2, 3, 4, 5;
SELECT * FROM information_schema.role_usage_grants WHERE object_type LIKE 'FOREIGN%' ORDER BY 1, 2, 3, 4, 5;
+ SELECT * FROM information_schema.foreign_tables ORDER BY 1, 2, 3;
+ SELECT * FROM information_schema.foreign_table_options ORDER BY 1, 2, 3, 4;
+ SELECT * FROM information_schema.column_options ORDER BY 1, 2, 3, 4, 5;
SET ROLE regress_test_role;
SELECT * FROM information_schema.user_mapping_options ORDER BY 1, 2, 3, 4;
SELECT * FROM information_schema.usage_privileges WHERE object_type LIKE 'FOREIGN%' ORDER BY 1, 2, 3, 4, 5;
*************** DROP SERVER s9 CASCADE;
*** 367,372 ****
--- 456,464 ----
RESET ROLE;
-- Cleanup
+ DROP SCHEMA foreign_schema CASCADE;
+ DROP TABLE t1 CASCADE;
+ DROP TABLE t2 CASCADE;
DROP ROLE regress_test_role; -- ERROR
DROP SERVER s5 CASCADE;
DROP SERVER t1 CASCADE;
*************** DROP FOREIGN DATA WRAPPER dummy CASCADE;
*** 386,391 ****
DROP ROLE foreign_data_user;
-- At this point we should have no wrappers, no servers, and no mappings.
! SELECT fdwname, fdwvalidator, fdwoptions FROM pg_foreign_data_wrapper;
SELECT srvname, srvoptions FROM pg_foreign_server;
SELECT * FROM pg_user_mapping;
--- 478,483 ----
DROP ROLE foreign_data_user;
-- At this point we should have no wrappers, no servers, and no mappings.
! SELECT fdwname, fdwvalidator, fdwhandler, fdwoptions FROM pg_foreign_data_wrapper;
SELECT srvname, srvoptions FROM pg_foreign_server;
SELECT * FROM pg_user_mapping;
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index af7aa2d..fa6dd75 100644
*** a/src/test/regress/sql/type_sanity.sql
--- b/src/test/regress/sql/type_sanity.sql
*************** WHERE p1.typanalyze = p2.oid AND p1.typt
*** 217,223 ****
SELECT p1.oid, p1.relname
FROM pg_class as p1
! WHERE p1.relkind NOT IN ('r', 'i', 's', 'S', 'c', 't', 'v');
-- Indexes should have an access method, others not.
--- 217,223 ----
SELECT p1.oid, p1.relname
FROM pg_class as p1
! WHERE p1.relkind NOT IN ('r', 'i', 's', 'S', 'c', 't', 'v', 'f');
-- Indexes should have an access method, others not.