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