Created
August 23, 2017 10:42
-
-
Save ololobus/ed1e231c9eb842b4be55779743aad18d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 18f328c8a20dfdbd805921c6dcb6d665067507a5 Mon Sep 17 00:00:00 2001 | |
From: Alex K <alex.lumir@gmail.com> | |
Date: Fri, 9 Jun 2017 23:41:51 +0300 | |
Subject: [PATCH 1/8] Allow ignoring some errors during COPY FROM | |
--- | |
contrib/file_fdw/file_fdw.c | 4 +- | |
src/backend/commands/copy.c | 352 +++++++++++++++++++++++++------------------- | |
src/include/commands/copy.h | 2 +- | |
3 files changed, 207 insertions(+), 151 deletions(-) | |
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c | |
index 2396bd442f..c83887944a 100644 | |
--- a/contrib/file_fdw/file_fdw.c | |
+++ b/contrib/file_fdw/file_fdw.c | |
@@ -689,7 +689,7 @@ fileIterateForeignScan(ForeignScanState *node) | |
{ | |
FileFdwExecutionState *festate = (FileFdwExecutionState *) node->fdw_state; | |
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot; | |
- bool found; | |
+ int found; | |
ErrorContextCallback errcallback; | |
/* Set up callback to identify error line number. */ | |
@@ -1080,7 +1080,7 @@ file_acquire_sample_rows(Relation onerel, int elevel, | |
TupleDesc tupDesc; | |
Datum *values; | |
bool *nulls; | |
- bool found; | |
+ int found; | |
char *filename; | |
bool is_program; | |
List *options; | |
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c | |
index fc5f4f66ea..935ef960b0 100644 | |
--- a/src/backend/commands/copy.c | |
+++ b/src/backend/commands/copy.c | |
@@ -54,6 +54,16 @@ | |
#define OCTVALUE(c) ((c) - '0') | |
/* | |
+ NextCopyFrom states: | |
+ 0 – Error or stop | |
+ 1 – Successfully read data | |
+ 2 – Data with errors, skip | |
+*/ | |
+#define NCF_STOP 0 | |
+#define NCF_SUCCESS 1 | |
+#define NCF_SKIP 2 | |
+ | |
+/* | |
* Represents the different source/dest cases we need to worry about at | |
* the bottom level | |
*/ | |
@@ -139,6 +149,7 @@ typedef struct CopyStateData | |
int cur_lineno; /* line number for error messages */ | |
const char *cur_attname; /* current att for error messages */ | |
const char *cur_attval; /* current att value for error messages */ | |
+ bool ignore_errors; /* ignore errors during COPY FROM */ | |
/* | |
* Working state for COPY TO/FROM | |
@@ -2340,6 +2351,7 @@ CopyFrom(CopyState cstate) | |
bool useHeapMultiInsert; | |
int nBufferedTuples = 0; | |
int prev_leaf_part_index = -1; | |
+ int next_cf_state; /* NextCopyFrom return state */ | |
#define MAX_BUFFERED_TUPLES 1000 | |
HeapTuple *bufferedTuples = NULL; /* initialize to silence warning */ | |
@@ -2549,145 +2561,154 @@ CopyFrom(CopyState cstate) | |
/* Switch into its memory context */ | |
MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); | |
- if (!NextCopyFrom(cstate, econtext, values, nulls, &loaded_oid)) | |
- break; | |
- | |
- /* And now we can form the input tuple. */ | |
- tuple = heap_form_tuple(tupDesc, values, nulls); | |
- | |
- if (loaded_oid != InvalidOid) | |
- HeapTupleSetOid(tuple, loaded_oid); | |
- | |
- /* | |
- * Constraints might reference the tableoid column, so initialize | |
- * t_tableOid before evaluating them. | |
- */ | |
- tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc); | |
- | |
- /* Triggers and stuff need to be invoked in query context. */ | |
- MemoryContextSwitchTo(oldcontext); | |
- | |
- /* Place tuple in tuple slot --- but slot shouldn't free it */ | |
- slot = myslot; | |
- ExecStoreTuple(tuple, slot, InvalidBuffer, false); | |
- | |
- /* Determine the partition to heap_insert the tuple into */ | |
- if (cstate->partition_dispatch_info) | |
- { | |
- int leaf_part_index; | |
- TupleConversionMap *map; | |
- | |
- /* | |
- * Away we go ... If we end up not finding a partition after all, | |
- * ExecFindPartition() does not return and errors out instead. | |
- * Otherwise, the returned value is to be used as an index into | |
- * arrays mt_partitions[] and mt_partition_tupconv_maps[] that | |
- * will get us the ResultRelInfo and TupleConversionMap for the | |
- * partition, respectively. | |
- */ | |
- leaf_part_index = ExecFindPartition(resultRelInfo, | |
- cstate->partition_dispatch_info, | |
- slot, | |
- estate); | |
- Assert(leaf_part_index >= 0 && | |
- leaf_part_index < cstate->num_partitions); | |
- | |
- /* | |
- * If this tuple is mapped to a partition that is not same as the | |
- * previous one, we'd better make the bulk insert mechanism gets a | |
- * new buffer. | |
- */ | |
- if (prev_leaf_part_index != leaf_part_index) | |
- { | |
- ReleaseBulkInsertStatePin(bistate); | |
- prev_leaf_part_index = leaf_part_index; | |
- } | |
- | |
- /* | |
- * Save the old ResultRelInfo and switch to the one corresponding | |
- * to the selected partition. | |
- */ | |
- saved_resultRelInfo = resultRelInfo; | |
- resultRelInfo = cstate->partitions + leaf_part_index; | |
- | |
- /* We do not yet have a way to insert into a foreign partition */ | |
- if (resultRelInfo->ri_FdwRoutine) | |
- ereport(ERROR, | |
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | |
- errmsg("cannot route inserted tuples to a foreign table"))); | |
- | |
- /* | |
- * For ExecInsertIndexTuples() to work on the partition's indexes | |
- */ | |
- estate->es_result_relation_info = resultRelInfo; | |
- | |
- /* | |
- * If we're capturing transition tuples, we might need to convert | |
- * from the partition rowtype to parent rowtype. | |
- */ | |
- if (cstate->transition_capture != NULL) | |
- { | |
- if (resultRelInfo->ri_TrigDesc && | |
- (resultRelInfo->ri_TrigDesc->trig_insert_before_row || | |
- resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) | |
- { | |
- /* | |
- * If there are any BEFORE or INSTEAD triggers on the | |
- * partition, we'll have to be ready to convert their | |
- * result back to tuplestore format. | |
- */ | |
- cstate->transition_capture->tcs_original_insert_tuple = NULL; | |
- cstate->transition_capture->tcs_map = | |
- cstate->transition_tupconv_maps[leaf_part_index]; | |
- } | |
- else | |
- { | |
- /* | |
- * Otherwise, just remember the original unconverted | |
- * tuple, to avoid a needless round trip conversion. | |
- */ | |
- cstate->transition_capture->tcs_original_insert_tuple = tuple; | |
- cstate->transition_capture->tcs_map = NULL; | |
- } | |
- } | |
- /* | |
- * We might need to convert from the parent rowtype to the | |
- * partition rowtype. | |
- */ | |
- map = cstate->partition_tupconv_maps[leaf_part_index]; | |
- if (map) | |
- { | |
- Relation partrel = resultRelInfo->ri_RelationDesc; | |
- | |
- tuple = do_convert_tuple(tuple, map); | |
- | |
- /* | |
- * We must use the partition's tuple descriptor from this | |
- * point on. Use a dedicated slot from this point on until | |
- * we're finished dealing with the partition. | |
- */ | |
- slot = cstate->partition_tuple_slot; | |
- Assert(slot != NULL); | |
- ExecSetSlotDescriptor(slot, RelationGetDescr(partrel)); | |
- ExecStoreTuple(tuple, slot, InvalidBuffer, true); | |
- } | |
+ next_cf_state = NextCopyFrom(cstate, econtext, values, nulls, &loaded_oid); | |
- tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc); | |
- } | |
- | |
- skip_tuple = false; | |
- | |
- /* BEFORE ROW INSERT Triggers */ | |
- if (resultRelInfo->ri_TrigDesc && | |
- resultRelInfo->ri_TrigDesc->trig_insert_before_row) | |
- { | |
- slot = ExecBRInsertTriggers(estate, resultRelInfo, slot); | |
- | |
- if (slot == NULL) /* "do nothing" */ | |
- skip_tuple = true; | |
- else /* trigger might have changed tuple */ | |
- tuple = ExecMaterializeSlot(slot); | |
+ if (!next_cf_state) { | |
+ break; | |
} | |
+ else if (next_cf_state == NCF_SUCCESS) | |
+ { | |
+ /* And now we can form the input tuple. */ | |
+ tuple = heap_form_tuple(tupDesc, values, nulls); | |
+ | |
+ if (loaded_oid != InvalidOid) | |
+ HeapTupleSetOid(tuple, loaded_oid); | |
+ | |
+ /* | |
+ * Constraints might reference the tableoid column, so initialize | |
+ * t_tableOid before evaluating them. | |
+ */ | |
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc); | |
+ | |
+ /* Triggers and stuff need to be invoked in query context. */ | |
+ MemoryContextSwitchTo(oldcontext); | |
+ | |
+ /* Place tuple in tuple slot --- but slot shouldn't free it */ | |
+ slot = myslot; | |
+ ExecStoreTuple(tuple, slot, InvalidBuffer, false); | |
+ | |
+ /* Determine the partition to heap_insert the tuple into */ | |
+ if (cstate->partition_dispatch_info) | |
+ { | |
+ int leaf_part_index; | |
+ TupleConversionMap *map; | |
+ | |
+ /* | |
+ * Away we go ... If we end up not finding a partition after all, | |
+ * ExecFindPartition() does not return and errors out instead. | |
+ * Otherwise, the returned value is to be used as an index into | |
+ * arrays mt_partitions[] and mt_partition_tupconv_maps[] that | |
+ * will get us the ResultRelInfo and TupleConversionMap for the | |
+ * partition, respectively. | |
+ */ | |
+ leaf_part_index = ExecFindPartition(resultRelInfo, | |
+ cstate->partition_dispatch_info, | |
+ slot, | |
+ estate); | |
+ Assert(leaf_part_index >= 0 && | |
+ leaf_part_index < cstate->num_partitions); | |
+ | |
+ /* | |
+ * If this tuple is mapped to a partition that is not same as the | |
+ * previous one, we'd better make the bulk insert mechanism gets a | |
+ * new buffer. | |
+ */ | |
+ if (prev_leaf_part_index != leaf_part_index) | |
+ { | |
+ ReleaseBulkInsertStatePin(bistate); | |
+ prev_leaf_part_index = leaf_part_index; | |
+ } | |
+ | |
+ /* | |
+ * Save the old ResultRelInfo and switch to the one corresponding | |
+ * to the selected partition. | |
+ */ | |
+ saved_resultRelInfo = resultRelInfo; | |
+ resultRelInfo = cstate->partitions + leaf_part_index; | |
+ | |
+ /* We do not yet have a way to insert into a foreign partition */ | |
+ if (resultRelInfo->ri_FdwRoutine) | |
+ ereport(ERROR, | |
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | |
+ errmsg("cannot route inserted tuples to a foreign table"))); | |
+ | |
+ /* | |
+ * For ExecInsertIndexTuples() to work on the partition's indexes | |
+ */ | |
+ estate->es_result_relation_info = resultRelInfo; | |
+ | |
+ /* | |
+ * If we're capturing transition tuples, we might need to convert | |
+ * from the partition rowtype to parent rowtype. | |
+ */ | |
+ if (cstate->transition_capture != NULL) | |
+ { | |
+ if (resultRelInfo->ri_TrigDesc && | |
+ (resultRelInfo->ri_TrigDesc->trig_insert_before_row || | |
+ resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) | |
+ { | |
+ /* | |
+ * If there are any BEFORE or INSTEAD triggers on the | |
+ * partition, we'll have to be ready to convert their | |
+ * result back to tuplestore format. | |
+ */ | |
+ cstate->transition_capture->tcs_original_insert_tuple = NULL; | |
+ cstate->transition_capture->tcs_map = | |
+ cstate->transition_tupconv_maps[leaf_part_index]; | |
+ } | |
+ else | |
+ { | |
+ /* | |
+ * Otherwise, just remember the original unconverted | |
+ * tuple, to avoid a needless round trip conversion. | |
+ */ | |
+ cstate->transition_capture->tcs_original_insert_tuple = tuple; | |
+ cstate->transition_capture->tcs_map = NULL; | |
+ } | |
+ } | |
+ /* | |
+ * We might need to convert from the parent rowtype to the | |
+ * partition rowtype. | |
+ */ | |
+ map = cstate->partition_tupconv_maps[leaf_part_index]; | |
+ if (map) | |
+ { | |
+ Relation partrel = resultRelInfo->ri_RelationDesc; | |
+ | |
+ tuple = do_convert_tuple(tuple, map); | |
+ | |
+ /* | |
+ * We must use the partition's tuple descriptor from this | |
+ * point on. Use a dedicated slot from this point on until | |
+ * we're finished dealing with the partition. | |
+ */ | |
+ slot = cstate->partition_tuple_slot; | |
+ Assert(slot != NULL); | |
+ ExecSetSlotDescriptor(slot, RelationGetDescr(partrel)); | |
+ ExecStoreTuple(tuple, slot, InvalidBuffer, true); | |
+ } | |
+ | |
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc); | |
+ } | |
+ | |
+ skip_tuple = false; | |
+ | |
+ /* BEFORE ROW INSERT Triggers */ | |
+ if (resultRelInfo->ri_TrigDesc && | |
+ resultRelInfo->ri_TrigDesc->trig_insert_before_row) | |
+ { | |
+ slot = ExecBRInsertTriggers(estate, resultRelInfo, slot); | |
+ | |
+ if (slot == NULL) /* "do nothing" */ | |
+ skip_tuple = true; | |
+ else /* trigger might have changed tuple */ | |
+ tuple = ExecMaterializeSlot(slot); | |
+ } | |
+ } | |
+ else | |
+ { | |
+ skip_tuple = true; | |
+ } | |
if (!skip_tuple) | |
{ | |
@@ -2985,6 +3006,7 @@ BeginCopyFrom(ParseState *pstate, | |
cstate->cur_lineno = 0; | |
cstate->cur_attname = NULL; | |
cstate->cur_attval = NULL; | |
+ cstate->ignore_errors = true; | |
/* Set up variables to avoid per-attribute overhead. */ | |
initStringInfo(&cstate->attribute_buf); | |
@@ -3262,7 +3284,7 @@ NextCopyFromRawFields(CopyState cstate, char ***fields, int *nfields) | |
* relation passed to BeginCopyFrom. This function fills the arrays. | |
* Oid of the tuple is returned with 'tupleOid' separately. | |
*/ | |
-bool | |
+int | |
NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
Datum *values, bool *nulls, Oid *tupleOid) | |
{ | |
@@ -3280,11 +3302,20 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
int *defmap = cstate->defmap; | |
ExprState **defexprs = cstate->defexprs; | |
+ int error_level = ERROR; /* Error level for COPY FROM input data errors */ | |
+ int exec_state = NCF_SUCCESS; /* Return code */ | |
+ | |
tupDesc = RelationGetDescr(cstate->rel); | |
attr = tupDesc->attrs; | |
num_phys_attrs = tupDesc->natts; | |
attr_count = list_length(cstate->attnumlist); | |
nfields = file_has_oids ? (attr_count + 1) : attr_count; | |
+ | |
+ /* Set error level to WARNING, if errors handling is turned on */ | |
+ if (cstate->ignore_errors) | |
+ { | |
+ error_level = WARNING; | |
+ } | |
/* Initialize all values for row to NULL */ | |
MemSet(values, 0, num_phys_attrs * sizeof(Datum)); | |
@@ -3300,13 +3331,17 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
/* read raw fields in the next line */ | |
if (!NextCopyFromRawFields(cstate, &field_strings, &fldct)) | |
- return false; | |
+ return NCF_STOP; | |
/* check for overflowing fields */ | |
if (nfields > 0 && fldct > nfields) | |
- ereport(ERROR, | |
+ { | |
+ exec_state = NCF_SKIP; | |
+ ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("extra data after last expected column"))); | |
+ } | |
+ | |
fieldno = 0; | |
@@ -3314,15 +3349,22 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
if (file_has_oids) | |
{ | |
if (fieldno >= fldct) | |
- ereport(ERROR, | |
+ { | |
+ exec_state = NCF_SKIP; | |
+ ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("missing data for OID column"))); | |
+ } | |
+ | |
string = field_strings[fieldno++]; | |
if (string == NULL) | |
- ereport(ERROR, | |
+ { | |
+ exec_state = NCF_SKIP; | |
+ ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("null OID in COPY data"))); | |
+ } | |
else if (cstate->oids && tupleOid != NULL) | |
{ | |
cstate->cur_attname = "oid"; | |
@@ -3330,9 +3372,13 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
*tupleOid = DatumGetObjectId(DirectFunctionCall1(oidin, | |
CStringGetDatum(string))); | |
if (*tupleOid == InvalidOid) | |
- ereport(ERROR, | |
+ { | |
+ exec_state = NCF_SKIP; | |
+ ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("invalid OID in COPY data"))); | |
+ } | |
+ | |
cstate->cur_attname = NULL; | |
cstate->cur_attval = NULL; | |
} | |
@@ -3345,10 +3391,14 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
int m = attnum - 1; | |
if (fieldno >= fldct) | |
- ereport(ERROR, | |
+ { | |
+ exec_state = NCF_SKIP; | |
+ ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("missing data for column \"%s\"", | |
NameStr(attr[m]->attname)))); | |
+ } | |
+ | |
string = field_strings[fieldno++]; | |
if (cstate->convert_select_flags && | |
@@ -3431,14 +3481,17 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
ereport(ERROR, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("received copy data after EOF marker"))); | |
- return false; | |
+ return NCF_STOP; | |
} | |
if (fld_count != attr_count) | |
- ereport(ERROR, | |
+ { | |
+ exec_state = NCF_SKIP; | |
+ ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("row field count is %d, expected %d", | |
(int) fld_count, attr_count))); | |
+ } | |
if (file_has_oids) | |
{ | |
@@ -3453,9 +3506,12 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
-1, | |
&isnull)); | |
if (isnull || loaded_oid == InvalidOid) | |
- ereport(ERROR, | |
+ { | |
+ exec_state = NCF_SKIP; | |
+ ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("invalid OID in COPY data"))); | |
+ } | |
cstate->cur_attname = NULL; | |
if (cstate->oids && tupleOid != NULL) | |
*tupleOid = loaded_oid; | |
@@ -3497,7 +3553,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
&nulls[defmap[i]]); | |
} | |
- return true; | |
+ return exec_state; | |
} | |
/* | |
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h | |
index 8b2971d287..bba64fc3d7 100644 | |
--- a/src/include/commands/copy.h | |
+++ b/src/include/commands/copy.h | |
@@ -31,7 +31,7 @@ extern void ProcessCopyOptions(ParseState *pstate, CopyState cstate, bool is_fro | |
extern CopyState BeginCopyFrom(ParseState *pstate, Relation rel, const char *filename, | |
bool is_program, copy_data_source_cb data_source_cb, List *attnamelist, List *options); | |
extern void EndCopyFrom(CopyState cstate); | |
-extern bool NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
+extern int NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
Datum *values, bool *nulls, Oid *tupleOid); | |
extern bool NextCopyFromRawFields(CopyState cstate, | |
char ***fields, int *nfields); | |
-- | |
2.11.0 | |
From 9b19c9e365c019ae0e3e56d05502044c5683fd09 Mon Sep 17 00:00:00 2001 | |
From: Alex K <alex.lumir@gmail.com> | |
Date: Sun, 11 Jun 2017 16:48:37 +0300 | |
Subject: [PATCH 2/8] Report line number on each ERROR/WARNING | |
--- | |
src/backend/commands/copy.c | 12 ++++++------ | |
1 file changed, 6 insertions(+), 6 deletions(-) | |
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c | |
index 935ef960b0..4dd9acd899 100644 | |
--- a/src/backend/commands/copy.c | |
+++ b/src/backend/commands/copy.c | |
@@ -3339,7 +3339,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
exec_state = NCF_SKIP; | |
ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
- errmsg("extra data after last expected column"))); | |
+ errmsg("extra data after last expected column at line %d", cstate->cur_lineno))); | |
} | |
@@ -3353,7 +3353,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
exec_state = NCF_SKIP; | |
ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
- errmsg("missing data for OID column"))); | |
+ errmsg("missing data for OID column at line %d", cstate->cur_lineno))); | |
} | |
string = field_strings[fieldno++]; | |
@@ -3363,7 +3363,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
exec_state = NCF_SKIP; | |
ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
- errmsg("null OID in COPY data"))); | |
+ errmsg("null OID in COPY data at line %d", cstate->cur_lineno))); | |
} | |
else if (cstate->oids && tupleOid != NULL) | |
{ | |
@@ -3376,7 +3376,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
exec_state = NCF_SKIP; | |
ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
- errmsg("invalid OID in COPY data"))); | |
+ errmsg("invalid OID in COPY data at line %d", cstate->cur_lineno))); | |
} | |
cstate->cur_attname = NULL; | |
@@ -3395,8 +3395,8 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
exec_state = NCF_SKIP; | |
ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
- errmsg("missing data for column \"%s\"", | |
- NameStr(attr[m]->attname)))); | |
+ errmsg("missing data for column \"%s\" at line %d", | |
+ NameStr(attr[m]->attname), cstate->cur_lineno))); | |
} | |
string = field_strings[fieldno++]; | |
-- | |
2.11.0 | |
From 9e395007d1b35dcaaa90fe884514091b6dec5dd9 Mon Sep 17 00:00:00 2001 | |
From: Alex K <alex.lumir@gmail.com> | |
Date: Tue, 13 Jun 2017 14:57:59 +0300 | |
Subject: [PATCH 3/8] Catch errors at InputFunctionCall inside NextCopyFrom | |
--- | |
src/backend/commands/copy.c | 29 +++++++++++++++++++++++++++++ | |
1 file changed, 29 insertions(+) | |
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c | |
index 4dd9acd899..be76c0df1e 100644 | |
--- a/src/backend/commands/copy.c | |
+++ b/src/backend/commands/copy.c | |
@@ -3304,6 +3304,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
int error_level = ERROR; /* Error level for COPY FROM input data errors */ | |
int exec_state = NCF_SUCCESS; /* Return code */ | |
+ MemoryContext oldcontext = CurrentMemoryContext; | |
tupDesc = RelationGetDescr(cstate->rel); | |
attr = tupDesc->attrs; | |
@@ -3434,10 +3435,38 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
cstate->cur_attname = NameStr(attr[m]->attname); | |
cstate->cur_attval = string; | |
+ | |
+ PG_TRY(); | |
+ { | |
values[m] = InputFunctionCall(&in_functions[m], | |
string, | |
typioparams[m], | |
attr[m]->atttypmod); | |
+ } | |
+ PG_CATCH(); | |
+ { | |
+ if (cstate->ignore_errors) | |
+ { | |
+ ErrorData *edata; | |
+ | |
+ /* Save error info */ | |
+ MemoryContextSwitchTo(oldcontext); | |
+ edata = CopyErrorData(); | |
+ FlushErrorState(); | |
+ | |
+ /* TODO Find an appropriate errcode */ | |
+ ereport(WARNING, | |
+ (errcode(ERRCODE_TOO_MANY_COLUMNS), | |
+ errmsg("%s at line %d col %d", edata->message, cstate->cur_lineno, attnum))); | |
+ return NCF_SKIP; | |
+ } | |
+ else | |
+ { | |
+ PG_RE_THROW(); | |
+ } | |
+ } | |
+ PG_END_TRY(); | |
+ | |
if (string != NULL) | |
nulls[m] = false; | |
cstate->cur_attname = NULL; | |
-- | |
2.11.0 | |
From c5074cf2b4d7595a770c9ac891bf78386532b9f8 Mon Sep 17 00:00:00 2001 | |
From: Alex K <alex.lumir@gmail.com> | |
Date: Tue, 13 Jun 2017 16:30:50 +0300 | |
Subject: [PATCH 4/8] Tabs vs spaces consistency | |
--- | |
src/backend/commands/copy.c | 12 ++++++------ | |
1 file changed, 6 insertions(+), 6 deletions(-) | |
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c | |
index be76c0df1e..f1042f2f41 100644 | |
--- a/src/backend/commands/copy.c | |
+++ b/src/backend/commands/copy.c | |
@@ -3447,17 +3447,17 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
{ | |
if (cstate->ignore_errors) | |
{ | |
- ErrorData *edata; | |
+ ErrorData *edata; | |
- /* Save error info */ | |
+ /* Save error info */ | |
MemoryContextSwitchTo(oldcontext); | |
- edata = CopyErrorData(); | |
+ edata = CopyErrorData(); | |
FlushErrorState(); | |
/* TODO Find an appropriate errcode */ | |
- ereport(WARNING, | |
- (errcode(ERRCODE_TOO_MANY_COLUMNS), | |
- errmsg("%s at line %d col %d", edata->message, cstate->cur_lineno, attnum))); | |
+ ereport(WARNING, | |
+ (errcode(ERRCODE_TOO_MANY_COLUMNS), | |
+ errmsg("%s at line %d col %d", edata->message, cstate->cur_lineno, attnum))); | |
return NCF_SKIP; | |
} | |
else | |
-- | |
2.11.0 | |
From 9a8c8ebed9f6722738bc3bb3132d488074ef9654 Mon Sep 17 00:00:00 2001 | |
From: Alex K <alex.lumir@gmail.com> | |
Date: Thu, 15 Jun 2017 20:35:51 +0300 | |
Subject: [PATCH 5/8] Fixed possible Segmentation fault during malformed lines | |
processing | |
--- | |
src/backend/commands/copy.c | 20 ++++++++++---------- | |
1 file changed, 10 insertions(+), 10 deletions(-) | |
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c | |
index f1042f2f41..cb5b4ad302 100644 | |
--- a/src/backend/commands/copy.c | |
+++ b/src/backend/commands/copy.c | |
@@ -3303,7 +3303,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
ExprState **defexprs = cstate->defexprs; | |
int error_level = ERROR; /* Error level for COPY FROM input data errors */ | |
- int exec_state = NCF_SUCCESS; /* Return code */ | |
+ // int exec_state = NCF_SUCCESS; /* Return code */ | |
MemoryContext oldcontext = CurrentMemoryContext; | |
tupDesc = RelationGetDescr(cstate->rel); | |
@@ -3337,10 +3337,10 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
/* check for overflowing fields */ | |
if (nfields > 0 && fldct > nfields) | |
{ | |
- exec_state = NCF_SKIP; | |
ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("extra data after last expected column at line %d", cstate->cur_lineno))); | |
+ return NCF_SKIP; | |
} | |
@@ -3351,20 +3351,20 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
{ | |
if (fieldno >= fldct) | |
{ | |
- exec_state = NCF_SKIP; | |
ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("missing data for OID column at line %d", cstate->cur_lineno))); | |
+ return NCF_SKIP; | |
} | |
string = field_strings[fieldno++]; | |
if (string == NULL) | |
{ | |
- exec_state = NCF_SKIP; | |
ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("null OID in COPY data at line %d", cstate->cur_lineno))); | |
+ return NCF_SKIP; | |
} | |
else if (cstate->oids && tupleOid != NULL) | |
{ | |
@@ -3374,10 +3374,10 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
CStringGetDatum(string))); | |
if (*tupleOid == InvalidOid) | |
{ | |
- exec_state = NCF_SKIP; | |
ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("invalid OID in COPY data at line %d", cstate->cur_lineno))); | |
+ return NCF_SKIP; | |
} | |
cstate->cur_attname = NULL; | |
@@ -3393,11 +3393,11 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
if (fieldno >= fldct) | |
{ | |
- exec_state = NCF_SKIP; | |
ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("missing data for column \"%s\" at line %d", | |
NameStr(attr[m]->attname), cstate->cur_lineno))); | |
+ return NCF_SKIP; | |
} | |
string = field_strings[fieldno++]; | |
@@ -3486,7 +3486,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
if (!CopyGetInt16(cstate, &fld_count)) | |
{ | |
/* EOF detected (end of file, or protocol-level EOF) */ | |
- return false; | |
+ return NCF_STOP; | |
} | |
if (fld_count == -1) | |
@@ -3515,11 +3515,11 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
if (fld_count != attr_count) | |
{ | |
- exec_state = NCF_SKIP; | |
ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("row field count is %d, expected %d", | |
(int) fld_count, attr_count))); | |
+ return NCF_SKIP; | |
} | |
if (file_has_oids) | |
@@ -3536,10 +3536,10 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
&isnull)); | |
if (isnull || loaded_oid == InvalidOid) | |
{ | |
- exec_state = NCF_SKIP; | |
ereport(error_level, | |
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT), | |
errmsg("invalid OID in COPY data"))); | |
+ return NCF_SKIP; | |
} | |
cstate->cur_attname = NULL; | |
if (cstate->oids && tupleOid != NULL) | |
@@ -3582,7 +3582,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
&nulls[defmap[i]]); | |
} | |
- return exec_state; | |
+ return NCF_SUCCESS; | |
} | |
/* | |
-- | |
2.11.0 | |
From c4c93114eae5bbe6850e07c79f21be51f3f9b76c Mon Sep 17 00:00:00 2001 | |
From: Alex K <alex.lumir@gmail.com> | |
Date: Fri, 16 Jun 2017 13:14:29 +0300 | |
Subject: [PATCH 6/8] Propagate catched SQL error code to warning | |
--- | |
src/backend/commands/copy.c | 15 ++++++++++----- | |
1 file changed, 10 insertions(+), 5 deletions(-) | |
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c | |
index cb5b4ad302..6305b3b0c5 100644 | |
--- a/src/backend/commands/copy.c | |
+++ b/src/backend/commands/copy.c | |
@@ -3302,8 +3302,8 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
int *defmap = cstate->defmap; | |
ExprState **defexprs = cstate->defexprs; | |
- int error_level = ERROR; /* Error level for COPY FROM input data errors */ | |
- // int exec_state = NCF_SUCCESS; /* Return code */ | |
+ /* Error level for COPY FROM input data errors */ | |
+ int error_level = ERROR; | |
MemoryContext oldcontext = CurrentMemoryContext; | |
tupDesc = RelationGetDescr(cstate->rel); | |
@@ -3436,6 +3436,10 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
cstate->cur_attname = NameStr(attr[m]->attname); | |
cstate->cur_attval = string; | |
+ /* | |
+ * Catch errors inside InputFunctionCall to handle | |
+ * errors due to the type invalid syntax. | |
+ */ | |
PG_TRY(); | |
{ | |
values[m] = InputFunctionCall(&in_functions[m], | |
@@ -3453,15 +3457,16 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, | |
MemoryContextSwitchTo(oldcontext); | |
edata = CopyErrorData(); | |
FlushErrorState(); | |
- | |
- /* TODO Find an appropriate errcode */ | |
+ | |
+ /* Propagate catched ERROR sqlerrcode and message as WARNING */ | |
ereport(WARNING, | |
- (errcode(ERRCODE_TOO_MANY_COLUMNS), | |
+ (errcode(edata->sqlerrcode), | |
errmsg("%s at line %d col %d", edata->message, cstate->cur_lineno, attnum))); | |
return NCF_SKIP; | |
} | |
else | |
{ | |
+ /* Propagate ERROR as is if errors handling is not turned on */ | |
PG_RE_THROW(); | |
} | |
} | |
-- | |
2.11.0 | |
From cc2c804122693ca9caa7f49dc19bbb7c43dbe87c Mon Sep 17 00:00:00 2001 | |
From: Alex K <alex.lumir@gmail.com> | |
Date: Mon, 19 Jun 2017 20:59:34 +0300 | |
Subject: [PATCH 7/8] IGNORE_ERRORS COPY SQL option added (only new syntax | |
supported) | |
--- | |
doc/src/sgml/ref/copy.sgml | 12 ++++++++++++ | |
src/backend/commands/copy.c | 10 +++++++++- | |
src/backend/parser/gram.y | 6 +++++- | |
3 files changed, 26 insertions(+), 2 deletions(-) | |
diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml | |
index 8de1150dfb..5e322308d6 100644 | |
--- a/doc/src/sgml/ref/copy.sgml | |
+++ b/doc/src/sgml/ref/copy.sgml | |
@@ -44,6 +44,7 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable | |
FORCE_NOT_NULL ( <replaceable class="parameter">column_name</replaceable> [, ...] ) | |
FORCE_NULL ( <replaceable class="parameter">column_name</replaceable> [, ...] ) | |
ENCODING '<replaceable class="parameter">encoding_name</replaceable>' | |
+ IGNORE_ERRORS [ <replaceable class="parameter">boolean</replaceable> ] | |
</synopsis> | |
</refsynopsisdiv> | |
@@ -249,6 +250,17 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable | |
</varlistentry> | |
<varlistentry> | |
+ <term><literal>IGNORE_ERRORS</literal></term> | |
+ <listitem> | |
+ <para> | |
+ Specifies ignoring errors in the input data. If TRUE, then all | |
+ errors due to the extra or missing columns and inappropriate type | |
+ format will be ignored with WARNING. | |
+ </para> | |
+ </listitem> | |
+ </varlistentry> | |
+ | |
+ <varlistentry> | |
<term><literal>NULL</literal></term> | |
<listitem> | |
<para> | |
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c | |
index 6305b3b0c5..29c5376b2d 100644 | |
--- a/src/backend/commands/copy.c | |
+++ b/src/backend/commands/copy.c | |
@@ -1214,6 +1214,15 @@ ProcessCopyOptions(ParseState *pstate, | |
defel->defname), | |
parser_errposition(pstate, defel->location))); | |
} | |
+ else if (strcmp(defel->defname, "ignore_errors") == 0) | |
+ { | |
+ if (cstate->ignore_errors) | |
+ ereport(ERROR, | |
+ (errcode(ERRCODE_SYNTAX_ERROR), | |
+ errmsg("conflicting or redundant options"), | |
+ parser_errposition(pstate, defel->location))); | |
+ cstate->ignore_errors = defGetBoolean(defel); | |
+ } | |
else | |
ereport(ERROR, | |
(errcode(ERRCODE_SYNTAX_ERROR), | |
@@ -3006,7 +3015,6 @@ BeginCopyFrom(ParseState *pstate, | |
cstate->cur_lineno = 0; | |
cstate->cur_attname = NULL; | |
cstate->cur_attval = NULL; | |
- cstate->ignore_errors = true; | |
/* Set up variables to avoid per-attribute overhead. */ | |
initStringInfo(&cstate->attribute_buf); | |
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y | |
index 0f3998ff89..83ff1ab72b 100644 | |
--- a/src/backend/parser/gram.y | |
+++ b/src/backend/parser/gram.y | |
@@ -636,7 +636,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); | |
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P | |
INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P | |
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER | |
- INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION | |
+ INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION IGNORE_ERRORS | |
JOIN | |
@@ -2977,6 +2977,10 @@ copy_opt_item: | |
{ | |
$$ = makeDefElem("encoding", (Node *)makeString($2), @1); | |
} | |
+ | IGNORE_ERRORS | |
+ { | |
+ $$ = makeDefElem("ignore_errors", (Node *)makeInteger(TRUE), @1); | |
+ } | |
; | |
/* The following exist for backward compatibility with very old versions */ | |
-- | |
2.11.0 | |
From 659aec15f7fcc391daa084f43dc1e1a66bdbe48e Mon Sep 17 00:00:00 2001 | |
From: Alex K <alex.lumir@gmail.com> | |
Date: Wed, 21 Jun 2017 14:32:44 +0300 | |
Subject: [PATCH 8/8] Regression test updated to handle new COPY functionality | |
--- | |
src/test/regress/expected/alter_table.out | 2 +- | |
src/test/regress/expected/copy2.out | 35 +++++++++++++++++++++++++++---- | |
src/test/regress/sql/copy2.sql | 23 ++++++++++++++++++++ | |
3 files changed, 55 insertions(+), 5 deletions(-) | |
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out | |
index 13d6a4b747..078ab27359 100644 | |
--- a/src/test/regress/expected/alter_table.out | |
+++ b/src/test/regress/expected/alter_table.out | |
@@ -1431,7 +1431,7 @@ ERROR: column "a" of relation "test" does not exist | |
copy test("........pg.dropped.1........") to stdout; | |
ERROR: column "........pg.dropped.1........" of relation "test" does not exist | |
copy test from stdin; | |
-ERROR: extra data after last expected column | |
+ERROR: extra data after last expected column at line 1 | |
CONTEXT: COPY test, line 1: "10 11 12" | |
select * from test; | |
b | c | |
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out | |
index 65e9c626b3..ab4d47b351 100644 | |
--- a/src/test/regress/expected/copy2.out | |
+++ b/src/test/regress/expected/copy2.out | |
@@ -36,15 +36,26 @@ COPY x from stdin; | |
ERROR: invalid input syntax for integer: "" | |
CONTEXT: COPY x, line 1, column a: "" | |
COPY x from stdin; | |
-ERROR: missing data for column "e" | |
+ERROR: missing data for column "e" at line 1 | |
CONTEXT: COPY x, line 1: "2000 230 23 23" | |
COPY x from stdin; | |
-ERROR: missing data for column "e" | |
+ERROR: missing data for column "e" at line 1 | |
CONTEXT: COPY x, line 1: "2001 231 \N \N" | |
-- extra data: should fail | |
COPY x from stdin; | |
-ERROR: extra data after last expected column | |
+ERROR: extra data after last expected column at line 1 | |
CONTEXT: COPY x, line 1: "2002 232 40 50 60 70 80" | |
+-- missing data: should be able to ignore errors | |
+COPY x from stdin with (ignore_errors); | |
+WARNING: missing data for column "e" at line 1 | |
+COPY x from stdin with (ignore_errors); | |
+WARNING: missing data for column "e" at line 1 | |
+-- extra data: should be able to ignore errors | |
+COPY x from stdin with (ignore_errors); | |
+WARNING: extra data after last expected column at line 1 | |
+-- data type inappropriate format: should be able to ignore errors | |
+COPY x from stdin with (ignore_errors); | |
+WARNING: invalid input syntax for integer: "'24'" at line 2 col 2 | |
-- various COPY options: delimiters, oids, NULL string, encoding | |
COPY x (b, c, d, e) from stdin with oids delimiter ',' null 'x'; | |
COPY x from stdin WITH DELIMITER AS ';' NULL AS ''; | |
@@ -60,6 +71,10 @@ SELECT * FROM x; | |
10003 | 24 | 34 | 44 | before trigger fired | |
10004 | 25 | 35 | 45 | before trigger fired | |
10005 | 26 | 36 | 46 | before trigger fired | |
+ 2004 | 27 | 37 | 47 | before trigger fired | |
+ 2006 | 28 | 38 | 48 | before trigger fired | |
+ 2008 | 29 | 39 | 49 | before trigger fired | |
+ 2009 | 30 | 40 | 50 | before trigger fired | |
6 | | 45 | 80 | before trigger fired | |
7 | | x | \x | before trigger fired | |
8 | | , | \, | before trigger fired | |
@@ -78,7 +93,7 @@ SELECT * FROM x; | |
3 | 3 | stuff | test_3 | after trigger fired | |
4 | 4 | stuff | test_4 | after trigger fired | |
5 | 5 | stuff | test_5 | after trigger fired | |
-(25 rows) | |
+(29 rows) | |
-- COPY w/ oids on a table w/o oids should fail | |
CREATE TABLE no_oids ( | |
@@ -101,6 +116,10 @@ COPY x TO stdout; | |
10003 24 34 44 before trigger fired | |
10004 25 35 45 before trigger fired | |
10005 26 36 46 before trigger fired | |
+2004 27 37 47 before trigger fired | |
+2006 28 38 48 before trigger fired | |
+2008 29 39 49 before trigger fired | |
+2009 30 40 50 before trigger fired | |
6 \N 45 80 before trigger fired | |
7 \N x \\x before trigger fired | |
8 \N , \\, before trigger fired | |
@@ -127,6 +146,10 @@ COPY x (c, e) TO stdout; | |
34 before trigger fired | |
35 before trigger fired | |
36 before trigger fired | |
+37 before trigger fired | |
+38 before trigger fired | |
+39 before trigger fired | |
+40 before trigger fired | |
45 before trigger fired | |
x before trigger fired | |
, before trigger fired | |
@@ -153,6 +176,10 @@ I'm null before trigger fired | |
24 before trigger fired | |
25 before trigger fired | |
26 before trigger fired | |
+27 before trigger fired | |
+28 before trigger fired | |
+29 before trigger fired | |
+30 before trigger fired | |
I'm null before trigger fired | |
I'm null before trigger fired | |
I'm null before trigger fired | |
diff --git a/src/test/regress/sql/copy2.sql b/src/test/regress/sql/copy2.sql | |
index f3a6d228fa..692f4d2f29 100644 | |
--- a/src/test/regress/sql/copy2.sql | |
+++ b/src/test/regress/sql/copy2.sql | |
@@ -72,6 +72,29 @@ COPY x from stdin; | |
2002 232 40 50 60 70 80 | |
\. | |
+-- missing data: should be able to ignore errors | |
+COPY x from stdin with (ignore_errors); | |
+2003 230 23 23 | |
+2004 27 37 47 57 | |
+\. | |
+COPY x from stdin with (ignore_errors); | |
+2005 231 \N \N | |
+2006 28 38 48 58 | |
+\. | |
+ | |
+-- extra data: should be able to ignore errors | |
+COPY x from stdin with (ignore_errors); | |
+2007 232 40 50 60 70 80 | |
+2008 29 39 49 59 | |
+\. | |
+ | |
+-- data type inappropriate format: should be able to ignore errors | |
+ | |
+COPY x from stdin with (ignore_errors); | |
+2009 30 40 50 60 | |
+2010 '24' 34 44 54 | |
+\. | |
+ | |
-- various COPY options: delimiters, oids, NULL string, encoding | |
COPY x (b, c, d, e) from stdin with oids delimiter ',' null 'x'; | |
500000,x,45,80,90 | |
-- | |
2.11.0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment