Add force_not_null option support to file_fdw.
authorShigeru Hanada <hanada@metrosystems.co.jp>
Tue, 14 Jun 2011 07:23:21 +0000 (16:23 +0900)
committerShigeru Hanada <shigeru.hanada@gmail.com>
Wed, 29 Jun 2011 09:46:19 +0000 (18:46 +0900)
contrib/file_fdw/file_fdw.c
doc/src/sgml/file-fdw.sgml

index 466c015107db28b46601b759ad4542d6589a597c..544d1e65cb0003aea856315b336a5ad096df3238 100644 (file)
@@ -23,7 +23,9 @@
 #include "foreign/fdwapi.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/cost.h"
+#include "utils/syscache.h"
 
 PG_MODULE_MAGIC;
 
@@ -56,16 +58,12 @@ static struct FileFdwOption valid_options[] = {
    {"escape", ForeignTableRelationId},
    {"null", ForeignTableRelationId},
    {"encoding", ForeignTableRelationId},
+   {"force_not_null", AttributeRelationId},    /* specified as boolean value */
 
    /*
     * force_quote is not supported by file_fdw because it's for COPY TO.
     */
 
-   /*
-    * force_not_null is not supported by file_fdw.  It would need a parser
-    * for list of columns, not to mention a way to check the column list
-    * against the table.
-    */
 
    /* Sentinel */
    {NULL, InvalidOid}
@@ -111,6 +109,7 @@ static void fileGetOptions(Oid foreigntableid,
 static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
               const char *filename,
               Cost *startup_cost, Cost *total_cost);
+static List * get_force_not_null(Oid relid);
 
 
 /*
@@ -144,6 +143,7 @@ file_fdw_validator(PG_FUNCTION_ARGS)
    List       *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
    Oid         catalog = PG_GETARG_OID(1);
    char       *filename = NULL;
+   char       *force_not_null = NULL;
    List       *other_options = NIL;
    ListCell   *cell;
 
@@ -197,7 +197,10 @@ file_fdw_validator(PG_FUNCTION_ARGS)
                             buf.data)));
        }
 
-       /* Separate out filename, since ProcessCopyOptions won't allow it */
+       /*
+        * Separate out filename and force_not_null, since ProcessCopyOptions
+        * won't allow it.
+        */
        if (strcmp(def->defname, "filename") == 0)
        {
            if (filename)
@@ -206,6 +209,20 @@ file_fdw_validator(PG_FUNCTION_ARGS)
                         errmsg("conflicting or redundant options")));
            filename = defGetString(def);
        }
+       else if (strcmp(def->defname, "force_not_null") == 0)
+       {
+           if (force_not_null)
+               ereport(ERROR,
+                       (errcode(ERRCODE_SYNTAX_ERROR),
+                        errmsg("conflicting or redundant options")));
+
+           force_not_null = defGetString(def);
+           if (strcmp(force_not_null, "true") != 0 &&
+               strcmp(force_not_null, "false") != 0)
+               ereport(ERROR,
+                       (errcode(ERRCODE_SYNTAX_ERROR),
+                        errmsg("force_not_null must be true or false")));
+       }
        else
            other_options = lappend(other_options, def);
    }
@@ -235,11 +252,96 @@ is_valid_option(const char *option, Oid context)
    return false;
 }
 
