per_column_option_v5.patch

text/plain

Filename: per_column_option_v5.patch
Type: text/plain
Part: 0
Message: per-column FDW options, v5
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 5e5f8a7..7c49cd0 100644
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 1157,1162 ****
--- 1157,1171 ----
        </entry>
       </row>
  
+      <row>
+       <entry><structfield>attfdwoptions</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry></entry>
+       <entry>
+        Attribute-level generic options, as <quote>keyword=value</> strings
+       </entry>
+      </row>
+ 
      </tbody>
     </tgroup>
    </table>
diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml
index ed4f157..0f0cbfa 100644
*** a/doc/src/sgml/information_schema.sgml
--- b/doc/src/sgml/information_schema.sgml
***************
*** 1018,1023 ****
--- 1018,1086 ----
    </table>
   </sect1>
  
+  <sect1 id="infoschema-column-options">
+   <title><literal>column_options</literal></title>
+ 
+   <para>
+    The view <literal>column_options</literal> 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).
+   </para>
+ 
+   <table>
+    <title><literal>column_options</literal> Columns</title>
+ 
+    <tgroup cols="3">
+     <thead>
+      <row>
+       <entry>Name</entry>
+       <entry>Data Type</entry>
+       <entry>Description</entry>
+      </row>
+     </thead>
+ 
+     <tbody>
+      <row>
+       <entry><literal>table_catalog</literal></entry>
+       <entry><type>sql_identifier</type></entry>
+       <entry>Name of the database that contains the foreign table (always the current database)</entry>
+      </row>
+ 
+      <row>
+       <entry><literal>table_schema</literal></entry>
+       <entry><type>sql_identifier</type></entry>
+       <entry>Name of the schema that contains the foreign table</entry>
+      </row>
+ 
+      <row>
+       <entry><literal>table_name</literal></entry>
+       <entry><type>sql_identifier</type></entry>
+       <entry>Name of the foreign table</entry>
+      </row>
+ 
+      <row>
+       <entry><literal>column_name</literal></entry>
+       <entry><type>sql_identifier</type></entry>
+       <entry>Name of the column</entry>
+      </row>
+ 
+      <row>
+       <entry><literal>option_name</literal></entry>
+       <entry><type>sql_identifier</type></entry>
+       <entry>Name of an option</entry>
+      </row>
+ 
+      <row>
+       <entry><literal>option_value</literal></entry>
+       <entry><type>character_data</type></entry>
+       <entry>Value of the option</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+  </sect1>
+ 
   <sect1 id="infoschema-column-privileges">
    <title><literal>column_privileges</literal></title>
  
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 <replaceable class="
*** 36,41 ****
--- 36,42 ----
      DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="PARAMETER">column</replaceable> [ RESTRICT | CASCADE ]
      ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> [ SET DATA ] TYPE <replaceable class="PARAMETER">type</replaceable>
      ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> { SET | DROP } NOT NULL
+     ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
      OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
      OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
  </synopsis>
*************** ALTER FOREIGN TABLE <replaceable class="
*** 125,136 ****
      <term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
      <listitem>
       <para>
!       Change options for the foreign table.
        <literal>ADD</>, <literal>SET</>, and <literal>DROP</>
        specify the action to be performed.  <literal>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.
       </para>
      </listitem>
     </varlistentry>
--- 126,137 ----
      <term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
      <listitem>
       <para>
!       Change options for the foreign table or the column of the foreign table.
        <literal>ADD</>, <literal>SET</>, and <literal>DROP</>
        specify the action to be performed.  <literal>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.
       </para>
      </listitem>
     </varlistentry>
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 ****
   <refsynopsisdiv>
  <synopsis>
  CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
!   { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ NULL | NOT NULL ] }
      [, ... ]
  ] )
    SERVER <replaceable class="parameter">server_name</replaceable>
--- 19,25 ----
   <refsynopsisdiv>
  <synopsis>
  CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
!   { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ NULL | NOT NULL ] }
      [, ... ]
  ] )
    SERVER <replaceable class="parameter">server_name</replaceable>
*************** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <
*** 138,147 ****
      <term><literal>OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ...] )</literal></term>
      <listitem>
       <para>
!       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.
       </para>
      </listitem>
     </varlistentry>
--- 138,149 ----
      <term><literal>OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ...] )</literal></term>
      <listitem>
       <para>
!       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.
       </para>
      </listitem>
     </varlistentry>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 54fc3ee..9e310a1 100644
