2323#include "foreign/fdwapi.h"
2424#include "foreign/foreign.h"
2525#include "miscadmin.h"
26+ #include "nodes/makefuncs.h"
2627#include "optimizer/cost.h"
2728#include "utils/rel.h"
29+ #include "utils/syscache.h"
2830
2931PG_MODULE_MAGIC ;
3032
@@ -40,6 +42,8 @@ struct FileFdwOption
4042/*
4143 * Valid options for file_fdw.
4244 * These options are based on the options for COPY FROM command.
45+ * But note that force_not_null is handled as a boolean option attached to
46+ * each column, not as a table option.
4347 *
4448 * Note: If you are adding new option for user mapping, you need to modify
4549 * fileGetOptions(), which currently doesn't bother to look at user mappings.
@@ -57,17 +61,12 @@ static struct FileFdwOption valid_options[] = {
5761{"escape" ,ForeignTableRelationId },
5862{"null" ,ForeignTableRelationId },
5963{"encoding" ,ForeignTableRelationId },
64+ {"force_not_null" ,AttributeRelationId },
6065
6166/*
6267 * force_quote is not supported by file_fdw because it's for COPY TO.
6368 */
6469
65- /*
66- * force_not_null is not supported by file_fdw. It would need a parser
67- * for list of columns, not to mention a way to check the column list
68- * against the table.
69- */
70-
7170/* Sentinel */
7271{NULL ,InvalidOid }
7372};
@@ -109,6 +108,7 @@ static void fileEndForeignScan(ForeignScanState *node);
109108static bool is_valid_option (const char * option ,Oid context );
110109static void fileGetOptions (Oid foreigntableid ,
111110char * * filename ,List * * other_options );
111+ static List * get_file_fdw_attribute_options (Oid relid );
112112static void estimate_costs (PlannerInfo * root ,RelOptInfo * baserel ,
113113const char * filename ,
114114Cost * startup_cost ,Cost * total_cost );
@@ -145,6 +145,7 @@ file_fdw_validator(PG_FUNCTION_ARGS)
145145List * options_list = untransformRelOptions (PG_GETARG_DATUM (0 ));
146146Oid catalog = PG_GETARG_OID (1 );
147147char * filename = NULL ;
148+ DefElem * force_not_null = NULL ;
148149List * other_options = NIL ;
149150ListCell * cell ;
150151
@@ -198,7 +199,11 @@ file_fdw_validator(PG_FUNCTION_ARGS)
198199buf .data )));
199200}
200201
201- /* Separate out filename, since ProcessCopyOptions won't allow it */
202+ /*
203+ * Separate out filename and force_not_null, since ProcessCopyOptions
204+ * won't accept them. (force_not_null only comes in a boolean
205+ * per-column flavor here.)
206+ */
202207if (strcmp (def -> defname ,"filename" )== 0 )
203208{
204209if (filename )
@@ -207,6 +212,16 @@ file_fdw_validator(PG_FUNCTION_ARGS)
207212errmsg ("conflicting or redundant options" )));
208213filename = defGetString (def );
209214}
215+ else if (strcmp (def -> defname ,"force_not_null" )== 0 )
216+ {
217+ if (force_not_null )
218+ ereport (ERROR ,
219+ (errcode (ERRCODE_SYNTAX_ERROR ),
220+ errmsg ("conflicting or redundant options" )));
221+ force_not_null = def ;
222+ /* Don't care what the value is, as long as it's a legal boolean */
223+ (void )defGetBoolean (def );
224+ }
210225else
211226other_options = lappend (other_options ,def );
212227}
@@ -277,6 +292,7 @@ fileGetOptions(Oid foreigntableid,
277292options = list_concat (options ,wrapper -> options );
278293options = list_concat (options ,server -> options );
279294options = list_concat (options ,table -> options );
295+ options = list_concat (options ,get_file_fdw_attribute_options (foreigntableid ));
280296
281297/*
282298 * Separate out the filename.
@@ -306,6 +322,88 @@ fileGetOptions(Oid foreigntableid,
306322* other_options = options ;
307323}
308324
325+ /*
326+ * Retrieve per-column generic options from pg_attribute and construct a list
327+ * of DefElems representing them.
328+ *
329+ * At the moment we only have "force_not_null", which should be combined into
330+ * a single DefElem listing all such columns, since that's what COPY expects.
331+ */
332+ static List *
333+ get_file_fdw_attribute_options (Oid relid )
334+ {
335+ Relation rel ;
336+ TupleDesc tupleDesc ;
337+ AttrNumber natts ;
338+ AttrNumber attnum ;
339+ List * fnncolumns = NIL ;
340+
341+ rel = heap_open (relid ,AccessShareLock );
342+ tupleDesc = RelationGetDescr (rel );
343+ natts = tupleDesc -> natts ;
344+
345+ /* Retrieve FDW options for all user-defined attributes. */
346+ for (attnum = 1 ;attnum <=natts ;attnum ++ )
347+ {
348+ HeapTuple tuple ;
349+ Form_pg_attribute attr ;
350+ Datum datum ;
351+ bool isnull ;
352+
353+ /* Skip dropped attributes. */
354+ if (tupleDesc -> attrs [attnum - 1 ]-> attisdropped )
355+ continue ;
356+
357+ /*
358+ * We need the whole pg_attribute tuple not just what is in the
359+ * tupleDesc, so must do a catalog lookup.
360+ */
361+ tuple = SearchSysCache2 (ATTNUM ,
362+ RelationGetRelid (rel ),
363+ Int16GetDatum (attnum ));
364+ if (!HeapTupleIsValid (tuple ))
365+ elog (ERROR ,"cache lookup failed for attribute %d of relation %u" ,
366+ attnum ,RelationGetRelid (rel ));
367+ attr = (Form_pg_attribute )GETSTRUCT (tuple );
368+
369+ datum = SysCacheGetAttr (ATTNUM ,
370+ tuple ,
371+ Anum_pg_attribute_attfdwoptions ,
372+ & isnull );
373+ if (!isnull )
374+ {
375+ List * options = untransformRelOptions (datum );
376+ ListCell * lc ;
377+
378+ foreach (lc ,options )
379+ {
380+ DefElem * def = (DefElem * )lfirst (lc );
381+
382+ if (strcmp (def -> defname ,"force_not_null" )== 0 )
383+ {
384+ if (defGetBoolean (def ))
385+ {
386+ char * attname = pstrdup (NameStr (attr -> attname ));
387+
388+ fnncolumns = lappend (fnncolumns ,makeString (attname ));
389+ }
390+ }
391+ /* maybe in future handle other options here */
392+ }
393+ }
394+
395+ ReleaseSysCache (tuple );
396+ }
397+
398+ heap_close (rel ,AccessShareLock );
399+
400+ /* Return DefElem only when some column(s) have force_not_null */
401+ if (fnncolumns != NIL )
402+ return list_make1 (makeDefElem ("force_not_null" , (Node * )fnncolumns ));
403+ else
404+ return NIL ;
405+ }
406+
309407/*
310408 * filePlanForeignScan
311409 *Create a FdwPlan for a scan on the foreign table