+/*
+ * Retrieve per-column generic options from pg_attribute and construct a list
+ * of column names for "force_not_null".
+ */
+static List *
+get_force_not_null(Oid relid)
+{
+   Relation    rel;
+   TupleDesc   tupleDesc;
+   AttrNumber  natts;
+   AttrNumber  attnum;
+   List       *columns = NIL;
+
+   rel = heap_open(relid, AccessShareLock);
+   tupleDesc = RelationGetDescr(rel);
+   natts = tupleDesc->natts;
+
+   /* Retrieve FDW options from every user-defined attributes. */
+   for (attnum = 1; attnum < natts; attnum++)
+   {
+       HeapTuple   tuple;
+       Form_pg_attribute   attr;
+       Datum       datum;
+       bool        isnull;
+       List       *options;
+       ListCell   *cell;
+
+
+       tuple = SearchSysCache2(ATTNUM,
+                               RelationGetRelid(rel),
+                               Int16GetDatum(attnum));
+       if (!HeapTupleIsValid(tuple))
+           ereport(ERROR,
+                   (errcode(ERRCODE_UNDEFINED_OBJECT),
+                    errmsg("cache lookup failed for attribute %d of relation %u",
+                           attnum, RelationGetRelid(rel))));
+       attr = (Form_pg_attribute) GETSTRUCT(tuple);
+
+       /* Skip dropped attributes. */
+       if (attr->attisdropped)
+       {
+           ReleaseSysCache(tuple);
+           continue;
+       }
+
+       datum = SysCacheGetAttr(ATTNUM,
+                               tuple,
+                               Anum_pg_attribute_attfdwoptions,
+                               &isnull);
+       if (isnull)
+           datum = PointerGetDatum(NULL);
+       options = untransformRelOptions(datum);
+
+       /*
+        * Find force_not_null option and append attname to the list if
+        * the value was true.
+        */
+       foreach (cell, options)
+       {
+           DefElem    *def = (DefElem *) lfirst(cell);
+           const char *value = defGetString(def);
+
+           if (strcmp(def->defname, "force_not_null") == 0 &&
+               strcmp(value, "true") == 0)
+           {
+               columns = lappend(columns, makeString(NameStr(attr->attname)));
+               elog(DEBUG1, "%s: force_not_null", NameStr(attr->attname));
+           }
+
+       }
+
+       ReleaseSysCache(tuple);
+   }
+
+   heap_close(rel, AccessShareLock);
+
+   /* Return DefElemn only when any column is set "force_not_null=true". */
+   if (columns != NIL)
+       return list_make1(makeDefElem("force_not_null", (Node *) columns));
+   else
+       return NIL;
+}
+
 /*
  * Fetch the options for a file_fdw foreign table.
  *
  * We have to separate out "filename" from the other options because
  * it must not appear in the options list passed to the core COPY code.
+ * And we must construct List of DefElem from pg_attribute.attfdwoptions for
+ * "force_not_null".
  */
 static void
 fileGetOptions(Oid foreigntableid,
@@ -286,6 +388,11 @@ fileGetOptions(Oid foreigntableid,
        }
        prev = lc;
    }
+
+   /* Retrieve force_not_null from pg_attribute and append it to the list. */
+   options = list_concat(options, get_force_not_null(foreigntableid));
+
+   /* The filename is required optiono. */
    if (*filename == NULL)
        ereport(ERROR,
                (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_REPLY),
index 8497d9a45f52d0e2b0498bcff6a18db5864275b4..107476fa4cb65b91a8cf54f930882fc73a578f67 100644 (file)
  </variablelist>
 
  <para>
-  <command>COPY</>'s <literal>OIDS</literal>, <literal>FORCE_QUOTE</literal>,
-  and <literal>FORCE_NOT_NULL</literal> options are currently not supported by
+  A column of a foreign table created using this wrapper can have the
+  following options:
+ </para>
+
+ <variablelist>
+
+  <varlistentry>
+   <term><literal>force_not_null</literal></term>
+
+   <listitem>
+    <para>
+     Specifies that the column shouldn't been matched against the null string.
+     Acceptable values are <literal>true</> and <literal>falset</>
+     (case sensitive).
+     the same as <command>COPY</>'s <literal>FORCE_NOT_NULL</literal> option.
+    </para>
+   </listitem>
+  </varlistentry>
+
+ </variablelist>
+
+ <para>
+  <command>COPY</>'s <literal>OIDS</literal> and
+  <literal>FORCE_QUOTE</literal> options are currently not supported by
   <literal>file_fdw</>.
  </para>
 
  <para>
-  These options can only be specified for a foreign table, not in the
-  options of the <literal>file_fdw</> foreign-data wrapper, nor in the
+  These options can only be specified for a foreign table or its columns, not
+  in the options of the <literal>file_fdw</> foreign-data wrapper, nor in the
   options of a server or user mapping using the wrapper.
  </para>
 
   Changing table-level options requires superuser privileges, for security
   reasons: only a superuser should be able to determine which file is read.
   In principle non-superusers could be allowed to change the other options,
-  but that's not supported at present.
+  but that's only force_not_null supported at present.
  </para>
 
  <para>