Skip to content

Commit a0c6dfe

Browse files
committed
Allow default expressions to be attached to columns of foreign tables.
There's still some discussion about exactly how postgres_fdw ought to handle this case, but there seems no debate that we want to allow defaults to be used for inserts into foreign tables. So remove the core-code restrictions that prevented it. While at it, get rid of the special grammar productions for CREATE FOREIGN TABLE, and instead add explicit FEATURE_NOT_SUPPORTED error checks for the disallowed cases. This makes the grammar a shade smaller, and more importantly results in much more intelligible error messages for unsupported cases. It's also one less thing to fix if we ever start supporting constraints on foreign tables.
1 parent 1ba0119 commit a0c6dfe

File tree

7 files changed

+123
-64
lines changed

7 files changed

+123
-64
lines changed

doc/src/sgml/ref/alter_foreign_table.sgml

+18-1
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
3232

3333
<phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
3434

35-
ADD [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ NULL | NOT NULL ]
35+
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> [ ... ] ]
3636
DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="PARAMETER">column_name</replaceable> [ RESTRICT | CASCADE ]
3737
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="PARAMETER">data_type</replaceable>
38+
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET DEFAULT <replaceable class="PARAMETER">expression</replaceable>
39+
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> DROP DEFAULT
3840
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> { SET | DROP } NOT NULL
3941
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET STATISTICS <replaceable class="PARAMETER">integer</replaceable>
4042
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
5961
<para>
6062
This form adds a new column to the foreign table, using the same syntax as
6163
<xref linkend="SQL-CREATEFOREIGNTABLE">.
64+
Unlike the case when adding a column to a regular table, nothing happens
65+
to the underlying storage: this action simply declares that
66+
some new column is now accessible through the foreign table.
6267
</para>
6368
</listitem>
6469
</varlistentry>
@@ -97,6 +102,18 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
97102
</listitem>
98103
</varlistentry>
99104

105+
<varlistentry>
106+
<term><literal>SET</literal>/<literal>DROP DEFAULT</literal></term>
107+
<listitem>
108+
<para>
109+
These forms set or remove the default value for a column.
110+
Default values only apply in subsequent <command>INSERT</command>
111+
or <command>UPDATE</> commands; they do not cause rows already in the
112+
table to change.
113+
</para>
114+
</listitem>
115+
</varlistentry>
116+
100117
<varlistentry>
101118
<term><literal>SET</literal>/<literal>DROP NOT NULL</literal></term>
102119
<listitem>

doc/src/sgml/ref/create_foreign_table.sgml

+30-1
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@
1919
<refsynopsisdiv>
2020
<synopsis>
2121
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
22-
{ <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 ] }
22+
<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> [ ... ] ]
2323
[, ... ]
2424
] )
2525
SERVER <replaceable class="parameter">server_name</replaceable>
2626
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
2727

28+
<phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
29+
30+
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
31+
{ NOT NULL |
32+
NULL |
33+
DEFAULT <replaceable>default_expr</replaceable> }
2834
</synopsis>
2935
</refsynopsisdiv>
3036

@@ -131,6 +137,27 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
131137
</listitem>
132138
</varlistentry>
133139

140+
<varlistentry>
141+
<term><literal>DEFAULT
142+
<replaceable>default_expr</replaceable></literal></term>
143+
<listitem>
144+
<para>
145+
The <literal>DEFAULT</> clause assigns a default data value for
146+
the column whose column definition it appears within. The value
147+
is any variable-free expression (subqueries and cross-references
148+
to other columns in the current table are not allowed). The
149+
data type of the default expression must match the data type of the
150+
column.
151+
</para>
152+
153+
<para>
154+
The default expression will be used in any insert operation that
155+
does not specify a value for the column. If there is no default
156+
for a column, then the default is null.
157+
</para>
158+
</listitem>
159+
</varlistentry>
160+
134161
<varlistentry>
135162
<term><replaceable class="PARAMETER">server_name</replaceable></term>
136163
<listitem>
@@ -190,6 +217,8 @@ SERVER film_server;
190217
<acronym>SQL</acronym> standard; however, much as with
191218
<link linkend="sql-createtable"><command>CREATE TABLE</></link>,
192219
<literal>NULL</> constraints and zero-column foreign tables are permitted.
220+
The ability to specify a default value is also a <productname>PostgreSQL</>
221+
extension.
193222
</para>
194223

195224
</refsect1>

src/backend/commands/tablecmds.c

