diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 5e5f8a7554755d6948ab1b3276cf02b5daffe615..6d5dad369d7a5ffbfa26fea3f0c3df64b631e101 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1157,6 +1157,15 @@ + + attfdwoptions + text[] + + + Attribute-level foreign data wrapper options, as keyword=value strings + + + diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml index ed4f1572a0a8a4be80715539ed9306377d80bead..0f0cbfaa83a5007426ac42e83654d2ee5e0a0bd1 100644 --- a/doc/src/sgml/information_schema.sgml +++ b/doc/src/sgml/information_schema.sgml @@ -1018,6 +1018,69 @@ + + <literal>column_options</literal> + + + The view column_options contains all the + options defined for foreign table columns in the current database. Only + those foreign table columns are shown that the current user has access to + (by way of being the owner or having some privilege). + + + + <literal>column_options</literal> Columns + + + + + Name + Data Type + Description + + + + + + table_catalog + sql_identifier + Name of the database that contains the foreign table (always the current database) + + + + table_schema + sql_identifier + Name of the schema that contains the foreign table + + + + table_name + sql_identifier + Name of the foreign 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 + + + +
+
+ <literal>column_privileges</literal> diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml index a45df020ea143f099719de827410190a5176564d..a422c88a4f6d3785861a76d8918df026fc996c6d 100644 --- a/doc/src/sgml/ref/alter_foreign_table.sgml +++ b/doc/src/sgml/ref/alter_foreign_table.sgml @@ -36,6 +36,7 @@ ALTER FOREIGN TABLE name DROP [ COLUMN ] [ IF EXISTS ] column [ RESTRICT | CASCADE ] ALTER [ COLUMN ] column [ SET DATA ] TYPE type ALTER [ COLUMN ] column { SET | DROP } NOT NULL + ALTER [ COLUMN ] column OPTIONS ( [ ADD | SET | DROP ] option ['value'] [, ... ]) OWNER TO new_owner OPTIONS ( [ ADD | SET | DROP ] option ['value'] [, ... ]) @@ -125,12 +126,13 @@ ALTER FOREIGN TABLE name OPTIONS ( [ ADD | SET | DROP ] option ['value'] [, ... ] ) - Change options for the foreign table. + Change options for the foreign table or one of its columns. 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. + if no operation is explicitly specified. Duplicate option names are not + allowed (although it's OK for a table option and a column option to have + the same name). Option names and values are also validated using the + foreign data wrapper library. diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml index ad91072bd125ccda4f70e6f35f450281ed00ba35..5e58fb8c5eb6ab3999883d43838d567502bfeefd 100644 --- a/doc/src/sgml/ref/create_foreign_table.sgml +++ b/doc/src/sgml/ref/create_foreign_table.sgml @@ -19,7 +19,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name ( [ - { column_name data_type [ NULL | NOT NULL ] } + { column_name data_type [ OPTIONS ( option 'value' [, ... ] ) ] [ NULL | NOT NULL ] } [, ... ] ] ) SERVER server_name @@ -138,10 +138,12 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name OPTIONS ( option 'value' [, ...] ) - Options to be associated with the new foreign table. + Options to be associated with the new foreign table or one of its + columns. The allowed option names and values are specific to each foreign data wrapper and are validated using the foreign-data wrapper's - validator function. Option names must be unique. + validator function. Duplicate option names are not allowed (although + it's OK for a table option and a column option to have the same name). diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index ecfafadc61575fd0cb4cdf472104f7b454de6aa5..98cb6bd24fbd2e8baae0f34f31980a6ec5706415 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -891,6 +891,12 @@ testdb=> below.) + + For some types of relation, \d shows additional information + for each column: column values for sequences, indexed expression for + indexes and per-column foreign data wrapper options for foreign tables. + + The command form \d+ is identical, except that more information is displayed: any comments associated with the diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 4656dba642cdd0fec56ea73da6d8357d6ab848bf..9e931df4b141a72ab68d064404e6d8e5bbc9ca26 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -363,7 +363,7 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) return false; if (attr1->attcollation != attr2->attcollation) return false; - /* attacl and attoptions are not even present... */ + /* attacl, attoptions and attfdwoptions are not even present... */ } if (tupdesc1->constr != NULL) @@ -483,7 +483,7 @@ TupleDescInitEntry(TupleDesc desc, att->attisdropped = false; att->attislocal = true; att->attinhcount = 0; - /* attacl and attoptions are not present in tupledescs */ + /* attacl, attoptions and attfdwoptions are not present in tupledescs */ tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(oidtypeid)); if (!HeapTupleIsValid(tuple)) diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl index 0aeaf5bfd76c7e34f01f96fb35bbd1d3f2c4bf40..d91af529f43d5c23d05c986b745bee80a971db3a 100644 --- a/src/backend/catalog/genbki.pl +++ b/src/backend/catalog/genbki.pl @@ -369,7 +369,8 @@ sub emit_pgattr_row attislocal => 't', attinhcount => '0', attacl => '_null_', - attoptions => '_null_' + attoptions => '_null_', + attfdwoptions => '_null_' ); return {%PGATTR_DEFAULTS, %row}; } @@ -400,6 +401,7 @@ sub emit_schemapg_row # Only the fixed-size portions of the descriptors are ever used. delete $row->{attacl}; delete $row->{attoptions}; + delete $row->{attfdwoptions}; # 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 439949362570d65d63ccc5e04b2b8c5565bb313b..7ec658146f093ccce94356f9eef9fefafbc303d8 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -126,7 +126,7 @@ static List *insert_ordered_unique_oid(List *list, Oid datum); */ /* - * The initializers below do not include the attoptions or attacl fields, + * The initializers below do not include trailing variable length fields, * but that's OK - we're never going to reference anything beyond the * fixed-size portion of the structure anyway. */ @@ -620,6 +620,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel, /* 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_attfdwoptions - 1] = true; tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls); diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql index 80cd091ec875f3cd035bb72f5d4140d31384181d..47c48bfb275f93e6ee73ad24fa6078dae01c279f 100644 --- a/src/backend/catalog/information_schema.sql +++ b/src/backend/catalog/information_schema.sql @@ -2534,6 +2534,39 @@ GRANT SELECT ON element_types TO PUBLIC; -- SQL/MED views; these use section numbers from part 9 of the standard. +/* Base view for foreign table columns */ +CREATE VIEW _pg_foreign_table_columns AS + SELECT n.nspname, + c.relname, + a.attname, + a.attfdwoptions + FROM pg_foreign_table t, pg_authid u, pg_namespace n, pg_class c, + pg_attribute a + WHERE u.oid = c.relowner + AND (pg_has_role(c.relowner, 'USAGE') + OR has_column_privilege(c.oid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES')) + AND n.oid = c.relnamespace + AND c.oid = t.ftrelid + AND c.relkind = 'f' + AND a.attrelid = c.oid + AND a.attnum > 0; + +/* + * 24.2 + * COLUMN_OPTIONS view + */ +CREATE VIEW column_options AS + SELECT CAST(current_database() AS sql_identifier) AS table_catalog, + c.nspname AS table_schema, + c.relname AS table_name, + c.attname AS column_name, + CAST((pg_options_to_table(c.attfdwoptions)).option_name AS sql_identifier) AS option_name, + CAST((pg_options_to_table(c.attfdwoptions)).option_value AS character_data) AS option_value + FROM _pg_foreign_table_columns c; + +GRANT SELECT ON column_options TO PUBLIC; + + /* Base view for foreign-data wrappers */ CREATE VIEW _pg_foreign_data_wrappers AS SELECT w.oid, diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 82bb756c75e0f422247ff67f28fcc12c3deca983..4509cdab90045b37a136c15b20eccc759ea6c6b4 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -346,6 +346,8 @@ static void ATPrepAlterColumnType(List **wqueue, static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno); static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd, LOCKMODE lockmode); +static void ATExecAlterColumnGenericOptions(Relation rel, const char *colName, + List *options, LOCKMODE lockmode); static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode); static void ATPostAlterTypeParse(Oid oldId, char *cmd, List **wqueue, LOCKMODE lockmode, bool rewrite); @@ -2648,6 +2650,7 @@ AlterTableGetLockLevel(List *cmds) case AT_DropNotNull: /* may change some SQL plans */ case AT_SetNotNull: case AT_GenericOptions: + case AT_AlterColumnGenericOptions: cmd_lockmode = AccessExclusiveLock; break; @@ -2925,6 +2928,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode); pass = AT_PASS_ALTER_TYPE; break; + case AT_AlterColumnGenericOptions: + ATSimplePermissions(rel, ATT_FOREIGN_TABLE); + /* This command never recurses */ + /* No command-specific prep needed */ + pass = AT_PASS_MISC; + break; case AT_ChangeOwner: /* ALTER OWNER */ /* This command never recurses */ /* No command-specific prep needed */ @@ -3169,6 +3178,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, case AT_AlterColumnType: /* ALTER COLUMN TYPE */ ATExecAlterColumnType(tab, rel, cmd, lockmode); break; + case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */ + ATExecAlterColumnGenericOptions(rel, cmd->name, (List *) cmd->def, lockmode); + break; case AT_ChangeOwner: /* ALTER OWNER */ ATExecChangeOwner(RelationGetRelid(rel), get_role_oid(cmd->name, false), @@ -7397,6 +7409,100 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, heap_freetuple(heapTup); } +static void +ATExecAlterColumnGenericOptions(Relation rel, + const char *colName, + List *options, + LOCKMODE lockmode) +{ + Relation ftrel; + Relation attrel; + ForeignServer *server; + ForeignDataWrapper *fdw; + HeapTuple tuple; + HeapTuple newtuple; + bool isnull; + Datum repl_val[Natts_pg_attribute]; + bool repl_null[Natts_pg_attribute]; + bool repl_repl[Natts_pg_attribute]; + Datum datum; + Form_pg_foreign_table fttableform; + Form_pg_attribute atttableform; + + if (options == NIL) + return; + + /* First, determine FDW validator associated to the foreign table. */ + ftrel = heap_open(ForeignTableRelationId, AccessShareLock); + tuple = SearchSysCache1(FOREIGNTABLEREL, rel->rd_id); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("foreign table \"%s\" does not exist", + RelationGetRelationName(rel)))); + fttableform = (Form_pg_foreign_table) GETSTRUCT(tuple); + server = GetForeignServer(fttableform->ftserver); + fdw = GetForeignDataWrapper(server->fdwid); + + heap_close(ftrel, AccessShareLock); + ReleaseSysCache(tuple); + + attrel = heap_open(AttributeRelationId, RowExclusiveLock); + tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + colName, RelationGetRelationName(rel)))); + + /* Prevent them from altering a system attribute */ + atttableform = (Form_pg_attribute) GETSTRUCT(tuple); + if (atttableform->attnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter system column \"%s\"", colName))); + + + /* Initialize buffers for new tuple values */ + 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(ATTNAME, + tuple, + Anum_pg_attribute_attfdwoptions, + &isnull); + if (isnull) + datum = PointerGetDatum(NULL); + + /* Transform the options */ + datum = transformGenericOptions(AttributeRelationId, + datum, + options, + fdw->fdwvalidator); + + if (PointerIsValid(DatumGetPointer(datum))) + repl_val[Anum_pg_attribute_attfdwoptions - 1] = datum; + else + repl_null[Anum_pg_attribute_attfdwoptions - 1] = true; + + repl_repl[Anum_pg_attribute_attfdwoptions - 1] = true; + + /* Everything looks good - update the tuple */ + + newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrel), + repl_val, repl_null, repl_repl); + ReleaseSysCache(tuple); + + simple_heap_update(attrel, &newtuple->t_self, newtuple); + CatalogUpdateIndexes(attrel, newtuple); + + heap_close(attrel, RowExclusiveLock); + + heap_freetuple(newtuple); +} + /* * Cleanup after we've finished all the ALTER TYPE operations for a * particular relation. We have to drop and recreate all the indexes diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 7a5145621f3b09fd72b218d77adefea32f250220..d0704ed0718040390f2a0d37447519a6e5b3b333 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2312,6 +2312,7 @@ _copyColumnDef(ColumnDef *from) COPY_NODE_FIELD(collClause); COPY_SCALAR_FIELD(collOid); COPY_NODE_FIELD(constraints); + COPY_NODE_FIELD(fdwoptions); return newnode; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index b5be09af1a0ced4c761b62366e78387019ceb381..417aeb882212d78576149b3b38a1ef8bee490e13 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2102,6 +2102,7 @@ _outColumnDef(StringInfo str, ColumnDef *node) WRITE_NODE_FIELD(collClause); WRITE_OID_FIELD(collOid); WRITE_NODE_FIELD(constraints); + WRITE_NODE_FIELD(fdwoptions); } static void diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index ac094aa5f3af93383c103d66246f45e5e9fdef73..e9f3896badb55ef56b46aee270b94d5487412d40 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -1769,6 +1769,15 @@ alter_table_cmd: def->raw_default = $8; $$ = (Node *)n; } + /* ALTER FOREIGN TABLE ALTER [COLUMN] OPTIONS */ + | ALTER opt_column ColId alter_generic_options + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_AlterColumnGenericOptions; + n->name = $3; + n->def = (Node *) $4; + $$ = (Node *)n; + } /* ALTER TABLE ADD CONSTRAINT ... */ | ADD_P TableConstraint { @@ -2497,7 +2506,7 @@ TypedTableElement: | TableConstraint { $$ = $1; } ; -columnDef: ColId Typename ColQualList +columnDef: ColId Typename create_generic_options ColQualList { ColumnDef *n = makeNode(ColumnDef); n->colname = $1; @@ -2510,7 +2519,8 @@ columnDef: ColId Typename ColQualList n->raw_default = NULL; n->cooked_default = NULL; n->collOid = InvalidOid; - SplitColQualList($3, &n->constraints, &n->collClause, + n->fdwoptions = $3; + SplitColQualList($4, &n->constraints, &n->collClause, yyscanner); $$ = (Node *)n; } diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 1be2ac68a587ce8610fde07cfa7bc45c5a0d5be0..21b54f7f153383827befd994e1ba3168ed9f9ae8 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -559,6 +559,31 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) break; } } + + /* + * Generate ALTER FOREIGN TABLE ALTER COLUMN statement which adds + * per-column foreign data wrapper options for this column. + */ + if (column->fdwoptions != NIL) + { + AlterTableStmt *stmt; + AlterTableCmd *cmd; + + cmd = makeNode(AlterTableCmd); + cmd->subtype = AT_AlterColumnGenericOptions; + cmd->name = column->colname; + cmd->def = (Node *) column->fdwoptions; + cmd->behavior = DROP_RESTRICT; + cmd->missing_ok = false; + + stmt = makeNode(AlterTableStmt); + stmt->relation = cxt->relation; + stmt->cmds = NIL; + stmt->relkind = OBJECT_FOREIGN_TABLE; + stmt->cmds = lappend(stmt->cmds, cmd); + + cxt->alist = lappend(cxt->alist, stmt); + } } /* diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index f2ee57cabd373913fe9e5083275eab808087779d..cf0fc4b5d3d32af2907aca6ce11ee1408f4c045d 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -5574,6 +5574,7 @@ getTableAttrs(TableInfo *tblinfo, int numTables) int i_attislocal; int i_attoptions; int i_attcollation; + int i_attfdwoptions; PGresult *res; int ntups; bool hasdefaults; @@ -5611,7 +5612,31 @@ getTableAttrs(TableInfo *tblinfo, int numTables) resetPQExpBuffer(q); - if (g_fout->remoteVersion >= 90100) + if (g_fout->remoteVersion >= 90200) + { + /* + * attfdwoptions is new in 9.2. + */ + appendPQExpBuffer(q, "SELECT a.attnum, a.attname, a.atttypmod, " + "a.attstattarget, a.attstorage, t.typstorage, " + "a.attnotnull, a.atthasdef, a.attisdropped, " + "a.attlen, a.attalign, a.attislocal, " + "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, " + "array_to_string(a.attoptions, ', ') AS attoptions, " + "CASE WHEN a.attcollation <> t.typcollation " + "THEN a.attcollation ELSE 0 END AS attcollation, " + "array_to_string(ARRAY(" + " SELECT option_name || ' ' || quote_literal(option_value) " + " FROM pg_options_to_table(attfdwoptions)), ', ') " + " AS attfdwoptions " + "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t " + "ON a.atttypid = t.oid " + "WHERE a.attrelid = '%u'::pg_catalog.oid " + "AND a.attnum > 0::pg_catalog.int2 " + "ORDER BY a.attrelid, a.attnum", + tbinfo->dobj.catId.oid); + } + else if (g_fout->remoteVersion >= 90100) { /* * attcollation is new in 9.1. Since we only want to dump COLLATE @@ -5626,7 +5651,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables) "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, " "array_to_string(a.attoptions, ', ') AS attoptions, " "CASE WHEN a.attcollation <> t.typcollation " - "THEN a.attcollation ELSE 0 END AS attcollation " + "THEN a.attcollation ELSE 0 END AS attcollation, " + "NULL AS attfdwoptions " "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t " "ON a.atttypid = t.oid " "WHERE a.attrelid = '%u'::pg_catalog.oid " @@ -5643,7 +5669,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables) "a.attlen, a.attalign, a.attislocal, " "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, " "array_to_string(a.attoptions, ', ') AS attoptions, " - "0 AS attcollation " + "0 AS attcollation, " + "NULL AS attfdwoptions " "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t " "ON a.atttypid = t.oid " "WHERE a.attrelid = '%u'::pg_catalog.oid " @@ -5659,7 +5686,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables) "a.attnotnull, a.atthasdef, a.attisdropped, " "a.attlen, a.attalign, a.attislocal, " "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, " - "'' AS attoptions, 0 AS attcollation " + "'' AS attoptions, 0 AS attcollation, " + "NULL AS attfdwoptions " "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t " "ON a.atttypid = t.oid " "WHERE a.attrelid = '%u'::pg_catalog.oid " @@ -5680,7 +5708,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables) "false AS attisdropped, a.attlen, " "a.attalign, false AS attislocal, " "format_type(t.oid,a.atttypmod) AS atttypname, " - "'' AS attoptions, 0 AS attcollation " + "'' AS attoptions, 0 AS attcollation, " + "NULL AS attfdwoptions " "FROM pg_attribute a LEFT JOIN pg_type t " "ON a.atttypid = t.oid " "WHERE a.attrelid = '%u'::oid " @@ -5698,7 +5727,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables) "attlen, attalign, " "false AS attislocal, " "(SELECT typname FROM pg_type WHERE oid = atttypid) AS atttypname, " - "'' AS attoptions, 0 AS attcollation " + "'' AS attoptions, 0 AS attcollation, " + "NULL AS attfdwoptions " "FROM pg_attribute a " "WHERE attrelid = '%u'::oid " "AND attnum > 0::int2 " @@ -5726,6 +5756,7 @@ getTableAttrs(TableInfo *tblinfo, int numTables) i_attislocal = PQfnumber(res, "attislocal"); i_attoptions = PQfnumber(res, "attoptions"); i_attcollation = PQfnumber(res, "attcollation"); + i_attfdwoptions = PQfnumber(res, "attfdwoptions"); tbinfo->numatts = ntups; tbinfo->attnames = (char **) malloc(ntups * sizeof(char *)); @@ -5742,6 +5773,7 @@ getTableAttrs(TableInfo *tblinfo, int numTables) tbinfo->attrdefs = (AttrDefInfo **) malloc(ntups * sizeof(AttrDefInfo *)); tbinfo->attoptions = (char **) malloc(ntups * sizeof(char *)); tbinfo->attcollation = (Oid *) malloc(ntups * sizeof(Oid)); + tbinfo->attfdwoptions = (char **) malloc(ntups * sizeof(char *)); tbinfo->inhAttrs = (bool *) malloc(ntups * sizeof(bool)); tbinfo->inhAttrDef = (bool *) malloc(ntups * sizeof(bool)); tbinfo->inhNotNull = (bool *) malloc(ntups * sizeof(bool)); @@ -5768,6 +5800,7 @@ getTableAttrs(TableInfo *tblinfo, int numTables) tbinfo->notnull[j] = (PQgetvalue(res, j, i_attnotnull)[0] == 't'); tbinfo->attoptions[j] = strdup(PQgetvalue(res, j, i_attoptions)); tbinfo->attcollation[j] = atooid(PQgetvalue(res, j, i_attcollation)); + tbinfo->attfdwoptions[j] = strdup(PQgetvalue(res, j, i_attfdwoptions)); tbinfo->attrdefs[j] = NULL; /* fix below */ if (PQgetvalue(res, j, i_atthasdef)[0] == 't') hasdefaults = true; @@ -12469,6 +12502,21 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) appendPQExpBuffer(q, "SET (%s);\n", tbinfo->attoptions[j]); } + + /* + * Dump per-column fdw options. + */ + if (tbinfo->relkind == RELKIND_FOREIGN_TABLE && + tbinfo->attfdwoptions[j] && + tbinfo->attfdwoptions[j][0] != '\0') + { + appendPQExpBuffer(q, "ALTER FOREIGN TABLE %s ", + fmtId(tbinfo->dobj.name)); + appendPQExpBuffer(q, "ALTER COLUMN %s ", + fmtId(tbinfo->attnames[j])); + appendPQExpBuffer(q, "OPTIONS (%s);\n", + tbinfo->attfdwoptions[j]); + } } } diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index c95614b16fa6e1499177bf44f0d5300a2dbe4cb8..3d5d534269f7f1e5828cf4c1824142c8f46319b8 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -275,6 +275,7 @@ typedef struct _tableInfo bool *attislocal; /* true if attr has local definition */ char **attoptions; /* per-attribute options */ Oid *attcollation; /* per-attribute collation selection */ + char **attfdwoptions; /* per-attribute fdw options */ /* * Note: we need to store per-attribute notnull, default, and constraint diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index e46568654e534fbdf46474d4144e4ec70d255abf..a8d5ddc96c22d05f2536dd1e75a2e36c9ac8e6cb 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1281,7 +1281,12 @@ describeOneTableDetails(const char *schemaname, res = NULL; } - /* Get column info */ + /* + * Get column info + * + * You need to modify value of "firstvcol" which willbe defined below if + * you are adding column(s) preceding to verbose-only columns. + */ printfPQExpBuffer(&buf, "SELECT a.attname,"); appendPQExpBuffer(&buf, "\n pg_catalog.format_type(a.atttypid, a.atttypmod)," "\n (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128)" @@ -1295,6 +1300,12 @@ describeOneTableDetails(const char *schemaname, appendPQExpBuffer(&buf, "\n NULL AS attcollation"); if (tableinfo.relkind == 'i') appendPQExpBuffer(&buf, ",\n pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef"); + else + appendPQExpBuffer(&buf, ",\n NULL AS indexdef"); + if (tableinfo.relkind == 'f' && pset.sversion >= 90200) + appendPQExpBuffer(&buf, ",\n a.attfdwoptions"); + else + appendPQExpBuffer(&buf, ",\n NULL AS attfdwoptions"); if (verbose) { appendPQExpBuffer(&buf, ",\n a.attstorage"); @@ -1386,6 +1397,9 @@ describeOneTableDetails(const char *schemaname, if (tableinfo.relkind == 'i') headers[cols++] = gettext_noop("Definition"); + if (tableinfo.relkind == 'f' && pset.sversion >= 90200) + headers[cols++] = gettext_noop("Options"); + if (verbose) { headers[cols++] = gettext_noop("Storage"); @@ -1471,10 +1485,14 @@ describeOneTableDetails(const char *schemaname, if (tableinfo.relkind == 'i') printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false); + /* FDW options for foreign table column, only for 9.2 or later */ + if (tableinfo.relkind == 'f' && pset.sversion >= 90200) + printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false); + /* Storage and Description */ if (verbose) { - int firstvcol = (tableinfo.relkind == 'i' ? 7 : 6); + int firstvcol = 8; char *storage = PQgetvalue(res, i, firstvcol); /* these strings are literal in our syntax, so not translated. */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 2fadf30792f0e5b1a13c564195a64e668c4b165d..f5c9797c60856d27cf9cb834545ccca4466c0c4b 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201107201 +#define CATALOG_VERSION_NO 201108051 #endif diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index 409d6ea3e7e1781d69fbeafc8d427675a9beb390..3ea87e8229ea757fdc6248e42eb3a164b32ed8ff 100644 --- a/src/include/catalog/pg_attribute.h +++ b/src/include/catalog/pg_attribute.h @@ -156,6 +156,9 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK /* Column-level options */ text attoptions[1]; + + /* Column-level FDW options */ + text attfdwoptions[1]; } FormData_pg_attribute; /* @@ -179,7 +182,7 @@ typedef FormData_pg_attribute *Form_pg_attribute; * ---------------- */ -#define Natts_pg_attribute 20 +#define Natts_pg_attribute 21 #define Anum_pg_attribute_attrelid 1 #define Anum_pg_attribute_attname 2 #define Anum_pg_attribute_atttypid 3 @@ -200,6 +203,7 @@ typedef FormData_pg_attribute *Form_pg_attribute; #define Anum_pg_attribute_attcollation 18 #define Anum_pg_attribute_attacl 19 #define Anum_pg_attribute_attoptions 20 +#define Anum_pg_attribute_attfdwoptions 21 /* ---------------- diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 002ae6b59c28aa7e97aff4da5d1d93c643cbb0c1..e00618026e40f233b9b67594e505f54cf01727da 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -132,7 +132,7 @@ typedef FormData_pg_class *Form_pg_class; /* 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 p r 29 0 t 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 p r 20 0 f f f f f 3 _null_ _null_ )); +DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 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 p r 26 0 t f f f f 3 _null_ _null_ )); DESCR(""); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 92e40d3fb5877ca09905bf1e9f0db8d2deedd766..a4fb3b5f7f6a0c2a54569899de0cf559d91da570 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -500,6 +500,7 @@ typedef struct ColumnDef CollateClause *collClause; /* untransformed COLLATE spec, if any */ Oid collOid; /* collation OID (InvalidOid if not set) */ List *constraints; /* other constraints on column */ + List *fdwoptions; /* per-column FDW options */ } ColumnDef; /* @@ -1197,6 +1198,7 @@ typedef enum AlterTableType AT_DropConstraint, /* drop constraint */ AT_DropConstraintRecurse, /* internal to commands/tablecmds.c */ AT_AlterColumnType, /* alter column type */ + AT_AlterColumnGenericOptions, /* alter column OPTIONS (...) */ AT_ChangeOwner, /* change owner */ AT_ClusterOn, /* CLUSTER ON */ AT_DropCluster, /* SET WITHOUT CLUSTER */ diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out index 2b3eddfc8b37b4a2ce827f2663943961f51956eb..45292b5fde414dc47f7adb1858e5b7c13040c816 100644 --- a/src/test/regress/expected/foreign_data.out +++ b/src/test/regress/expected/foreign_data.out @@ -646,19 +646,19 @@ ERROR: syntax error at or near "WITH OIDS" LINE 1: CREATE FOREIGN TABLE ft1 () SERVER sc WITH OIDS; ^ CREATE FOREIGN TABLE ft1 ( - c1 integer NOT NULL, - c2 text, + c1 integer OPTIONS (param1 'val1') NOT NULL, + c2 text OPTIONS (param2 'val2', param3 'val3'), c3 date ) SERVER sc OPTIONS (delimiter ',', quote '"'); COMMENT ON FOREIGN TABLE ft1 IS 'ft1'; COMMENT ON COLUMN ft1.c1 IS 'ft1.c1'; \d+ ft1 - Foreign table "public.ft1" - Column | Type | Modifiers | Storage | Description ---------+---------+-----------+----------+------------- - c1 | integer | not null | plain | ft1.c1 - c2 | text | | extended | - c3 | date | | plain | + Foreign table "public.ft1" + Column | Type | Modifiers | Options | Storage | Description +--------+---------+-----------+---------------------------+----------+------------- + c1 | integer | not null | {param1=val1} | plain | ft1.c1 + c2 | text | | {param2=val2,param3=val3} | extended | + c3 | date | | | plain | Server: sc Has OIDs: no @@ -687,7 +687,7 @@ 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 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 @@ -698,6 +698,27 @@ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE char(10) USING '0'; -- ERROR ERROR: "ft1" is not a table 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 ALTER COLUMN xmin OPTIONS (ADD p1 'v1'); -- ERROR +ERROR: cannot alter system column "xmin" +ALTER FOREIGN TABLE ft1 ALTER COLUMN c7 OPTIONS (ADD p1 'v1', ADD p2 'v2'), + ALTER COLUMN c8 OPTIONS (ADD p1 'v1', ADD p2 'v2'); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 OPTIONS (SET p2 'V2', DROP p1); +\d+ ft1 + Foreign table "public.ft1" + Column | Type | Modifiers | Options | Storage | Description +--------+---------+-----------+---------------------------+----------+------------- + c1 | integer | not null | {param1=val1} | plain | + c2 | text | | {param2=val2,param3=val3} | extended | + c3 | date | | | plain | + c4 | integer | | | plain | + c6 | integer | not null | | plain | + c7 | integer | | {p1=v1,p2=v2} | plain | + c8 | text | | {p2=V2} | extended | + c9 | integer | | | plain | + c10 | integer | | {p1=v1} | plain | +Server: sc +Has OIDs: no + -- can't change the column type if it's used elsewhere CREATE TABLE use_ft1_column_type (x ft1); ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer; -- ERROR @@ -726,17 +747,17 @@ ERROR: relation "ft1" does not exist 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 | - c3 | date | - c4 | integer | - c6 | integer | not null - c7 | integer | - c8 | text | - c10 | integer | + Foreign table "foreign_schema.foreign_table_1" + Column | Type | Modifiers | Options +------------------+---------+-----------+--------------------------- + foreign_column_1 | integer | not null | {param1=val1} + c2 | text | | {param2=val2,param3=val3} + c3 | date | | + c4 | integer | | + c6 | integer | not null | + c7 | integer | | {p1=v1,p2=v2} + c8 | text | | {p2=V2} + c10 | integer | | {p1=v1} Server: sc -- Information schema diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql index 58e506047744b2a39e64775e497026af607b8856..b3b49cc2e3f43e9a1dbb378af03aa5cb79b7eadc 100644 --- a/src/test/regress/sql/foreign_data.sql +++ b/src/test/regress/sql/foreign_data.sql @@ -264,8 +264,8 @@ CREATE FOREIGN TABLE ft1 () SERVER no_server; -- 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 NOT NULL, - c2 text, + c1 integer OPTIONS (param1 'val1') NOT NULL, + c2 text OPTIONS (param2 'val2', param3 'val3'), c3 date ) SERVER sc OPTIONS (delimiter ',', quote '"'); COMMENT ON FOREIGN TABLE ft1 IS 'ft1'; @@ -288,7 +288,7 @@ 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 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 @@ -297,6 +297,11 @@ 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 ALTER COLUMN xmin OPTIONS (ADD p1 'v1'); -- ERROR +ALTER FOREIGN TABLE ft1 ALTER COLUMN c7 OPTIONS (ADD p1 'v1', ADD p2 'v2'), + ALTER COLUMN c8 OPTIONS (ADD p1 'v1', ADD p2 'v2'); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 OPTIONS (SET p2 'V2', DROP p1); +\d+ ft1 -- can't change the column type if it's used elsewhere CREATE TABLE use_ft1_column_type (x ft1); ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer; -- ERROR