diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml index f0b3129ab090a285700a6f2750c990e40b5d4018..723aa075c5794c64bd00d3f2ae209935ddf49889 100644 --- a/doc/src/sgml/ref/alter_foreign_table.sgml +++ b/doc/src/sgml/ref/alter_foreign_table.sgml @@ -32,9 +32,11 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab <phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase> - ADD [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ NULL | NOT NULL ] + ADD [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ COLLATE <replaceable class="PARAMETER">collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ] DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="PARAMETER">column_name</replaceable> [ RESTRICT | CASCADE ] ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="PARAMETER">data_type</replaceable> + ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET DEFAULT <replaceable class="PARAMETER">expression</replaceable> + ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> DROP DEFAULT ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> { SET | DROP } NOT NULL ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET STATISTICS <replaceable class="PARAMETER">integer</replaceable> ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] ) @@ -59,6 +61,9 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab <para> This form adds a new column to the foreign table, using the same syntax as <xref linkend="SQL-CREATEFOREIGNTABLE">. + Unlike the case when adding a column to a regular table, nothing happens + to the underlying storage: this action simply declares that + some new column is now accessible through the foreign table. </para> </listitem> </varlistentry> @@ -97,6 +102,18 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab </listitem> </varlistentry> + <varlistentry> + <term><literal>SET</literal>/<literal>DROP DEFAULT</literal></term> + <listitem> + <para> + These forms set or remove the default value for a column. + Default values only apply in subsequent <command>INSERT</command> + or <command>UPDATE</> commands; they do not cause rows already in the + table to change. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><literal>SET</literal>/<literal>DROP NOT NULL</literal></term> <listitem> diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml index 0a6ac29d4d995c306b133d9c84ea9bdd40cab42f..1ef4b5e9d7427e1babad14a5c329e03c0bd6604a 100644 --- a/doc/src/sgml/ref/create_foreign_table.sgml +++ b/doc/src/sgml/ref/create_foreign_table.sgml @@ -19,12 +19,18 @@ <refsynopsisdiv> <synopsis> CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [ - { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ NULL | NOT NULL ] } + <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ] [, ... ] ] ) SERVER <replaceable class="parameter">server_name</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] +<phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase> + +[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ] +{ NOT NULL | + NULL | + DEFAULT <replaceable>default_expr</replaceable> } </synopsis> </refsynopsisdiv> @@ -131,6 +137,27 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name </listitem> </varlistentry> + <varlistentry> + <term><literal>DEFAULT + <replaceable>default_expr</replaceable></literal></term> + <listitem> + <para> + The <literal>DEFAULT</> clause assigns a default data value for + the column whose column definition it appears within. The value + is any variable-free expression (subqueries and cross-references + to other columns in the current table are not allowed). The + data type of the default expression must match the data type of the + column. + </para> + + <para> + The default expression will be used in any insert operation that + does not specify a value for the column. If there is no default + for a column, then the default is null. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><replaceable class="PARAMETER">server_name</replaceable></term> <listitem> @@ -190,6 +217,8 @@ SERVER film_server; <acronym>SQL</acronym> standard; however, much as with <link linkend="sql-createtable"><command>CREATE TABLE</></link>, <literal>NULL</> constraints and zero-column foreign tables are permitted. + The ability to specify a default value is also a <productname>PostgreSQL</> + extension. </para> </refsect1> diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 47b6233a80d39204a51d45bf5b76b675dc0167f1..8bb8f54ae52d23f398e085872c4729dd013a5c78 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -465,7 +465,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId) if (stmt->constraints != NIL && relkind == RELKIND_FOREIGN_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("constraints on foreign tables are not supported"))); + errmsg("constraints are not supported on foreign tables"))); /* * Look up the namespace in which we are supposed to create the relation, @@ -588,11 +588,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId) { RawColumnDefault *rawEnt; - if (relkind == RELKIND_FOREIGN_TABLE) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("default values on foreign tables are not supported"))); - Assert(colDef->cooked_default == NULL); rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); @@ -2978,7 +2973,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, * substitutes default values into INSERTs before it expands * rules. */ - ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW); + ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* No command-specific prep needed */ pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP; @@ -4528,11 +4523,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, { RawColumnDefault *rawEnt; - if (relkind == RELKIND_FOREIGN_TABLE) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("default values on foreign tables are not supported"))); - rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); rawEnt->attnum = attribute.attnum; rawEnt->raw_default = copyObject(colDef->raw_default); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 0787d2f5061c34b63d02b6601935175b839eefd1..9d07f30906c4bfeaa7625e6c0a3e54f343bf9d02 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -330,7 +330,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <list> 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 @@ -408,7 +407,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <vsetstmt> set_rest set_rest_more SetResetClause FunctionSetResetClause %type <node> TableElement TypedTableElement ConstraintElem TableFuncElement - ForeignTableElement %type <node> columnDef columnOptions %type <defelt> def_elem reloption_elem old_aggr_elem %type <node> def_arg columnElem where_clause where_or_current_clause @@ -4137,57 +4135,37 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o CreateForeignTableStmt: CREATE FOREIGN TABLE qualified_name - OptForeignTableElementList + '(' OptTableElementList ')' SERVER name create_generic_options { CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt); $4->relpersistence = RELPERSISTENCE_PERMANENT; n->base.relation = $4; - n->base.tableElts = $5; + n->base.tableElts = $6; n->base.inhRelations = NIL; n->base.if_not_exists = false; /* FDW-specific data */ - n->servername = $7; - n->options = $8; + n->servername = $9; + n->options = $10; $$ = (Node *) n; } | CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name - OptForeignTableElementList + '(' OptTableElementList ')' SERVER name create_generic_options { CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt); $7->relpersistence = RELPERSISTENCE_PERMANENT; n->base.relation = $7; - n->base.tableElts = $8; + n->base.tableElts = $9; n->base.inhRelations = NIL; n->base.if_not_exists = true; /* FDW-specific data */ - n->servername = $10; - n->options = $11; + n->servername = $12; + n->options = $13; $$ = (Node *) n; } ; -OptForeignTableElementList: - '(' ForeignTableElementList ')' { $$ = $2; } - | '(' ')' { $$ = NIL; } - ; - -ForeignTableElementList: - ForeignTableElement - { - $$ = list_make1($1); - } - | ForeignTableElementList ',' ForeignTableElement - { - $$ = lappend($1, $3); - } - ; - -ForeignTableElement: - columnDef { $$ = $1; } - ; - /***************************************************************************** * * QUERY: diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 8a1876c8a39529c3fdbaf62b06e4091396b7979e..4fdcf180fa448aea97801b2418fcb31d835f3a3d 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -70,6 +70,7 @@ typedef struct RangeVar *relation; /* relation to create */ Relation rel; /* opened/locked rel, if ALTER */ List *inhRelations; /* relations to inherit from */ + bool isforeign; /* true if CREATE/ALTER FOREIGN TABLE */ bool isalter; /* true if altering existing table */ bool hasoids; /* does relation have an OID column? */ List *columns; /* ColumnDef items */ @@ -195,9 +196,15 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) cxt.pstate = pstate; if (IsA(stmt, CreateForeignTableStmt)) + { cxt.stmtType = "CREATE FOREIGN TABLE"; + cxt.isforeign = true; + } else + { cxt.stmtType = "CREATE TABLE"; + cxt.isforeign = false; + } cxt.relation = stmt->relation; cxt.rel = NULL; cxt.inhRelations = stmt->inhRelations; @@ -515,11 +522,23 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) break; case CONSTR_CHECK: + if (cxt->isforeign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("constraints are not supported on foreign tables"), + parser_errposition(cxt->pstate, + constraint->location))); cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); break; case CONSTR_PRIMARY: case CONSTR_UNIQUE: + if (cxt->isforeign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("constraints are not supported on foreign tables"), + parser_errposition(cxt->pstate, + constraint->location))); if (constraint->keys == NIL) constraint->keys = list_make1(makeString(column->colname)); cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); @@ -531,7 +550,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) break; case CONSTR_FOREIGN: - + if (cxt->isforeign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("constraints are not supported on foreign tables"), + parser_errposition(cxt->pstate, + constraint->location))); /* * Fill in the current attribute's name and throw it into the * list of FK constraints to be processed later. @@ -555,8 +579,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) } /* - * Generate ALTER FOREIGN TABLE ALTER COLUMN statement which adds - * per-column foreign data wrapper options for this column. + * If needed, generate ALTER FOREIGN TABLE ALTER COLUMN statement to add + * per-column foreign data wrapper options to this column after creation. */ if (column->fdwoptions != NIL) { @@ -587,6 +611,13 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) static void transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) { + if (cxt->isforeign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("constraints are not supported on foreign tables"), + parser_errposition(cxt->pstate, + constraint->location))); + switch (constraint->contype) { case CONSTR_PRIMARY: @@ -640,7 +671,14 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla char *comment; ParseCallbackState pcbstate; - setup_parser_errposition_callback(&pcbstate, cxt->pstate, table_like_clause->relation->location); + setup_parser_errposition_callback(&pcbstate, cxt->pstate, + table_like_clause->relation->location); + + /* we could support LIKE in many cases, but worry about it another day */ + if (cxt->isforeign) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("LIKE is not supported for foreign tables"))); relation = relation_openrv(table_like_clause->relation, AccessShareLock); @@ -2334,7 +2372,16 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) pstate->p_sourcetext = queryString; cxt.pstate = pstate; - cxt.stmtType = "ALTER TABLE"; + if (stmt->relkind == OBJECT_FOREIGN_TABLE) + { + cxt.stmtType = "ALTER FOREIGN TABLE"; + cxt.isforeign = true; + } + else + { + cxt.stmtType = "ALTER TABLE"; + cxt.isforeign = false; + } cxt.relation = stmt->relation; cxt.rel = rel; cxt.inhRelations = NIL; diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out index 1bfdca134bb6d9ab05628c169b035cca77658f45..60506e07b1c94fcfb6e9ab8adb6f016b4a84b7f2 100644 --- a/src/test/regress/expected/foreign_data.out +++ b/src/test/regress/expected/foreign_data.out @@ -664,8 +664,6 @@ LINE 1: CREATE FOREIGN TABLE ft1 (); ^ CREATE FOREIGN TABLE ft1 () SERVER no_server; -- ERROR ERROR: server "no_server" does not exist -CREATE FOREIGN TABLE ft1 (c1 serial) SERVER sc; -- ERROR -ERROR: default values on foreign tables are not supported CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS; -- ERROR ERROR: syntax error at or near "WITH OIDS" LINE 1: CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS; @@ -707,17 +705,14 @@ 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; -- ERROR -ERROR: default values on foreign tables are not supported +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 OPTIONS (p1 'v1'); -ALTER FOREIGN TABLE ft1 ALTER COLUMN c4 SET DEFAULT 0; -- ERROR -ERROR: "ft1" is not a table or view -ALTER FOREIGN TABLE ft1 ALTER COLUMN c5 DROP DEFAULT; -- ERROR -ERROR: "ft1" is not a table or view +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 @@ -739,7 +734,8 @@ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1; c1 | integer | not null | ("param 1" 'val1') | plain | 10000 | c2 | text | | (param2 'val2', param3 'val3') | extended | | c3 | date | | | plain | | - c4 | integer | | | plain | | + c4 | integer | default 0 | | plain | | + c5 | integer | | | plain | | c6 | integer | not null | | plain | | c7 | integer | | (p1 'v1', p2 'v2') | plain | | c8 | text | | (p2 'V2') | extended | | @@ -755,7 +751,9 @@ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer; -- ERROR ERROR: cannot alter foreign table "ft1" because column "use_ft1_column_type.x" uses its row type DROP TABLE use_ft1_column_type; ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0); -- ERROR -ERROR: "ft1" is not a table +ERROR: constraints are not supported on foreign tables +LINE 1: ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c... + ^ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR ERROR: "ft1" is not a table ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const; @@ -783,7 +781,8 @@ ALTER FOREIGN TABLE foreign_schema.ft1 RENAME TO foreign_table_1; foreign_column_1 | integer | not null | ("param 1" 'val1') c2 | text | | (param2 'val2', param3 'val3') c3 | date | | - c4 | integer | | + c4 | integer | default 0 | + c5 | integer | | c6 | integer | not null | c7 | integer | | (p1 'v1', p2 'v2') c8 | text | | (p2 'V2') diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql index 38057db7b449e0c9d4ecb81b3ec7c63b56d981e3..f819eb1b8eb3c9eed49533fd1c287b57fe8632d1 100644 --- a/src/test/regress/sql/foreign_data.sql +++ b/src/test/regress/sql/foreign_data.sql @@ -267,7 +267,6 @@ CREATE SCHEMA foreign_schema; CREATE SERVER s0 FOREIGN DATA WRAPPER dummy; CREATE FOREIGN TABLE ft1 (); -- ERROR CREATE FOREIGN TABLE ft1 () SERVER no_server; -- ERROR -CREATE FOREIGN TABLE ft1 (c1 serial) SERVER sc; -- ERROR CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS; -- ERROR CREATE FOREIGN TABLE ft1 ( c1 integer OPTIONS ("param 1" 'val1') NOT NULL, @@ -289,15 +288,15 @@ 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; -- ERROR +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 OPTIONS (p1 'v1'); -ALTER FOREIGN TABLE ft1 ALTER COLUMN c4 SET DEFAULT 0; -- ERROR -ALTER FOREIGN TABLE ft1 ALTER COLUMN c5 DROP DEFAULT; -- ERROR +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