diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 2d36a72..0369aab 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -71,6 +71,9 @@ static const struct FileFdwOption valid_options[] = {
* force_quote is not supported by file_fdw because it's for COPY TO.
*/
+ /* Validation options */
+ {"validate_data_file", ForeignTableRelationId},
+
/* Sentinel */
{NULL, InvalidOid}
};
@@ -94,6 +97,8 @@ typedef struct FileFdwExecutionState
char *filename; /* file to read */
List *options; /* merged COPY options, excluding filename */
CopyState cstate; /* state of reading file */
+ bool validate_data_file;
+ /* whether to validate NOT NULL constraints */
} FileFdwExecutionState;
/*
@@ -134,7 +139,9 @@ static bool fileAnalyzeForeignTable(Relation relation,
*/
static bool is_valid_option(const char *option, Oid context);
static void fileGetOptions(Oid foreigntableid,
- char **filename, List **other_options);
+ char **filename,
+ List **other_options,
+ bool *validate_data_file);
static List *get_file_fdw_attribute_options(Oid relid);
static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
FileFdwPlanState *fdw_private);
@@ -182,6 +189,7 @@ file_fdw_validator(PG_FUNCTION_ARGS)
char *filename = NULL;
DefElem *force_not_null = NULL;
List *other_options = NIL;
+ DefElem *validate_data_file = NULL;
ListCell *cell;
/*
@@ -235,9 +243,9 @@ file_fdw_validator(PG_FUNCTION_ARGS)
}
/*
- * Separate out filename and force_not_null, since ProcessCopyOptions
- * won't accept them. (force_not_null only comes in a boolean
- * per-column flavor here.)
+ * Separate out filename, force_not_null and validate_data_file, since
+ * ProcessCopyOptions won't accept them. (force_not_null only comes in
+ * a boolean per-column flavor here.)
*/
if (strcmp(def->defname, "filename") == 0)
{
@@ -257,6 +265,16 @@ file_fdw_validator(PG_FUNCTION_ARGS)
/* Don't care what the value is, as long as it's a legal boolean */
(void) defGetBoolean(def);
}
+ else if (strcmp(def->defname, "validate_data_file") == 0)
+ {
+ if (validate_data_file)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ validate_data_file = def;
+ /* Don't care what the value is, as long as it's a legal boolean */
+ (void) defGetBoolean(def);
+ }
else
other_options = lappend(other_options, def);
}
@@ -297,19 +315,23 @@ is_valid_option(const char *option, Oid context)
/*
* 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.
+ * We have to separate out "filename" and "validate_data_file" from the other
+ * options because it must not appear in the options list passed to the core
+ * COPY code.
*/
static void
fileGetOptions(Oid foreigntableid,
- char **filename, List **other_options)
+ char **filename,
+ List **other_options,
+ bool *validate_data_file)
{
ForeignTable *table;
ForeignServer *server;
ForeignDataWrapper *wrapper;
List *options;
ListCell *lc,
- *prev;
+ *prev,
+ *next;
/*
* Extract options from FDW objects. We ignore user mappings because
@@ -330,21 +352,28 @@ fileGetOptions(Oid foreigntableid,
options = list_concat(options, get_file_fdw_attribute_options(foreigntableid));
/*
- * Separate out the filename.
+ * Separate out the filename and data file validation option.
*/
*filename = NULL;
+ *validate_data_file = false;
prev = NULL;
- foreach(lc, options)
+ for (lc = list_head(options); lc; lc = next)
{
DefElem *def = (DefElem *) lfirst(lc);
+ next = lnext(lc);
if (strcmp(def->defname, "filename") == 0)
{
*filename = defGetString(def);
options = list_delete_cell(options, lc, prev);
- break;
}
- prev = lc;
+ else if (strcmp(def->defname, "validate_data_file") == 0)
+ {
+ *validate_data_file = defGetBoolean(def);
+ options = list_delete_cell(options, lc, prev);
+ }
+ else
+ prev = lc;
}
/*
@@ -425,6 +454,7 @@ fileGetForeignRelSize(PlannerInfo *root,
Oid foreigntableid)
{
FileFdwPlanState *fdw_private;
+ bool validate_data_file;
/*
* Fetch options. We only need filename at this point, but we might
@@ -432,7 +462,9 @@ fileGetForeignRelSize(PlannerInfo *root,
*/
fdw_private = (FileFdwPlanState *) palloc(sizeof(FileFdwPlanState));
fileGetOptions(foreigntableid,
- &fdw_private->filename, &fdw_private->options);
+ &fdw_private->filename,
+ &fdw_private->options,
+ &validate_data_file);
baserel->fdw_private = (void *) fdw_private;
/* Estimate relation size */
@@ -517,10 +549,13 @@ fileExplainForeignScan(ForeignScanState *node, ExplainState *es)
{
char *filename;
List *options;
+ bool validate_data_file;
/* Fetch options --- we only need filename at this point */
fileGetOptions(RelationGetRelid(node->ss.ss_currentRelation),
- &filename, &options);
+ &filename,
+ &options,
+ &validate_data_file);
ExplainPropertyText("Foreign File", filename, es);
@@ -544,6 +579,7 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
{
char *filename;
List *options;
+ bool validate_data_file;
CopyState cstate;
FileFdwExecutionState *festate;
@@ -555,7 +591,9 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
/* Fetch options of foreign table */
fileGetOptions(RelationGetRelid(node->ss.ss_currentRelation),
- &filename, &options);
+ &filename,
+ &options,
+ &validate_data_file);
/*
* Create CopyState from FDW options. We always acquire all columns, so
@@ -574,6 +612,7 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
festate->filename = filename;
festate->options = options;
festate->cstate = cstate;
+ festate->validate_data_file = validate_data_file;
node->fdw_state = (void *) festate;
}
@@ -588,6 +627,8 @@ fileIterateForeignScan(ForeignScanState *node)
{
FileFdwExecutionState *festate = (FileFdwExecutionState *) node->fdw_state;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+ Relation rel = node->ss.ss_currentRelation;
+ TupleConstr *constr = rel->rd_att->constr;
bool found;
ErrorContextCallback errcontext;
@@ -614,7 +655,11 @@ fileIterateForeignScan(ForeignScanState *node)
slot->tts_values, slot->tts_isnull,
NULL);
if (found)
+ {
ExecStoreVirtualTuple(slot);
+ if (constr && constr->has_not_null && festate->validate_data_file)
+ ExecNotNullCheck(rel, slot);
+ }
/* Remove error callback. */
error_context_stack = errcontext.previous;
@@ -664,10 +709,14 @@ fileAnalyzeForeignTable(Relation relation,
{
char *filename;
List *options;
+ bool validate_data_file;
struct stat stat_buf;
/* Fetch options of foreign table */
- fileGetOptions(RelationGetRelid(relation), &filename, &options);
+ fileGetOptions(RelationGetRelid(relation),
+ &filename,
+ &options,
+ &validate_data_file);
/*
* Get size of the file. (XXX if we fail here, would it be better to
@@ -831,6 +880,7 @@ file_acquire_sample_rows(Relation onerel, int elevel,
bool found;
char *filename;
List *options;
+ bool validate_data_file;
CopyState cstate;
ErrorContextCallback errcontext;
MemoryContext oldcontext = CurrentMemoryContext;
@@ -844,7 +894,10 @@ file_acquire_sample_rows(Relation onerel, int elevel,
nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
/* Fetch options of foreign table */
- fileGetOptions(RelationGetRelid(onerel), &filename, &options);
+ fileGetOptions(RelationGetRelid(onerel),
+ &filename,
+ &options,
+ &validate_data_file);
/*
* Create CopyState from FDW options.
diff --git a/contrib/file_fdw/output/file_fdw.source b/contrib/file_fdw/output/file_fdw.source
index 84f0750..1232e5d 100644
--- a/contrib/file_fdw/output/file_fdw.source
+++ b/contrib/file_fdw/output/file_fdw.source
@@ -123,7 +123,7 @@ ERROR: invalid option "force_not_null"
HINT: Valid options in this context are:
CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (force_not_null '*'); -- ERROR
ERROR: invalid option "force_not_null"
-HINT: Valid options in this context are: filename, format, header, delimiter, quote, escape, null, encoding
+HINT: Valid options in this context are: filename, format, header, delimiter, quote, escape, null, encoding, validate_data_file
-- basic query tests
SELECT * FROM agg_text WHERE b > 10.0 ORDER BY a;
a | b
diff --git a/doc/src/sgml/file-fdw.sgml b/doc/src/sgml/file-fdw.sgml
index 4c7f7e8..4816042 100644
--- a/doc/src/sgml/file-fdw.sgml
+++ b/doc/src/sgml/file-fdw.sgml
@@ -108,6 +108,16 @@
+
+ validate_data_file
+
+
+
+ Specifies whether the NOT NULL constraint is validated.
+
+
+
+
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index fbb36fa..2de72df 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1438,6 +1438,28 @@ ExecutePlan(EState *estate,
/*
+ * ExecNotNullCheck --- check that tuple meets NOT NULL constraints
+ */
+void
+ExecNotNullCheck(Relation rel, TupleTableSlot *slot)
+{
+ int natts = rel->rd_att->natts;
+ int attrChk;
+
+ for (attrChk = 1; attrChk <= natts; attrChk++)
+ {
+ if (rel->rd_att->attrs[attrChk - 1]->attnotnull &&
+ slot_attisnull(slot, attrChk))
+ ereport(ERROR,
+ (errcode(ERRCODE_NOT_NULL_VIOLATION),
+ errmsg("null value in column \"%s\" violates not-null constraint",
+ NameStr(rel->rd_att->attrs[attrChk - 1]->attname)),
+ errdetail("Failing row contains %s.",
+ ExecBuildSlotValueDescription(slot, 64))));
+ }
+}
+
+/*
* ExecRelCheck --- check that tuple meets constraints for result relation
*/
static const char *
@@ -1509,22 +1531,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Assert(constr);
if (constr->has_not_null)
- {
- int natts = rel->rd_att->natts;
- int attrChk;
-
- for (attrChk = 1; attrChk <= natts; attrChk++)
- {
- if (rel->rd_att->attrs[attrChk - 1]->attnotnull &&
- slot_attisnull(slot, attrChk))
- ereport(ERROR,
- (errcode(ERRCODE_NOT_NULL_VIOLATION),
- errmsg("null value in column \"%s\" violates not-null constraint",
- NameStr(rel->rd_att->attrs[attrChk - 1]->attname)),
- errdetail("Failing row contains %s.",
- ExecBuildSlotValueDescription(slot, 64))));
- }
- }
+ ExecNotNullCheck(rel, slot);
if (constr->num_check > 0)
{
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index f5503a5..5eda8ea 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -188,6 +188,7 @@ extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
+extern void ExecNotNullCheck(Relation rel, TupleTableSlot *slot);
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate);
extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti);