#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;
{"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}
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);
/*
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;
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)
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);
}
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,
}
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),
</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>