*** a/doc/src/sgml/ref/psql-ref.sgml
--- b/doc/src/sgml/ref/psql-ref.sgml
*************** testdb=&gt;
*** 891,896 ****
--- 891,903 ----
          below.)
          </para>
  
+ 		<para>
+         In addition to common information, <literal>\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.
+ 		</para>
+ 
          <para>
          The command form <literal>\d+</literal> 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 4656dba..9e931df 100644
*** a/src/backend/access/common/tupdesc.c
--- b/src/backend/access/common/tupdesc.c
*************** equalTupleDescs(TupleDesc tupdesc1, Tupl
*** 363,369 ****
  			return false;
  		if (attr1->attcollation != attr2->attcollation)
  			return false;
! 		/* attacl and attoptions are not even present... */
  	}
  
  	if (tupdesc1->constr != NULL)
--- 363,369 ----
  			return false;
  		if (attr1->attcollation != attr2->attcollation)
  			return false;
! 		/* attacl, attoptions and attfdwoptions are not even present... */
  	}
  
  	if (tupdesc1->constr != NULL)
*************** TupleDescInitEntry(TupleDesc desc,
*** 483,489 ****
  	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))
--- 483,489 ----
  	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 4399493..7ec6581 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
*** 620,625 ****
--- 620,626 ----
  	/* 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 80cd091..47c48bf 100644
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
*************** GRANT SELECT ON element_types TO PUBLIC;
*** 2534,2539 ****
--- 2534,2572 ----
  
  -- 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 82bb756..7d8e613 100644
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** static void ATPrepAlterColumnType(List *
*** 346,351 ****
--- 346,352 ----
  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);
*************** AlterTableGetLockLevel(List *cmds)
*** 2648,2653 ****
--- 2649,2655 ----
  			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
*** 2925,2930 ****
--- 2927,2938 ----
  			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
*** 3169,3174 ****
--- 3177,3185 ----
  		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 *
*** 7397,7402 ****
--- 7408,7507 ----
  	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 7a51456..d0704ed 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 b5be09a..417aeb8 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outColumnDef(StringInfo str, ColumnDef 
*** 2102,2107 ****
--- 2102,2108 ----
  	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 ac094aa..e9f3896 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 <name> ALTER [COLUMN] <colname> 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 <name> 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;
  				}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1be2ac6..782f5bc 100644
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
*************** transformColumnDefinition(CreateStmtCont
*** 559,564 ****
--- 559,589 ----
  				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);
+ 	}
  }
  
  /*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f2ee57c..61b3492 100644
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** getTableAttrs(TableInfo *tblinfo, int nu
*** 5574,5579 ****
--- 5574,5580 ----
  	int			i_attislocal;
  	int			i_attoptions;
  	int			i_attcollation;
+ 	int			i_attfdwoptions;
  	PGresult   *res;
  	int			ntups;
  	bool		hasdefaults;
*************** getTableAttrs(TableInfo *tblinfo, int nu
*** 5611,5617 ****
  
  		resetPQExpBuffer(q);
  
! 		if (g_fout->remoteVersion >= 90100)
  		{
  			/*
  			 * attcollation is new in 9.1.	Since we only want to dump COLLATE
--- 5612,5642 ----
  
  		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
*** 5626,5632 ****
  				  "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 "
--- 5651,5658 ----
  				  "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
*** 5634,5639 ****
--- 5660,5666 ----
  							  "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
*** 5643,5649 ****
  							  "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 "
--- 5670,5677 ----
  							  "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
*** 5659,5665 ****
  							  "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 "
--- 5687,5694 ----
  							  "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
*** 5680,5686 ****
  							  "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 "
--- 5709,5716 ----
  							  "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
*** 5698,5704 ****
  							  "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 "
--- 5728,5735 ----
  							  "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
*** 5726,5731 ****
--- 5757,5763 ----
  		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
*** 5742,5747 ****
--- 5774,5780 ----
  		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
*** 5768,5773 ****
--- 5801,5807 ----
  			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
*** 12469,12474 ****
--- 12503,12523 ----
  				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 b50c5d6..5d7dfcf 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");
*************** describeOneTableDetails(const char *sche
*** 1386,1391 ****
--- 1397,1405 ----
  	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
*** 1471,1480 ****
  		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. */
--- 1485,1498 ----
  		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/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 92e40d3..a4fb3b5 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
*** 1197,1202 ****
--- 1198,1204 ----
  	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 2b3eddf..45292b5 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:  "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
*************** 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 58e5060..b3b49cc 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