file_fdw_notnull_v2.patch

application/octet-stream

Filename: file_fdw_notnull_v2.patch
Type: application/octet-stream
Part: 0
Message: not null validation option in contrib/file_fdw

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
Series: patch v2
File+
contrib/file_fdw/file_fdw.c 45 9
contrib/file_fdw/output/file_fdw.source 1 1
doc/src/sgml/file-fdw.sgml 10 0
src/backend/executor/execMain.c 25 18
src/include/executor/executor.h 1 0
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 @@
    </listitem>
   </varlistentry>
 
+  <varlistentry>
+   <term><literal>validate</literal></term>
+
+   <listitem>
+    <para>
+     Specifies whether the <literal>NOT NULL</literal> constraint is validated.
+    </para>
+   </listitem>
+  </varlistentry>
+
  </variablelist>
 
  <para>
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);