+2-12
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
465465
if (stmt->constraints != NIL && relkind == RELKIND_FOREIGN_TABLE)
466466
ereport(ERROR,
467467
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
468-
errmsg("constraints on foreign tables are not supported")));
468+
errmsg("constraints are not supported on foreign tables")));
469469

470470
/*
471471
* Look up the namespace in which we are supposed to create the relation,
@@ -588,11 +588,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
588588
{
589589
RawColumnDefault *rawEnt;
590590

591-
if (relkind == RELKIND_FOREIGN_TABLE)
592-
ereport(ERROR,
593-
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
594-
errmsg("default values on foreign tables are not supported")));
595-
596591
Assert(colDef->cooked_default == NULL);
597592

598593
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
@@ -2978,7 +2973,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
29782973
* substitutes default values into INSERTs before it expands
29792974
* rules.
29802975
*/
2981-
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW);
2976+
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
29822977
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
29832978
/* No command-specific prep needed */
29842979
pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
@@ -4528,11 +4523,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
45284523
{
45294524
RawColumnDefault *rawEnt;
45304525

4531-
if (relkind == RELKIND_FOREIGN_TABLE)
4532-
ereport(ERROR,
4533-
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
4534-
errmsg("default values on foreign tables are not supported")));
4535-
45364526
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
45374527
rawEnt->attnum = attribute.attnum;
45384528
rawEnt->raw_default = copyObject(colDef->raw_default);

src/backend/parser/gram.y

+8-30
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
330330
%type <list> stmtblock stmtmulti
331331
OptTableElementList TableElementList OptInherit definition
332332
OptTypedTableElementList TypedTableElementList
333-
OptForeignTableElementList ForeignTableElementList
334333
reloptions opt_reloptions
335334
OptWith opt_distinct opt_definition func_args func_args_list
336335
func_args_with_defaults func_args_with_defaults_list
@@ -408,7 +407,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
408407
%type <vsetstmt> set_rest set_rest_more SetResetClause FunctionSetResetClause
409408

410409
%type <node> TableElement TypedTableElement ConstraintElem TableFuncElement
411-
ForeignTableElement
412410
%type <node> columnDef columnOptions
413411
%type <defelt> def_elem reloption_elem old_aggr_elem
414412
%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
41374135

41384136
CreateForeignTableStmt:
41394137
CREATE FOREIGN TABLE qualified_name
4140-
OptForeignTableElementList
4138+
'(' OptTableElementList ')'
41414139
SERVER name create_generic_options
41424140
{
41434141
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
41444142
$4->relpersistence = RELPERSISTENCE_PERMANENT;
41454143
n->base.relation = $4;
4146-
n->base.tableElts = $5;
4144+
n->base.tableElts = $6;
41474145
n->base.inhRelations = NIL;
41484146
n->base.if_not_exists = false;
41494147
/* FDW-specific data */
4150-
n->servername = $7;
4151-
n->options = $8;
4148+
n->servername = $9;
4149+
n->options = $10;
41524150
$$ = (Node *) n;
41534151
}
41544152
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
4155-
OptForeignTableElementList
4153+
'(' OptTableElementList ')'
41564154
SERVER name create_generic_options
41574155
{
41584156
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
41594157
$7->relpersistence = RELPERSISTENCE_PERMANENT;
41604158
n->base.relation = $7;
4161-
n->base.tableElts = $8;
4159+
n->base.tableElts = $9;
41624160
n->base.inhRelations = NIL;
41634161
n->base.if_not_exists = true;
41644162
/* FDW-specific data */
4165-
n->servername = $10;
4166-
n->options = $11;
4163+
n->servername = $12;
4164+
n->options = $13;
41674165
$$ = (Node *) n;
41684166
}
41694167
;
41704168

4171-
OptForeignTableElementList:
4172-
'(' ForeignTableElementList ')' { $$ = $2; }
4173-
| '(' ')' { $$ = NIL; }
4174-
;
4175-
4176-
ForeignTableElementList:
4177-
ForeignTableElement
4178-
{
4179-
$$ = list_make1($1);
4180-
}
4181-
| ForeignTableElementList ',' ForeignTableElement
4182-
{
4183-
$$ = lappend($1, $3);
4184-
}
4185-
;
4186-
4187-
ForeignTableElement:
4188-
columnDef { $$ = $1; }
4189-
;
4190-
41914169
/*****************************************************************************
41924170
*
41934171
* QUERY:

src/backend/parser/parse_utilcmd.c

+52-5
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ typedef struct
7070
RangeVar *relation; /* relation to create */
7171
Relation rel; /* opened/locked rel, if ALTER */
7272
List *inhRelations; /* relations to inherit from */
73+
bool isforeign; /* true if CREATE/ALTER FOREIGN TABLE */
7374
bool isalter; /* true if altering existing table */
7475
bool hasoids; /* does relation have an OID column? */
7576
List *columns; /* ColumnDef items */
@@ -195,9 +196,15 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
195196

