Allow per-column foreign data wrapper options.
authorRobert Haas <rhaas@postgresql.org>
Fri, 5 Aug 2011 17:24:03 +0000 (13:24 -0400)
committerRobert Haas <rhaas@postgresql.org>
Fri, 5 Aug 2011 17:24:03 +0000 (13:24 -0400)
Shigeru Hanada, with fairly minor editing by me.

23 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/information_schema.sgml
doc/src/sgml/ref/alter_foreign_table.sgml
doc/src/sgml/ref/create_foreign_table.sgml
doc/src/sgml/ref/psql-ref.sgml
src/backend/access/common/tupdesc.c
src/backend/catalog/genbki.pl
src/backend/catalog/heap.c
src/backend/catalog/information_schema.sql
src/backend/commands/tablecmds.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/outfuncs.c
src/backend/parser/gram.y
src/backend/parser/parse_utilcmd.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/bin/psql/describe.c
src/include/catalog/catversion.h
src/include/catalog/pg_attribute.h
src/include/catalog/pg_class.h
src/include/nodes/parsenodes.h
src/test/regress/expected/foreign_data.out
src/test/regress/sql/foreign_data.sql

index 5e5f8a7554755d6948ab1b3276cf02b5daffe615..6d5dad369d7a5ffbfa26fea3f0c3df64b631e101 100644 (file)
       </entry>
      </row>
 
+     <row>
+      <entry><structfield>attfdwoptions</structfield></entry>
+      <entry><type>text[]</type></entry>
+      <entry></entry>
+      <entry>
+       Attribute-level foreign data wrapper options, as <quote>keyword=value</> strings
+      </entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
index ed4f1572a0a8a4be80715539ed9306377d80bead..0f0cbfaa83a5007426ac42e83654d2ee5e0a0bd1 100644 (file)
   </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>
 
index a45df020ea143f099719de827410190a5176564d..a422c88a4f6d3785861a76d8918df026fc996c6d 100644 (file)
@@ -36,6 +36,7 @@ ALTER FOREIGN TABLE <replaceable class="PARAMETER">name</replaceable>
     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>
@@ -125,12 +126,13 @@ ALTER FOREIGN TABLE <replaceable class="PARAMETER">name</replaceable>
     <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.
+      Change options for the foreign table or one of its columns.
       <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.
+      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.
      </para>
     </listitem>
    </varlistentry>
index ad91072bd125ccda4f70e6f35f450281ed00ba35..5e58fb8c5eb6ab3999883d43838d567502bfeefd 100644 (file)
@@ -19,7 +19,7 @@
  <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 ] }
+  { <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>
@@ -138,10 +138,12 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
     <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.
+      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).
      </para>
     </listitem>
    </varlistentry>
index ecfafadc61575fd0cb4cdf472104f7b454de6aa5..98cb6bd24fbd2e8baae0f34f31980a6ec5706415 100644 (file)
@@ -891,6 +891,12 @@ testdb=&gt;
         below.)
         </para>
 
+        <para>
+        For some types of relation, <literal>\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.
+        </para>
+
         <para>
         The command form <literal>\d+</literal> is identical, except that
         more information is displayed: any comments associated with the
index 4656dba642cdd0fec56ea73da6d8357d6ab848bf..9e931df4b141a72ab68d064404e6d8e5bbc9ca26 100644 (file)
@@ -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))
index 0aeaf5bfd76c7e34f01f96fb35bbd1d3f2c4bf40..d91af529f43d5c23d05c986b745bee80a971db3a 100644 (file)
@@ -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.
index 439949362570d65d63ccc5e04b2b8c5565bb313b..7ec658146f093ccce94356f9eef9fefafbc303d8 100644 (file)
@@ -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);
 
index 80cd091ec875f3cd035bb72f5d4140d31384181d..47c48bfb275f93e6ee73ad24fa6078dae01c279f 100644 (file)
@@ -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,
index 82bb756c75e0f422247ff67f28fcc12c3deca983..4509cdab90045b37a136c15b20eccc759ea6c6b4 100644 (file)
@@ -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
index 7a5145621f3b09fd72b218d77adefea32f250220..d0704ed0718040390f2a0d37447519a6e5b3b333 100644 (file)
@@ -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;
 }
index b5be09af1a0ced4c761b62366e78387019ceb381..417aeb882212d78576149b3b38a1ef8bee490e13 100644 (file)
@@ -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
index ac094aa5f3af93383c103d66246f45e5e9fdef73..e9f3896badb55ef56b46aee270b94d5487412d40 100644 (file)
@@ -1769,6 +1769,15 @@ alter_table_cmd:
                    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
                {
@@ -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;
                }
index 1be2ac68a587ce8610fde07cfa7bc45c5a0d5be0..21b54f7f153383827befd994e1ba3168ed9f9ae8 100644 (file)
@@ -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);
+   }
 }
 
 /*
index f2ee57cabd373913fe9e5083275eab808087779d..cf0fc4b5d3d32af2907aca6ce11ee1408f4c045d 100644 (file)
@@ -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]);
+           }
        }
    }
 
index c95614b16fa6e1499177bf44f0d5300a2dbe4cb8..3d5d534269f7f1e5828cf4c1824142c8f46319b8 100644 (file)
@@ -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
index e46568654e534fbdf46474d4144e4ec70d255abf..a8d5ddc96c22d05f2536dd1e75a2e36c9ac8e6cb 100644 (file)
@@ -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. */
index 2fadf30792f0e5b1a13c564195a64e668c4b165d..f5c9797c60856d27cf9cb834545ccca4466c0c4b 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 201107201
+#define CATALOG_VERSION_NO 201108051
 
 #endif
index 409d6ea3e7e1781d69fbeafc8d427675a9beb390..3ea87e8229ea757fdc6248e42eb3a164b32ed8ff 100644 (file)
@@ -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
 
 
 /* ----------------
index 002ae6b59c28aa7e97aff4da5d1d93c643cbb0c1..e00618026e40f233b9b67594e505f54cf01727da 100644 (file)
@@ -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("");
index 92e40d3fb5877ca09905bf1e9f0db8d2deedd766..a4fb3b5f7f6a0c2a54569899de0cf559d91da570 100644 (file)
@@ -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 */
index 2b3eddfc8b37b4a2ce827f2663943961f51956eb..45292b5fde414dc47f7adb1858e5b7c13040c816 100644 (file)
@@ -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
index 58e506047744b2a39e64775e497026af607b8856..b3b49cc2e3f43e9a1dbb378af03aa5cb79b7eadc 100644 (file)
@@ -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