diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml
index 8433344e5b6f..b627d422b384 100644
--- a/doc/src/sgml/ref/copy.sgml
+++ b/doc/src/sgml/ref/copy.sgml
@@ -44,6 +44,7 @@ COPY { table_name [ ( column_name [, ...] ) | * }
FORCE_NULL { ( column_name [, ...] ) | * }
ON_ERROR error_action
+ TABLE error_saving_table
REJECT_LIMIT maxerror
ENCODING 'encoding_name'
LOG_VERBOSITY verbosity
@@ -395,15 +396,25 @@ COPY { table_name [ ( error_action value of
stop means fail the command, while
- ignore means discard the input row and continue with the next one.
+ ignore means discard the input row and continue with the next one,
+ table means save error details to error_saving_table
+ and continue with the next one.
The default is stop.
- The ignore option is applicable only for COPY FROM
+ The ignore and table option are applicable only for COPY FROM
when the FORMAT is text or csv.
- A NOTICE message containing the ignored row count is
+ If ON_ERROR=table,
+ a NOTICE message containing the row count that is saved to
+ error_saving_table is
+ emitted at the end of the COPY FROM if at least one
+ row was saved.
+
+
+
+ If ON_ERROR=ignore, a NOTICE message containing the ignored row count is
emitted at the end of the COPY FROM if at least one
row was discarded. When LOG_VERBOSITY option is set to
verbose, a NOTICE message
@@ -463,6 +474,108 @@ COPY { table_name [ (
+
+ TABLE
+
+
+ Save error context details to the table error_saving_table.
+ This option is allowed only in COPY FROM and
+ ON_ERROR is specified with TABLE.
+ It also require user have INSERT privileges on all columns
+ in the error_saving_table.
+
+
+
+ If table error_saving_table does meet the following definition
+ (column ordinal position should be the same as the below), an error will be raised.
+
+
+
+
+
+ Column name
+ Data type
+ Description
+
+
+
+
+
+ userid
+ oid
+ The user generated the error.
+ Reference pg_authid.oid,
+ however there is no hard dependency with catalog pg_authid.
+ If the corresponding row on pg_authid is deleted, this value becomes stale.
+
+
+
+
+ copy_tbl
+ oid
+ The COPY FROM operation destination table oid.
+ Reference pg_class.oid,
+ however there is no hard dependency with catalog pg_class.
+ If the corresponding row on pg_class is deleted, this value becomes stale.
+
+
+
+
+ filename
+ text
+ The path name of the COPY FROM input
+
+
+
+ lineno
+ bigint
+ Line number where the error occurred, counting from 1
+
+
+
+ line
+ text
+ Raw content of the error occurred line
+
+
+
+ colname
+ text
+ Field where the error occurred
+
+
+
+ raw_field_value
+ text
+ Raw content of the error occurred field
+
+
+
+ err_message
+ text
+ The error message
+
+
+
+ err_detail
+ text
+ Detailed error message
+
+
+
+ errorcode
+ text
+ The error code
+
+
+
+
+
+
+
+
+
+
WHERE
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 74ae42b19a71..a54922141174 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -410,6 +410,8 @@ defGetCopyOnErrorChoice(DefElem *def, ParseState *pstate, bool is_from)
if (pg_strcasecmp(sval, "ignore") == 0)
return COPY_ON_ERROR_IGNORE;
+ if (pg_strcasecmp(sval, "table") == 0)
+ return COPY_ON_ERROR_TABLE;
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
/*- translator: first %s is the name of a COPY option, e.g. ON_ERROR */
@@ -502,6 +504,7 @@ ProcessCopyOptions(ParseState *pstate,
bool freeze_specified = false;
bool header_specified = false;
bool on_error_specified = false;
+ bool on_error_tbl_specified = false;
bool log_verbosity_specified = false;
bool reject_limit_specified = false;
ListCell *option;
@@ -677,6 +680,13 @@ ProcessCopyOptions(ParseState *pstate,
reject_limit_specified = true;
opts_out->reject_limit = defGetCopyRejectLimitOption(defel);
}
+ else if (strcmp(defel->defname, "table") == 0)
+ {
+ if (on_error_tbl_specified)
+ errorConflictingDefElem(defel, pstate);
+ on_error_tbl_specified = true;
+ opts_out->on_error_tbl = defGetString(defel);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -685,6 +695,25 @@ ProcessCopyOptions(ParseState *pstate,
parser_errposition(pstate, defel->location)));
}
+ if (opts_out->on_error == COPY_ON_ERROR_TABLE)
+ {
+ if (opts_out->on_error_tbl == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot specify %s option value to \"%s\" when %s is not specified", "ON_ERROR", "TABLE", "TABLE"),
+ errhint("You may need also specify \"%s\" option.", "TABLE"));
+
+ if (opts_out->reject_limit)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot specify %s option when %s option is specified as \"%s\"", "REJECT_LIMIT", "ON_ERROR", "TABLE"));
+ }
+
+ if (opts_out->on_error != COPY_ON_ERROR_TABLE && opts_out->on_error_tbl != NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("COPY %s option can only be used when %s option is specified as \"%s\"", "TABLE", "ON_ERROR", "TABLE"));
+
/*
* Check for incompatible options (must do these three before inserting
* defaults)
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index fbbbc09a97b1..91816a7f781a 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -44,7 +44,10 @@
#include "pgstat.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
+#include "storage/lmgr.h"
#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/portal.h"
@@ -1175,6 +1178,16 @@ CopyFrom(CopyFromState cstate)
continue;
}
+ if (cstate->opts.on_error == COPY_ON_ERROR_TABLE &&
+ cstate->escontext->error_occurred)
+ {
+ cstate->escontext->error_occurred = false;
+ cstate->escontext->details_wanted = true;
+ memset(cstate->escontext->error_data, 0, sizeof(ErrorData));
+ /* Repeat NextCopyFrom() until no soft error occurs */
+ continue;
+ }
+
ExecStoreVirtualTuple(myslot);
/*
@@ -1467,14 +1480,31 @@ CopyFrom(CopyFromState cstate)
/* Done, clean up */
error_context_stack = errcallback.previous;
- if (cstate->opts.on_error != COPY_ON_ERROR_STOP &&
- cstate->num_errors > 0 &&
+ if (cstate->num_errors > 0 &&
cstate->opts.log_verbosity >= COPY_LOG_VERBOSITY_DEFAULT)
- ereport(NOTICE,
- errmsg_plural("%" PRIu64 " row was skipped due to data type incompatibility",
- "%" PRIu64 " rows were skipped due to data type incompatibility",
- cstate->num_errors,
- cstate->num_errors));
+ {
+ if (cstate->opts.on_error == COPY_ON_ERROR_IGNORE)
+ ereport(NOTICE,
+ errmsg_plural("%" PRIu64 " row was skipped due to data type incompatibility",
+ "%" PRIu64 " rows were skipped due to data type incompatibility",
+ cstate->num_errors,
+ cstate->num_errors));
+ else if (cstate->opts.on_error == COPY_ON_ERROR_TABLE)
+ ereport(NOTICE,
+ errmsg_plural("%" PRIu64 " row was saved to table \"%s\" due to data type incompatibility",
+ "%" PRIu64 " rows were saved to table \"%s\" due to data type incompatibility",
+ cstate->num_errors,
+ cstate->num_errors,
+ RelationGetRelationName(cstate->error_saving_rel)));
+ }
+
+ /*
+ * similar to
+ * (https://postgr.es/m/7bcfc39d4176faf85ab317d0c26786953646a411.camel@cybertec.at)
+ * in COPY FROM keep error saving table locks until the transaction end.
+ */
+ if (cstate->error_saving_rel != NULL)
+ table_close(cstate->error_saving_rel, NoLock);
if (bistate != NULL)
FreeBulkInsertState(bistate);
@@ -1622,15 +1652,169 @@ BeginCopyFrom(ParseState *pstate,
cstate->escontext->error_occurred = false;
/*
- * Currently we only support COPY_ON_ERROR_IGNORE. We'll add other
- * options later
+ * Currently we only support COPY_ON_ERROR_IGNORE, COPY_ON_ERROR_TABLE.
+ * We'll add other options later.
*/
if (cstate->opts.on_error == COPY_ON_ERROR_IGNORE)
cstate->escontext->details_wanted = false;
+ else if (cstate->opts.on_error == COPY_ON_ERROR_TABLE)
+ cstate->escontext->details_wanted = true;
}
else
cstate->escontext = NULL;
+ if (cstate->opts.on_error == COPY_ON_ERROR_TABLE)
+ {
+ Datum ins_prev;
+ Oid err_tbl_oid;
+ Relation pg_attribute;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple atup;
+ Form_pg_attribute attForm;
+ int attcnt = 0;
+ bool on_error_tbl_ok = true;
+
+ Assert(cstate->opts.on_error_tbl != NULL);
+
+ err_tbl_oid = RelnameGetRelid(cstate->opts.on_error_tbl);
+ if (!OidIsValid(err_tbl_oid))
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist",
+ cstate->opts.on_error_tbl));
+
+ if (RelationGetRelid(cstate->rel) == err_tbl_oid)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot use relation \"%s\" for COPY error saving while copying data to it",
+ cstate->opts.on_error_tbl));
+
+ /* error saving table must be a regular realtion */
+ if (get_rel_relkind(err_tbl_oid) != RELKIND_RELATION)
+ ereport(ERROR,
+ errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot use relation \"%s\" for COPY error saving",
+ cstate->opts.on_error_tbl),
+ errdetail_relkind_not_supported(get_rel_relkind(err_tbl_oid)));
+
+ /*
+ * we may insert tuples to error-saving table later, to do that we need
+ * first check it's lock situation. If it is already under heavy lock,
+ * then our COPY operation would stuck. Instead of let COPY FROM stuck,
+ * just error report the error saving table is under heavy lock.
+ */
+ if (!ConditionalLockRelationOid(err_tbl_oid, RowExclusiveLock))
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("can not use table \"%s\" for error saving because it was being locked",
+ cstate->opts.on_error_tbl));
+
+ cstate->error_saving_rel = table_open(err_tbl_oid, RowExclusiveLock);
+
+ /* current user should have INSERT privilege on error_saving table */
+ ins_prev = DirectFunctionCall3(has_table_privilege_id_id,
+ ObjectIdGetDatum(GetUserId()),
+ ObjectIdGetDatum(err_tbl_oid),
+ CStringGetTextDatum("INSERT"));
+ if (!DatumGetBool(ins_prev))
+ ereport(ERROR,
+ errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to set table \"%s\" for COPY FROM error saving",
+ RelationGetRelationName(cstate->error_saving_rel)),
+ errhint("Ensure current user have enough privilege on \"%s\" for COPY FROM error saving",
+ RelationGetRelationName(cstate->error_saving_rel)));
+
+ /*
+ * Verify whether the definition of the table
+ * (cstate->error_saving_rel)— including column names, data types, and
+ * the number of columns— is suitable for use in error saving.
+ */
+ pg_attribute = table_open(AttributeRelationId, AccessShareLock);
+ ScanKeyInit(&scankey,
+ Anum_pg_attribute_attrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(err_tbl_oid));
+
+ scan = systable_beginscan(pg_attribute, AttributeRelidNumIndexId, true,
+ SnapshotSelf, 1, &scankey);
+ while (HeapTupleIsValid(atup = systable_getnext(scan)) && on_error_tbl_ok)
+ {
+ attForm = (Form_pg_attribute) GETSTRUCT(atup);
+
+ if (attForm->attnum < 1 || attForm->attisdropped)
+ continue;
+
+ attcnt++;
+ switch (attForm->attnum)
+ {
+ case 1:
+ if (attForm->atttypid != OIDOID ||
+ strcmp(NameStr(attForm->attname), "userid") != 0)
+ on_error_tbl_ok = false;
+ break;
+ case 2:
+ if (attForm->atttypid != OIDOID ||
+ strcmp(NameStr(attForm->attname), "copy_tbl") != 0)
+ on_error_tbl_ok = false;
+ break;
+ case 3:
+ if (attForm->atttypid != TEXTOID ||
+ strcmp(NameStr(attForm->attname), "filename") != 0)
+ on_error_tbl_ok = false;
+ break;
+ case 4:
+ if (attForm->atttypid != INT8OID ||
+ strcmp(NameStr(attForm->attname), "lineno") != 0)
+ on_error_tbl_ok = false;
+ break;
+ case 5:
+ if (attForm->atttypid != TEXTOID ||
+ strcmp(NameStr(attForm->attname), "line") != 0)
+ on_error_tbl_ok = false;
+ break;
+ case 6:
+ if (attForm->atttypid != TEXTOID ||
+ strcmp(NameStr(attForm->attname), "colname") != 0)
+ on_error_tbl_ok = false;
+ break;
+ case 7:
+ if (attForm->atttypid != TEXTOID ||
+ strcmp(NameStr(attForm->attname), "raw_field_value") != 0)
+ on_error_tbl_ok = false;
+ break;
+ case 8:
+ if (attForm->atttypid != TEXTOID ||
+ strcmp(NameStr(attForm->attname), "err_message") != 0)
+ on_error_tbl_ok = false;
+ break;
+ case 9:
+ if (attForm->atttypid != TEXTOID ||
+ strcmp(NameStr(attForm->attname), "err_detail") != 0)
+ on_error_tbl_ok = false;
+ break;
+ case 10:
+ if (attForm->atttypid != TEXTOID ||
+ strcmp(NameStr(attForm->attname), "errorcode") != 0)
+ on_error_tbl_ok = false;
+ break;
+ default:
+ on_error_tbl_ok = false;
+ break;
+ }
+ }
+ systable_endscan(scan);
+ table_close(pg_attribute, AccessShareLock);
+
+ if (attcnt != ERROR_TBL_COLUMNS || !on_error_tbl_ok)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("table \"%s\" cannot be used for COPY error saving",
+ RelationGetRelationName(cstate->error_saving_rel)),
+ errdetail("Table \"%s\" data definition is not suitable for error saving",
+ RelationGetRelationName(cstate->error_saving_rel)));
+ }
+
/* Convert FORCE_NULL name list to per-column flags, check validity */
cstate->opts.force_null_flags = (bool *) palloc0(num_phys_attrs * sizeof(bool));
if (cstate->opts.force_null_all)
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index f5fc346e2013..34acacf86603 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -62,6 +62,7 @@
#include
#include
+#include "access/heapam.h"
#include "commands/copyapi.h"
#include "commands/copyfrom_internal.h"
#include "commands/progress.h"
@@ -78,6 +79,8 @@
#define ISOCTAL(c) (((c) >= '0') && ((c) <= '7'))
#define OCTVALUE(c) ((c) - '0')
+#define ERROR_TBL_COLUMNS 10
+
/*
* These macros centralize code used to process line_buf and input_buf buffers.
* They are macros because they often do continue/break control and to avoid
@@ -1035,9 +1038,54 @@ CopyFromTextLikeOneRow(CopyFromState cstate, ExprContext *econtext,
{
Assert(cstate->opts.on_error != COPY_ON_ERROR_STOP);
+ if (cstate->opts.on_error == COPY_ON_ERROR_TABLE)
+ {
+ /*
+ * We use ErrorSaveContext to form a tuple and insert it to the
+ * error saving table. A RowExclusiveLock on error_saving_rel
+ * was already acquired in BeginCopyFrom.
+ */
+ HeapTuple tuple;
+ TupleDesc tupdesc;
+ char *err_detail;
+ char *err_code;
+ Datum values[ERROR_TBL_COLUMNS] = {0};
+ bool isnull[ERROR_TBL_COLUMNS] = {0};
+ int j = 0;
+
+ Assert(cstate->rel != NULL);
+ Assert(cstate->escontext->error_occurred);
+
+ values[j++] = ObjectIdGetDatum(GetUserId());
+ values[j++] = ObjectIdGetDatum(cstate->rel->rd_rel->oid);
+ values[j++] = CStringGetTextDatum(cstate->filename ? cstate->filename : "STDIN");
+ values[j++] = Int64GetDatum((long long) cstate->cur_lineno);
+ values[j++] = CStringGetTextDatum(cstate->line_buf.data);
+ values[j++] = CStringGetTextDatum(cstate->cur_attname);
+ values[j++] = CStringGetTextDatum(string);
+ values[j++] = CStringGetTextDatum(cstate->escontext->error_data->message);
+
+ if (!cstate->escontext->error_data->detail)
+ err_detail = NULL;
+ else
+ err_detail = cstate->escontext->error_data->detail;
+ values[j] = err_detail ? CStringGetTextDatum(err_detail) : (Datum) 0;
+ isnull[j++] = err_detail ? false : true;
+
+ err_code = unpack_sql_state(cstate->escontext->error_data->sqlerrcode);
+ values[j++] = CStringGetTextDatum(err_code);
+
+ Assert(j == ERROR_TBL_COLUMNS);
+
+ tupdesc = RelationGetDescr(cstate->error_saving_rel);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ simple_heap_insert(cstate->error_saving_rel, tuple);
+ }
+
cstate->num_errors++;
- if (cstate->opts.log_verbosity == COPY_LOG_VERBOSITY_VERBOSE)
+ if (cstate->opts.log_verbosity == COPY_LOG_VERBOSITY_VERBOSE &&
+ cstate->opts.on_error == COPY_ON_ERROR_IGNORE)
{
/*
* Since we emit line number and column info in the below
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3c4268b271a4..0a45305c4b4e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3577,6 +3577,7 @@ copy_generic_opt_arg:
| NumericOnly { $$ = (Node *) $1; }
| '*' { $$ = (Node *) makeNode(A_Star); }
| DEFAULT { $$ = (Node *) makeString("default"); }
+ | TABLE { $$ = (Node *) makeString("table"); }
| '(' copy_generic_opt_arg_list ')' { $$ = (Node *) $2; }
| /* EMPTY */ { $$ = NULL; }
;
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index 06dfdfef7210..0c7a2defc512 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -38,8 +38,15 @@ typedef enum CopyOnErrorChoice
{
COPY_ON_ERROR_STOP = 0, /* immediately throw errors, default */
COPY_ON_ERROR_IGNORE, /* ignore errors */
+ COPY_ON_ERROR_TABLE, /* saving errors info to table */
} CopyOnErrorChoice;
+/*
+ * used for (COPY on_error 'table'); the error saving table have only 10
+ * columns.
+*/
+#define ERROR_TBL_COLUMNS 10
+
/*
* Represents verbosity of logged messages by COPY command.
*/
@@ -86,6 +93,7 @@ typedef struct CopyFormatOptions
CopyOnErrorChoice on_error; /* what to do when error happened */
CopyLogVerbosityChoice log_verbosity; /* verbosity of logged messages */
int64 reject_limit; /* maximum tolerable number of errors */
+ char *on_error_tbl; /* on error, save error info to the table, table name */
List *convert_select; /* list of column names (can be NIL) */
} CopyFormatOptions;
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index c8b22af22d85..c974311f6bf4 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -73,6 +73,7 @@ typedef struct CopyFromStateData
/* parameters from the COPY command */
Relation rel; /* relation to copy from */
+ Relation error_saving_rel; /* relation for copy from error saving */
List *attnumlist; /* integer list of attnums to copy */
char *filename; /* filename, or NULL for STDIN */
bool is_program; /* is 'filename' a program to popen? */
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index 64ea33aeae8f..67c15d8849d6 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -813,6 +813,103 @@ ERROR: skipped more than REJECT_LIMIT (3) rows due to data type incompatibility
CONTEXT: COPY check_ign_err, line 5, column n: ""
COPY check_ign_err FROM STDIN WITH (on_error ignore, reject_limit 4);
NOTICE: 4 rows were skipped due to data type incompatibility
+create table err_tbl(
+userid oid, -- the user oid while copy generated this entry
+copy_tbl oid, --copy table
+filename text,
+lineno bigint,
+line text,
+colname text,
+raw_field_value text,
+err_message text,
+err_detail text,
+errorcode text
+);
+--cannot use for error saving.
+create table err_tbl_1(
+userid oid, copy_tbl oid, filename text, lineno bigint, line text,
+colname text, raw_field_value text,
+err_message text,
+err_detail text);
+--cannot use for error saving.
+create table err_tbl_2(
+userid oid, copy_tbl oid, filename text, lineno bigint,line text,
+colname text, raw_field_value text, err_message text,
+err_detail text,
+errorcode text,
+errorcode1 text
+);
+create table t_copy_tbl(a int, b int, c int);
+create view s1 as select 1 as a;
+----invalid options, the below all should fails
+COPY err_tbl FROM STDIN WITH (DELIMITER ',', on_error table, table err_tbl);
+ERROR: cannot use relation "err_tbl" for COPY error saving while copying data to it
+COPY t_copy_tbl FROM STDIN WITH (on_error table);
+ERROR: cannot specify ON_ERROR option value to "TABLE" when TABLE is not specified
+HINT: You may need also specify "TABLE" option.
+COPY t_copy_tbl FROM STDIN WITH (table err_tbl);
+ERROR: COPY TABLE option can only be used when ON_ERROR option is specified as "TABLE"
+COPY t_copy_tbl TO STDIN WITH (on_error table);
+ERROR: COPY ON_ERROR cannot be used with COPY TO
+LINE 1: COPY t_copy_tbl TO STDIN WITH (on_error table);
+ ^
+COPY t_copy_tbl(a,b) FROM STDIN WITH (on_error table, reject_limit 10, table err_tbl);
+ERROR: cannot specify REJECT_LIMIT option when ON_ERROR option is specified as "TABLE"
+COPY t_copy_tbl(a,b) FROM STDIN WITH (on_error table, table not_exists);
+ERROR: relation "not_exists" does not exist
+COPY t_copy_tbl(a) FROM STDIN WITH (on_error table, table s1);
+ERROR: cannot use relation "s1" for COPY error saving
+DETAIL: This operation is not supported for views.
+--should fail. err_tbl_1 does not meet criteria
+COPY t_copy_tbl(a,b) FROM STDIN WITH (on_error table, table err_tbl_1);
+ERROR: table "err_tbl_1" cannot be used for COPY error saving
+DETAIL: Table "err_tbl_1" data definition is not suitable for error saving
+--should fail. err_tbl_2 does not meet criteria
+COPY t_copy_tbl(a,b) FROM STDIN WITH (on_error table, table err_tbl_2);
+ERROR: table "err_tbl_2" cannot be used for COPY error saving
+DETAIL: Table "err_tbl_2" data definition is not suitable for error saving
+----invalid options, the above all should fails
+--should fail, copied data have extra columns
+COPY t_copy_tbl(a,b) FROM STDIN WITH (DELIMITER ',', on_error table, table err_tbl);
+ERROR: extra data after last expected column
+CONTEXT: COPY t_copy_tbl, line 1: "1,2,3,4"
+--should fail, copied data have less columns
+COPY t_copy_tbl(a,b) FROM STDIN WITH (DELIMITER ',', on_error table, table err_tbl);
+ERROR: extra data after last expected column
+CONTEXT: COPY t_copy_tbl, line 1: "1,2,"
+--ok cases.
+COPY t_copy_tbl FROM STDIN WITH (DELIMITER ',', on_error table, table err_tbl);
+NOTICE: 4 rows were saved to table "err_tbl" due to data type incompatibility
+--should fail. lack privilege
+begin;
+create user regress_user20;
+grant insert(userid,copy_tbl,filename,lineno,line) on table err_tbl to regress_user20;
+grant insert on table t_copy_tbl to regress_user20;
+set role regress_user20;
+COPY t_copy_tbl FROM STDIN WITH (delimiter ',', on_error table, table err_tbl);
+ERROR: permission denied to set table "err_tbl" for COPY FROM error saving
+HINT: Ensure current user have enough privilege on "err_tbl" for COPY FROM error saving
+ROLLBACK;
+select pg_class.relname as copy_destination
+ ,filename,lineno ,line
+ ,colname,raw_field_value,err_message
+ ,err_detail,errorcode
+from err_tbl join pg_class on copy_tbl = pg_class.oid;
+ copy_destination | filename | lineno | line | colname | raw_field_value | err_message | err_detail | errorcode
+------------------+----------+--------+--------------------------+---------+---------------------+--------------------------------------------------------------+------------+-----------
+ t_copy_tbl | STDIN | 1 | 1,2,a | c | a | invalid input syntax for type integer: "a" | | 22P02
+ t_copy_tbl | STDIN | 3 | 1,_junk,test | b | _junk | invalid input syntax for type integer: "_junk" | | 22P02
+ t_copy_tbl | STDIN | 4 | cola,colb,colc | a | cola | invalid input syntax for type integer: "cola" | | 22P02
+ t_copy_tbl | STDIN | 6 | 1,11,4238679732489879879 | c | 4238679732489879879 | value "4238679732489879879" is out of range for type integer | | 22003
+(4 rows)
+
+select * from t_copy_tbl;
+ a | b | c
+---+---+---
+ 1 | 2 | 3
+ 4 | 5 | 6
+(2 rows)
+
-- clean up
DROP TABLE forcetest;
DROP TABLE vistest;
@@ -831,6 +928,10 @@ DROP TABLE check_ign_err;
DROP TABLE check_ign_err2;
DROP DOMAIN dcheck_ign_err2;
DROP TABLE hard_err;
+DROP TABLE err_tbl;
+DROP TABLE err_tbl_1;
+DROP TABLE err_tbl_2;
+DROP TABLE t_copy_tbl CASCADE;
--
-- COPY FROM ... DEFAULT
--
diff --git a/src/test/regress/sql/copy2.sql b/src/test/regress/sql/copy2.sql
index 45273557ce04..a190432682c6 100644
--- a/src/test/regress/sql/copy2.sql
+++ b/src/test/regress/sql/copy2.sql
@@ -588,6 +588,86 @@ a {7} 7
10 {10} 10
\.
+create table err_tbl(
+userid oid, -- the user oid while copy generated this entry
+copy_tbl oid, --copy table
+filename text,
+lineno bigint,
+line text,
+colname text,
+raw_field_value text,
+err_message text,
+err_detail text,
+errorcode text
+);
+--cannot use for error saving.
+create table err_tbl_1(
+userid oid, copy_tbl oid, filename text, lineno bigint, line text,
+colname text, raw_field_value text,
+err_message text,
+err_detail text);
+
+--cannot use for error saving.
+create table err_tbl_2(
+userid oid, copy_tbl oid, filename text, lineno bigint,line text,
+colname text, raw_field_value text, err_message text,
+err_detail text,
+errorcode text,
+errorcode1 text
+);
+create table t_copy_tbl(a int, b int, c int);
+create view s1 as select 1 as a;
+
+----invalid options, the below all should fails
+COPY err_tbl FROM STDIN WITH (DELIMITER ',', on_error table, table err_tbl);
+COPY t_copy_tbl FROM STDIN WITH (on_error table);
+COPY t_copy_tbl FROM STDIN WITH (table err_tbl);
+COPY t_copy_tbl TO STDIN WITH (on_error table);
+COPY t_copy_tbl(a,b) FROM STDIN WITH (on_error table, reject_limit 10, table err_tbl);
+COPY t_copy_tbl(a,b) FROM STDIN WITH (on_error table, table not_exists);
+COPY t_copy_tbl(a) FROM STDIN WITH (on_error table, table s1);
+--should fail. err_tbl_1 does not meet criteria
+COPY t_copy_tbl(a,b) FROM STDIN WITH (on_error table, table err_tbl_1);
+--should fail. err_tbl_2 does not meet criteria
+COPY t_copy_tbl(a,b) FROM STDIN WITH (on_error table, table err_tbl_2);
+----invalid options, the above all should fails
+
+--should fail, copied data have extra columns
+COPY t_copy_tbl(a,b) FROM STDIN WITH (DELIMITER ',', on_error table, table err_tbl);
+1,2,3,4
+\.
+
+--should fail, copied data have less columns
+COPY t_copy_tbl(a,b) FROM STDIN WITH (DELIMITER ',', on_error table, table err_tbl);
+1,2,
+\.
+
+--ok cases.
+COPY t_copy_tbl FROM STDIN WITH (DELIMITER ',', on_error table, table err_tbl);
+1,2,a
+1,2,3
+1,_junk,test
+cola,colb,colc
+4,5,6
+1,11,4238679732489879879
+\.
+
+--should fail. lack privilege
+begin;
+create user regress_user20;
+grant insert(userid,copy_tbl,filename,lineno,line) on table err_tbl to regress_user20;
+grant insert on table t_copy_tbl to regress_user20;
+set role regress_user20;
+COPY t_copy_tbl FROM STDIN WITH (delimiter ',', on_error table, table err_tbl);
+ROLLBACK;
+
+select pg_class.relname as copy_destination
+ ,filename,lineno ,line
+ ,colname,raw_field_value,err_message
+ ,err_detail,errorcode
+from err_tbl join pg_class on copy_tbl = pg_class.oid;
+select * from t_copy_tbl;
+
-- clean up
DROP TABLE forcetest;
DROP TABLE vistest;
@@ -606,6 +686,10 @@ DROP TABLE check_ign_err;
DROP TABLE check_ign_err2;
DROP DOMAIN dcheck_ign_err2;
DROP TABLE hard_err;
+DROP TABLE err_tbl;
+DROP TABLE err_tbl_1;
+DROP TABLE err_tbl_2;
+DROP TABLE t_copy_tbl CASCADE;
--
-- COPY FROM ... DEFAULT