196197
cxt.pstate = pstate;
197198
if (IsA(stmt, CreateForeignTableStmt))
199+
{
198200
cxt.stmtType = "CREATE FOREIGN TABLE";
201+
cxt.isforeign = true;
202+
}
199203
else
204+
{
200205
cxt.stmtType = "CREATE TABLE";
206+
cxt.isforeign = false;
207+
}
201208
cxt.relation = stmt->relation;
202209
cxt.rel = NULL;
203210
cxt.inhRelations = stmt->inhRelations;
@@ -515,11 +522,23 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
515522
break;
516523

517524
case CONSTR_CHECK:
525+
if (cxt->isforeign)
526+
ereport(ERROR,
527+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
528+
errmsg("constraints are not supported on foreign tables"),
529+
parser_errposition(cxt->pstate,
530+
constraint->location)));
518531
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
519532
break;
520533

521534
case CONSTR_PRIMARY:
522535
case CONSTR_UNIQUE:
536+
if (cxt->isforeign)
537+
ereport(ERROR,
538+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
539+
errmsg("constraints are not supported on foreign tables"),
540+
parser_errposition(cxt->pstate,
541+
constraint->location)));
523542
if (constraint->keys == NIL)
524543
constraint->keys = list_make1(makeString(column->colname));
525544
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -531,7 +550,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
531550
break;
532551

533552
case CONSTR_FOREIGN:
534-
553+
if (cxt->isforeign)
554+
ereport(ERROR,
555+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
556+
errmsg("constraints are not supported on foreign tables"),
557+
parser_errposition(cxt->pstate,
558+
constraint->location)));
535559
/*
536560
* Fill in the current attribute's name and throw it into the
537561
* list of FK constraints to be processed later.
@@ -555,8 +579,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
555579
}
556580

557581
/*
558-
* Generate ALTER FOREIGN TABLE ALTER COLUMN statement which adds
559-
* per-column foreign data wrapper options for this column.
582+
* If needed, generate ALTER FOREIGN TABLE ALTER COLUMN statement to add
583+
* per-column foreign data wrapper options to this column after creation.
560584
*/
561585
if (column->fdwoptions != NIL)
562586
{
@@ -587,6 +611,13 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
587611
static void
588612
transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
589613
{
614+
if (cxt->isforeign)
615+
ereport(ERROR,
616+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
617+
errmsg("constraints are not supported on foreign tables"),
618+
parser_errposition(cxt->pstate,
619+
constraint->location)));
620+
590621
switch (constraint->contype)
591622
{
592623
case CONSTR_PRIMARY:
@@ -640,7 +671,14 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
640671
char *comment;
641672
ParseCallbackState pcbstate;
642673

643-
setup_parser_errposition_callback(&pcbstate, cxt->pstate, table_like_clause->relation->location);
674+
setup_parser_errposition_callback(&pcbstate, cxt->pstate,
675+
table_like_clause->relation->location);
676+
677+
/* we could support LIKE in many cases, but worry about it another day */
678+
if (cxt->isforeign)
679+
ereport(ERROR,
680+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
681+
errmsg("LIKE is not supported for foreign tables")));
644682

645683
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
646684

@@ -2334,7 +2372,16 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
23342372
pstate->p_sourcetext = queryString;
23352373

23362374
cxt.pstate = pstate;
2337-
cxt.stmtType = "ALTER TABLE";
2375+
if (stmt->relkind == OBJECT_FOREIGN_TABLE)
2376+
{
2377+
cxt.stmtType = "ALTER FOREIGN TABLE";
2378+
cxt.isforeign = true;
2379+
}
2380+
else
2381+
{
2382+
cxt.stmtType = "ALTER TABLE";
2383+
cxt.isforeign = false;
2384+
}
23382385
cxt.relation = stmt->relation;
23392386
cxt.rel = rel;
23402387
cxt.inhRelations = NIL;

0 commit comments

Comments
 (0)