diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 2d36a72..f796a54 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", ForeignTableRelationId},
+
/* Sentinel */
{NULL, InvalidOid}
};
@@ -93,6 +96,7 @@ typedef struct FileFdwExecutionState
{
char *filename; /* file to read */
List *options; /* merged COPY options, excluding filename */
+ bool validate; /* whether to validate NOT NULL constraint */
CopyState cstate; /* state of reading file */
} FileFdwExecutionState;
@@ -134,7 +138,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);
static List *get_file_fdw_attribute_options(Oid relid);
static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
FileFdwPlanState *fdw_private);
@@ -180,6 +186,7 @@ file_fdw_validator(PG_FUNCTION_ARGS)
List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
Oid catalog = PG_GETARG_OID(1);
char *filename = NULL;
+ DefElem *validate = NULL;
DefElem *force_not_null = NULL;
List *other_options = NIL;
ListCell *cell;
@@ -247,6 +254,16 @@ file_fdw_validator(PG_FUNCTION_ARGS)
errmsg("conflicting or redundant options")));
filename = defGetString(def);
}
+ else if (strcmp(def->defname, "validate") == 0)
+ {
+ if (validate)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ validate = def;
+ /* Don't care what the value is, as long as it's a legal boolean */
+ (void) defGetBoolean(def);
+ }
else if (strcmp(def->defname, "force_not_null") == 0)
{
if (force_not_null)
@@ -301,8 +318,8 @@ is_valid_option(const char *option, Oid context)
* it must not appear in the options list passed to the core COPY code.
*/
static void
-fileGetOptions(Oid foreigntableid,
- char **filename, List **other_options)
+fileGetOptions(Oid foreigntableid, char **filename,
+ List **other_options, bool *validate)
{
ForeignTable *table;
ForeignServer *server;
@@ -333,6 +350,7 @@ fileGetOptions(Oid foreigntableid,
* Separate out the filename.
*/
*filename = NULL;
+ *validate = false;
prev = NULL;
foreach(lc, options)
{
@@ -342,7 +360,11 @@ fileGetOptions(Oid foreigntableid,
{
*filename = defGetString(def);
options = list_delete_cell(options, lc, prev);
- break;
+ }
+ if (strcmp(def->defname, "validate") == 0)
+ {
+ *validate = defGetBoolean(def);
+ options = list_delete_cell(options, lc, prev);
}
prev = lc;
}
@@ -425,6 +447,7 @@ fileGetForeignRelSize(PlannerInfo *root,
Oid foreigntableid)
{
FileFdwPlanState *fdw_private;
+ bool validate;
/*
* Fetch options. We only need filename at this point, but we might
@@ -432,7 +455,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);
baserel->fdw_private = (void *) fdw_private;
/* Estimate relation size */
@@ -517,10 +542,11 @@ fileExplainForeignScan(ForeignScanState *node, ExplainState *es)
{
char *filename;
List *options;
+ bool validate;
/* Fetch options --- we only need filename at this point */
fileGetOptions(RelationGetRelid(node->ss.ss_currentRelation),
- &filename, &options);
+ &filename, &options, &validate);
ExplainPropertyText("Foreign File", filename, es);
@@ -544,6 +570,7 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
{
char *filename;
List *options;
+ bool validate;
CopyState cstate;
FileFdwExecutionState *festate;
@@ -555,7 +582,7 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
/* Fetch options of foreign table */
fileGetOptions(RelationGetRelid(node->ss.ss_currentRelation),
- &filename, &options);
+ &filename, &options, &validate);
/*
* Create CopyState from FDW options. We always acquire all columns, so
@@ -573,6 +600,7 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
festate = (FileFdwExecutionState *) palloc(sizeof(FileFdwExecutionState));
festate->filename = filename;
festate->options = options;
+ festate->validate = validate;
festate->cstate = cstate;
node->fdw_state = (void *) festate;
@@ -588,6 +616,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 +644,11 @@ fileIterateForeignScan(ForeignScanState *node)
slot->tts_values, slot->tts_isnull,
NULL);
if (found)
+ {
ExecStoreVirtualTuple(slot);
+ if (constr && constr->has_not_null && festate->validate)
+ ExecNotNullCheck(rel, slot);
+ }
/* Remove error callback. */
error_context_stack = errcontext.previous;
@@ -664,10 +698,11 @@ fileAnalyzeForeignTable(Relation relation,
{
char *filename;
List *options;
+ bool validate;
struct stat stat_buf;
/* Fetch options of foreign table */
- fileGetOptions(RelationGetRelid(relation), &filename, &options);
+ fileGetOptions(RelationGetRelid(relation), &filename, &options, &validate);
/*
* Get size of the file. (XXX if we fail here, would it be better to
@@ -831,6 +866,7 @@ file_acquire_sample_rows(Relation onerel, int elevel,
bool found;
char *filename;
List *options;
+ bool validate;
CopyState cstate;
ErrorContextCallback errcontext;
MemoryContext oldcontext = CurrentMemoryContext;
@@ -844,7 +880,7 @@ 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);
/*
* 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..017f193 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
-- 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..d6b474b 100644
--- a/doc/src/sgml/file-fdw.sgml
+++ b/doc/src/sgml/file-fdw.sgml
@@ -108,6 +108,16 @@
+
+ validate
+
+
+
+ Specifies whether the NOT NULL constraint is validated.
+
+
+
+
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index fbb36fa..5417eea 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -80,7 +80,7 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
static bool ExecCheckRTEPerms(RangeTblEntry *rte);
static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
static char *ExecBuildSlotValueDescription(TupleTableSlot *slot,
- int maxfieldlen);
+ int maxfieldlen);
static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
Plan *planTree);
@@ -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)
{
@@ -1548,7 +1555,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
* here since heap field values could be very long, whereas index entries
* typically aren't so wide.
*/
-static char *
+char *
ExecBuildSlotValueDescription(TupleTableSlot *slot, int maxfieldlen)
{
StringInfoData buf;
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);