diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 713ee25..ae5cf6b 100644 *** a/doc/src/sgml/catalogs.sgml --- b/doc/src/sgml/catalogs.sgml *************** *** 1152,1157 **** --- 1152,1166 ---- + + attfdwoptions + text[] + + + Attribute-level generic options, as keyword=value strings + + + diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml index ab9ce2a..9bc0d3c 100644 *** a/doc/src/sgml/information_schema.sgml --- b/doc/src/sgml/information_schema.sgml *************** *** 958,963 **** --- 958,1026 ---- + + <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 a45df02..95ae02a 100644 *** a/doc/src/sgml/ref/alter_foreign_table.sgml --- b/doc/src/sgml/ref/alter_foreign_table.sgml *************** ALTER FOREIGN TABLE 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'] [, ... ]) *************** ALTER FOREIGN TABLE option ['value'] [, ... ] ) ! Change options for 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. --- 126,137 ---- 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 ! in each associated object; 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 ad91072..8863386 100644 *** a/doc/src/sgml/ref/create_foreign_table.sgml --- b/doc/src/sgml/ref/create_foreign_table.sgml *************** *** 19,25 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name ( [ ! { column_name data_type [ NULL | NOT NULL ] } [, ... ] ] ) SERVER server_name --- 19,25 ---- CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name ( [ ! { column_name data_type [ OPTIONS ( option 'value' [, ... ] ) ] [ NULL | NOT NULL ] } [, ... ] ] ) SERVER server_name *************** CREATE FOREIGN TABLE [ IF NOT EXISTS ] < *** 138,147 **** OPTIONS ( option 'value' [, ...] ) ! Options to be associated with 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's ! validator function. Option names must be unique. --- 138,149 ---- OPTIONS ( option 'value' [, ...] ) ! Options to be associated with the new foreign table or the column of ! the foreign table. 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 in each associated ! object. diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 132a7b3..74b26b2 100644 *** a/doc/src/sgml/ref/psql-ref.sgml --- b/doc/src/sgml/ref/psql-ref.sgml *************** testdb=> *** 891,896 **** --- 891,903 ---- below.) + + In addition to common information, \d shows + relation-kind-specific information for each column: + column values for sequences, indexed expression for indexes and + per-column generic 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 16979c4..4ffedab 100644 *** a/src/backend/access/common/tupdesc.c --- b/src/backend/access/common/tupdesc.c *************** equalTupleDescs(TupleDesc tupdesc1, Tupl *** 362,368 **** return false; if (attr1->attcollation != attr2->attcollation) return false; ! /* attacl and attoptions are not even present... */ } if (tupdesc1->constr != NULL) --- 362,368 ---- return false; if (attr1->attcollation != attr2->attcollation) return false; ! /* attacl, attoptions and attfdwoptions are not even present... */ } if (tupdesc1->constr != NULL) *************** TupleDescInitEntry(TupleDesc desc, *** 482,488 **** att->attisdropped = false; att->attislocal = true; att->attinhcount = 0; ! /* attacl and attoptions are not present in tupledescs */ tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(oidtypeid)); if (!HeapTupleIsValid(tuple)) --- 482,488 ---- att->attisdropped = false; att->attislocal = true; att->attinhcount = 0; ! /* 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 0aeaf5b..d91af52 100644 *** a/src/backend/catalog/genbki.pl --- b/src/backend/catalog/genbki.pl *************** sub emit_pgattr_row *** 369,375 **** attislocal => 't', attinhcount => '0', attacl => '_null_', ! attoptions => '_null_' ); return {%PGATTR_DEFAULTS, %row}; } --- 369,376 ---- attislocal => 't', attinhcount => '0', attacl => '_null_', ! attoptions => '_null_', ! attfdwoptions => '_null_' ); return {%PGATTR_DEFAULTS, %row}; } *************** sub emit_schemapg_row *** 400,405 **** --- 401,407 ---- # 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 e606ac2..dd0f4fd 100644 *** a/src/backend/catalog/heap.c --- b/src/backend/catalog/heap.c *************** static List *insert_ordered_unique_oid(L *** 126,132 **** */ /* ! * 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. */ --- 126,132 ---- */ /* ! * 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. */ *************** InsertPgAttributeTuple(Relation pg_attri *** 607,612 **** --- 607,613 ---- /* 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 9334c76..4292d88 100644 *** a/src/backend/catalog/information_schema.sql --- b/src/backend/catalog/information_schema.sql *************** GRANT SELECT ON element_types TO PUBLIC; *** 2516,2521 **** --- 2516,2554 ---- -- 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 b2ba11c..88c54d4 100644 *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** static void ATPrepAlterColumnType(List * *** 344,349 **** --- 344,350 ---- 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(char *cmd, List **wqueue, LOCKMODE lockmode); static void change_owner_recurse_to_sequences(Oid relationOid, *************** AlterTableGetLockLevel(List *cmds) *** 2607,2612 **** --- 2608,2614 ---- case AT_DropNotNull: /* may change some SQL plans */ case AT_SetNotNull: case AT_GenericOptions: + case AT_AlterColumnGenericOptions: cmd_lockmode = AccessExclusiveLock; break; *************** ATPrepCmd(List **wqueue, Relation rel, A *** 2881,2886 **** --- 2883,2894 ---- 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 */ *************** ATExecCmd(List **wqueue, AlteredTableInf *** 3114,3119 **** --- 3122,3130 ---- 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), *************** ATExecAlterColumnType(AlteredTableInfo * *** 7168,7173 **** --- 7179,7278 ---- 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 c9133dd..148110f 100644 *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** _copyColumnDef(ColumnDef *from) *** 2312,2317 **** --- 2312,2318 ---- 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 681f5f8..cb9be13 100644 *** a/src/backend/nodes/outfuncs.c --- b/src/backend/nodes/outfuncs.c *************** _outColumnDef(StringInfo str, ColumnDef *** 2101,2106 **** --- 2101,2107 ---- 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 62cff8a..da56959 100644 *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** alter_table_cmd: *** 1769,1774 **** --- 1769,1783 ---- 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 { *************** TypedTableElement: *** 2497,2503 **** | TableConstraint { $$ = $1; } ; ! columnDef: ColId Typename ColQualList { ColumnDef *n = makeNode(ColumnDef); n->colname = $1; --- 2506,2512 ---- | TableConstraint { $$ = $1; } ; ! columnDef: ColId Typename create_generic_options ColQualList { ColumnDef *n = makeNode(ColumnDef); n->colname = $1; *************** columnDef: ColId Typename ColQualList *** 2510,2516 **** n->raw_default = NULL; n->cooked_default = NULL; n->collOid = InvalidOid; ! SplitColQualList($3, &n->constraints, &n->collClause, yyscanner); $$ = (Node *)n; } --- 2519,2526 ---- n->raw_default = NULL; n->cooked_default = NULL; n->collOid = InvalidOid; ! n->fdwoptions = $3; ! SplitColQualList($4, &n->constraints, &n->collClause, yyscanner); $$ = (Node *)n; } *************** AlterFdwStmt: ALTER FOREIGN DATA_P WRAPP *** 3680,3686 **** /* Options definition for CREATE FDW, SERVER and USER MAPPING */ create_generic_options: OPTIONS '(' generic_option_list ')' { $$ = $3; } ! | /*EMPTY*/ { $$ = NIL; } ; generic_option_list: --- 3690,3696 ---- /* Options definition for CREATE FDW, SERVER and USER MAPPING */ create_generic_options: OPTIONS '(' generic_option_list ')' { $$ = $3; } ! | /*EMPTY*/ { $$ = NIL } ; generic_option_list: diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 8744654..8f1f615 100644 *** a/src/backend/parser/parse_utilcmd.c --- b/src/backend/parser/parse_utilcmd.c *************** transformColumnDefinition(CreateStmtCont *** 555,560 **** --- 555,591 ---- break; } } + + /* + * Generate ALTER FOREIGN TABLE ALTER COLUMN statement which adds + * per-column generic 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); + + foreach (clist, column->fdwoptions) + { + DefElem *option = (DefElem *) lfirst(clist); + elog(DEBUG3, "%s=%s", option->defname, strVal(option->arg)); + } + } } /* diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 9e69b0f..258e153 100644 *** a/src/bin/pg_dump/pg_dump.c --- b/src/bin/pg_dump/pg_dump.c *************** getTableAttrs(TableInfo *tblinfo, int nu *** 5556,5561 **** --- 5556,5562 ---- int i_attislocal; int i_attoptions; int i_attcollation; + int i_attfdwoptions; PGresult *res; int ntups; bool hasdefaults; *************** getTableAttrs(TableInfo *tblinfo, int nu *** 5593,5599 **** resetPQExpBuffer(q); ! if (g_fout->remoteVersion >= 90100) { /* * attcollation is new in 9.1. Since we only want to dump COLLATE --- 5594,5624 ---- resetPQExpBuffer(q); ! 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 *************** getTableAttrs(TableInfo *tblinfo, int nu *** 5608,5614 **** "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 " "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 " --- 5633,5640 ---- "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, " ! "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 " *************** getTableAttrs(TableInfo *tblinfo, int nu *** 5616,5621 **** --- 5642,5648 ---- "ORDER BY a.attrelid, a.attnum", tbinfo->dobj.catId.oid); } + else if (g_fout->remoteVersion >= 90000) { /* attoptions is new in 9.0 */ *************** getTableAttrs(TableInfo *tblinfo, int nu *** 5625,5631 **** "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 " "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 " --- 5652,5659 ---- "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, " ! "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 " *************** getTableAttrs(TableInfo *tblinfo, int nu *** 5641,5647 **** "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 " "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 " --- 5669,5676 ---- "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, " ! "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 " *************** getTableAttrs(TableInfo *tblinfo, int nu *** 5662,5668 **** "false AS attisdropped, a.attlen, " "a.attalign, false AS attislocal, " "format_type(t.oid,a.atttypmod) AS atttypname, " ! "'' AS attoptions, 0 AS attcollation " "FROM pg_attribute a LEFT JOIN pg_type t " "ON a.atttypid = t.oid " "WHERE a.attrelid = '%u'::oid " --- 5691,5698 ---- "false AS attisdropped, a.attlen, " "a.attalign, false AS attislocal, " "format_type(t.oid,a.atttypmod) AS atttypname, " ! "'' 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 " *************** getTableAttrs(TableInfo *tblinfo, int nu *** 5680,5686 **** "attlen, attalign, " "false AS attislocal, " "(SELECT typname FROM pg_type WHERE oid = atttypid) AS atttypname, " ! "'' AS attoptions, 0 AS attcollation " "FROM pg_attribute a " "WHERE attrelid = '%u'::oid " "AND attnum > 0::int2 " --- 5710,5717 ---- "attlen, attalign, " "false AS attislocal, " "(SELECT typname FROM pg_type WHERE oid = atttypid) AS atttypname, " ! "'' AS attoptions, 0 AS attcollation, " ! "NULL AS attfdwoptions " "FROM pg_attribute a " "WHERE attrelid = '%u'::oid " "AND attnum > 0::int2 " *************** getTableAttrs(TableInfo *tblinfo, int nu *** 5708,5713 **** --- 5739,5745 ---- 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 *)); *************** getTableAttrs(TableInfo *tblinfo, int nu *** 5724,5729 **** --- 5756,5762 ---- 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)); *************** getTableAttrs(TableInfo *tblinfo, int nu *** 5750,5755 **** --- 5783,5789 ---- 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; *************** dumpTableSchema(Archive *fout, TableInfo *** 12451,12456 **** --- 12485,12505 ---- appendPQExpBuffer(q, "SET (%s);\n", tbinfo->attoptions[j]); } + + /* + * Dump per-column generic 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 c95614b..0a65401 100644 *** a/src/bin/pg_dump/pg_dump.h --- b/src/bin/pg_dump/pg_dump.h *************** typedef struct _tableInfo *** 275,280 **** --- 275,281 ---- bool *attislocal; /* true if attr has local definition */ char **attoptions; /* per-attribute options */ Oid *attcollation; /* per-attribute collation selection */ + char **attfdwoptions; /* per-attribute generic 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 b2c54b5..381150c 100644 *** a/src/bin/psql/describe.c --- b/src/bin/psql/describe.c *************** describeOneTableDetails(const char *sche *** 1281,1287 **** res = NULL; } ! /* Get column info */ 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)" --- 1281,1292 ---- res = NULL; } ! /* ! * 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)" *************** describeOneTableDetails(const char *sche *** 1295,1300 **** --- 1300,1311 ---- 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, pg_catalog.col_description(a.attrelid, a.attnum)"); appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_attribute a"); *************** describeOneTableDetails(const char *sche *** 1376,1381 **** --- 1387,1395 ---- 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"); *************** describeOneTableDetails(const char *sche *** 1458,1467 **** if (tableinfo.relkind == 'i') printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false); /* Storage and Description */ if (verbose) { ! int firstvcol = (tableinfo.relkind == 'i' ? 7 : 6); char *storage = PQgetvalue(res, i, firstvcol); /* these strings are literal in our syntax, so not translated. */ --- 1472,1485 ---- 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 = 8; char *storage = PQgetvalue(res, i, firstvcol); /* these strings are literal in our syntax, so not translated. */ diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h index fb86d1e..b6bc472 100644 *** a/src/bin/psql/describe.h --- b/src/bin/psql/describe.h *************** extern bool listUserMappings(const char *** 87,92 **** --- 87,95 ---- /* \det */ extern bool listForeignTables(const char *pattern, bool verbose); + /* \dec */ + extern bool listForeignTableColumns(const char *pattern, bool verbose); + /* \dL */ extern bool listLanguages(const char *pattern, bool verbose, bool showSystem); diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index 409d6ea..3ea87e8 100644 *** a/src/include/catalog/pg_attribute.h --- b/src/include/catalog/pg_attribute.h *************** CATALOG(pg_attribute,1249) BKI_BOOTSTRAP *** 156,161 **** --- 156,164 ---- /* Column-level options */ text attoptions[1]; + + /* Column-level FDW options */ + text attfdwoptions[1]; } FormData_pg_attribute; /* *************** typedef FormData_pg_attribute *Form_pg_a *** 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 --- 182,188 ---- * ---------------- */ ! #define Natts_pg_attribute 21 #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 *** 200,205 **** --- 203,209 ---- #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 002ae6b..e006180 100644 *** a/src/include/catalog/pg_class.h --- b/src/include/catalog/pg_class.h *************** typedef FormData_pg_class *Form_pg_class *** 132,138 **** /* 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_ )); 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(""); --- 132,138 ---- /* 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 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 c65e3cd..19dff9b 100644 *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** typedef struct ColumnDef *** 500,505 **** --- 500,506 ---- 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; /* *************** typedef enum AlterTableType *** 1196,1201 **** --- 1197,1203 ---- 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 def850b..a298a9c 100644 *** a/src/test/regress/expected/foreign_data.out --- b/src/test/regress/expected/foreign_data.out *************** ERROR: syntax error at or near "WITH OI *** 646,664 **** LINE 1: CREATE FOREIGN TABLE ft1 () SERVER sc WITH OIDS; ^ CREATE FOREIGN TABLE ft1 ( ! c1 integer NOT NULL, ! c2 text, 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 | Server: sc Has OIDs: no --- 646,664 ---- LINE 1: CREATE FOREIGN TABLE ft1 () SERVER sc WITH OIDS; ^ CREATE FOREIGN TABLE ft1 ( ! 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 | 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 *************** ALTER FOREIGN TABLE ft1 ADD COLUMN c6 in *** 687,693 **** 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 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 --- 687,693 ---- 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 *************** ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 *** 698,703 **** --- 698,724 ---- ERROR: ALTER TYPE USING is only supported on plain tables 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 *************** ERROR: relation "ft1" does not exist *** 726,742 **** 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 | Server: sc -- Information schema --- 747,763 ---- 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 | 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 d323921..5d9b24a 100644 *** a/src/test/regress/sql/foreign_data.sql --- b/src/test/regress/sql/foreign_data.sql *************** CREATE FOREIGN TABLE ft1 () SERVER no_se *** 264,271 **** 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, c3 date ) SERVER sc OPTIONS (delimiter ',', quote '"'); COMMENT ON FOREIGN TABLE ft1 IS 'ft1'; --- 264,271 ---- 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 (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'; *************** ALTER FOREIGN TABLE ft1 ADD COLUMN c6 in *** 288,294 **** 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 ALTER COLUMN c4 SET DEFAULT 0; -- ERROR ALTER FOREIGN TABLE ft1 ALTER COLUMN c5 DROP DEFAULT; -- ERROR --- 288,294 ---- 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 c7 *** 297,302 **** --- 297,307 ---- 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