diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml index f0b3129ab0..723aa075c5 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 ] namewhere action is one of: - ADD [ COLUMN ] column_name data_type [ NULL | NOT NULL ] + ADD [ COLUMN ] column_name data_type [ COLLATE collation ] [ column_constraint [ ... ] ] DROP [ COLUMN ] [ IF EXISTS ] column_name [ RESTRICT | CASCADE ] ALTER [ COLUMN ] column_name [ SET DATA ] TYPE data_type + ALTER [ COLUMN ] column_name SET DEFAULT expression + ALTER [ COLUMN ] column_name DROP DEFAULT ALTER [ COLUMN ] column_name { SET | DROP } NOT NULL ALTER [ COLUMN ] column_name SET STATISTICS integer ALTER [ COLUMN ] column_name SET ( attribute_option = value [, ... ] ) @@ -59,6 +61,9 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name This form adds a new column to the foreign table, using the same syntax as . + 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. @@ -97,6 +102,18 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name + + SET/DROP DEFAULT + + + These forms set or remove the default value for a column. + Default values only apply in subsequent INSERT + or UPDATE commands; they do not cause rows already in the + table to change. + + + + SET/DROP NOT NULL diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml index 0a6ac29d4d..1ef4b5e9d7 100644 --- a/doc/src/sgml/ref/create_foreign_table.sgml +++ b/doc/src/sgml/ref/create_foreign_table.sgml @@ -19,12 +19,18 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name ( [ - { column_name data_type [ OPTIONS ( option 'value' [, ... ] ) ] [ NULL | NOT NULL ] } + column_name data_type [ OPTIONS ( option 'value' [, ... ] ) ] [ COLLATE collation ] [ column_constraint [ ... ] ] [, ... ] ] ) SERVER server_name [ OPTIONS ( option 'value' [, ... ] ) ] +where column_constraint is: + +[ CONSTRAINT constraint_name ] +{ NOT NULL | + NULL | + DEFAULT default_expr } @@ -131,6 +137,27 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name + + DEFAULT + default_expr + + + The 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. + + + + 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. + + + + server_name @@ -190,6 +217,8 @@ SERVER film_server; SQL standard; however, much as with CREATE TABLE, NULL constraints and zero-column foreign tables are permitted. + The ability to specify a default value is also a PostgreSQL + extension. diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 47b6233a80..8bb8f54ae5 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 0787d2f506..9d07f30906 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 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 set_rest set_rest_more SetResetClause FunctionSetResetClause %type TableElement TypedTableElement ConstraintElem TableFuncElement - ForeignTableElement %type columnDef columnOptions %type def_elem reloption_elem old_aggr_elem %type 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 8a1876c8a3..4fdcf180fa 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 1bfdca134b..60506e07b1 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 38057db7b4..f819eb1b8e 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