=== Applying patches on top of PostgreSQL commit ID a6483f5ac9680801da0c8ad207c2870c0d6a61c2 === /etc/rc.d/jail: WARNING: Per-jail configuration via jail_* variables is obsolete. Please consider migrating to /etc/jail.conf. Thu Mar 5 19:18:24 UTC 2026 On branch cf/4817 nothing to commit, working tree clean === using 'git am' to apply patch ./v7-0001-COPY-FROM-on_error-table.patch === Applying: COPY FROM (on_error table) Using index info to reconstruct a base tree... M doc/src/sgml/datatype.sgml M doc/src/sgml/ref/copy.sgml M src/backend/catalog/system_functions.sql M src/backend/commands/copy.c M src/backend/commands/copyfrom.c M src/backend/commands/copyfromparse.c M src/backend/parser/gram.y M src/include/commands/copy.h M src/include/commands/copyfrom_internal.h M src/test/regress/expected/copy2.out M src/test/regress/sql/copy2.sql Falling back to patching base and 3-way merge... Auto-merging src/test/regress/sql/copy2.sql Auto-merging src/test/regress/expected/copy2.out Auto-merging src/include/commands/copyfrom_internal.h Auto-merging src/include/commands/copy.h CONFLICT (content): Merge conflict in src/include/commands/copy.h Auto-merging src/backend/parser/gram.y Auto-merging src/backend/commands/copyfromparse.c CONFLICT (content): Merge conflict in src/backend/commands/copyfromparse.c Auto-merging src/backend/commands/copyfrom.c CONFLICT (content): Merge conflict in src/backend/commands/copyfrom.c Auto-merging src/backend/commands/copy.c CONFLICT (content): Merge conflict in src/backend/commands/copy.c Auto-merging src/backend/catalog/system_functions.sql Auto-merging doc/src/sgml/ref/copy.sgml CONFLICT (content): Merge conflict in doc/src/sgml/ref/copy.sgml Auto-merging doc/src/sgml/datatype.sgml error: Failed to merge in the changes. hint: Use 'git am --show-current-patch=diff' to see the failed patch Patch failed at 0001 COPY FROM (on_error table) When you have resolved this problem, run "git am --continue". If you prefer to skip this patch, run "git am --skip" instead. To restore the original branch and stop patching, run "git am --abort". Unstaged changes after reset: M doc/src/sgml/datatype.sgml M doc/src/sgml/ref/copy.sgml M src/backend/catalog/system_functions.sql M src/backend/commands/copy.c M src/backend/commands/copyfrom.c M src/backend/commands/copyfromparse.c M src/backend/parser/gram.y M src/include/commands/copy.h M src/include/commands/copyfrom_internal.h M src/test/regress/expected/copy2.out M src/test/regress/sql/copy2.sql === using patch(1) to apply patch ./v7-0001-COPY-FROM-on_error-table.patch === patching file doc/src/sgml/datatype.sgml Hunk #1 succeeded at 5105 (offset 13 lines). patching file doc/src/sgml/ref/copy.sgml Hunk #2 FAILED at 414. Hunk #3 FAILED at 482. 2 out of 3 hunks FAILED -- saving rejects to file doc/src/sgml/ref/copy.sgml.rej patching file src/backend/catalog/system_functions.sql Hunk #1 succeeded at 534 (offset -279 lines). patching file src/backend/commands/copy.c Hunk #1 FAILED at 417. Hunk #2 succeeded at 567 (offset 51 lines). Hunk #3 succeeded at 743 (offset 51 lines). Hunk #4 succeeded at 758 (offset 51 lines). 1 out of 4 hunks FAILED -- saving rejects to file src/backend/commands/copy.c.rej patching file src/backend/commands/copyfrom.c Hunk #2 FAILED at 46. Hunk #3 succeeded at 1176 (offset 1 line). Hunk #4 FAILED at 1487. Hunk #5 succeeded at 1534 (offset 5 lines). Hunk #6 FAILED at 1645. 3 out of 6 hunks FAILED -- saving rejects to file src/backend/commands/copyfrom.c.rej patching file src/backend/commands/copyfromparse.c Hunk #2 FAILED at 1034. Hunk #3 FAILED at 1045. 2 out of 3 hunks FAILED -- saving rejects to file src/backend/commands/copyfromparse.c.rej patching file src/backend/parser/gram.y Hunk #1 succeeded at 3701 (offset 76 lines). patching file src/include/commands/copy.h Hunk #1 FAILED at 35. Hunk #2 succeeded at 85 (offset 1 line). 1 out of 2 hunks FAILED -- saving rejects to file src/include/commands/copy.h.rej patching file src/include/commands/copyfrom_internal.h patching file src/test/regress/expected/copy2.out Hunk #1 succeeded at 881 (offset 61 lines). Hunk #2 succeeded at 987 (offset 64 lines). patching file src/test/regress/sql/copy2.sql Hunk #1 succeeded at 634 (offset 43 lines). Hunk #2 succeeded at 726 (offset 46 lines). Unstaged changes after reset: M doc/src/sgml/datatype.sgml M doc/src/sgml/ref/copy.sgml M src/backend/catalog/system_functions.sql M src/backend/commands/copy.c M src/backend/commands/copyfrom.c M src/backend/commands/copyfromparse.c M src/backend/parser/gram.y M src/include/commands/copy.h M src/include/commands/copyfrom_internal.h M src/test/regress/expected/copy2.out M src/test/regress/sql/copy2.sql Removing doc/src/sgml/ref/copy.sgml.rej Removing src/backend/commands/copy.c.rej Removing src/backend/commands/copyfrom.c.rej Removing src/backend/commands/copyfromparse.c.rej Removing src/include/commands/copy.h.rej === using 'git apply' to apply patch ./v7-0001-COPY-FROM-on_error-table.patch === Applied patch to 'doc/src/sgml/datatype.sgml' cleanly. Applied patch to 'doc/src/sgml/ref/copy.sgml' with conflicts. Applied patch to 'src/backend/catalog/system_functions.sql' cleanly. Applied patch to 'src/backend/commands/copy.c' with conflicts. Applied patch to 'src/backend/commands/copyfrom.c' with conflicts. Applied patch to 'src/backend/commands/copyfromparse.c' with conflicts. Applied patch to 'src/backend/parser/gram.y' cleanly. Applied patch to 'src/include/commands/copy.h' with conflicts. Applied patch to 'src/include/commands/copyfrom_internal.h' cleanly. Applied patch to 'src/test/regress/expected/copy2.out' cleanly. Applied patch to 'src/test/regress/sql/copy2.sql' cleanly. U doc/src/sgml/ref/copy.sgml U src/backend/commands/copy.c U src/backend/commands/copyfrom.c U src/backend/commands/copyfromparse.c U src/include/commands/copy.h diff --cc doc/src/sgml/ref/copy.sgml index 0ad890ef95f,0215aac0771..00000000000 --- a/doc/src/sgml/ref/copy.sgml +++ b/doc/src/sgml/ref/copy.sgml @@@ -414,32 -415,30 +415,55 @@@ COPY { t An error_action value of stop means fail the command, while ignore means discard the input row and continue with the next one, ++<<<<<<< ours + and set_null means replace the field containing the invalid + input value with a null value and continue to the next field. + The default is stop. + + + The ignore and set_null + options are applicable only for COPY FROM + when the FORMAT is text or csv. + + + If ON_ERROR is set to ignore or + set_null, a NOTICE message is emitted at the end of the + COPY FROM command containing the count of rows that were ignored or + changed, if at least one row was affected. + + + When LOG_VERBOSITY option is set to verbose, + for ignore option, a NOTICE message ++======= + table means save error information to error_saving_table + and continue with the next one. + The default is stop. + + + The ignore and table option are applicable only for COPY FROM + when the FORMAT is text or csv. + + + If ON_ERROR option is set to table, + a NOTICE message containing the row count saved to + error_saving_table is + emitted at the end of the COPY FROM. + + + + If ON_ERROR option is set to 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 ++>>>>>>> theirs containing the line of the input file and the column name whose input - conversion has failed is emitted for each discarded row. + conversion has failed is emitted for each discarded row; + for set_null option, a NOTICE + message containing the line of the input file and the column name where + value was replaced with NULL for each input conversion + failure. When it is set to silent, no message is emitted - regarding ignored rows. + regarding input conversion failed rows. @@@ -493,7 -491,23 +517,27 @@@ ++<<<<<<< ours + ++======= + + TABLE + + + Save error context details to the specified table: error_saving_table. + This option is allowed only in COPY FROM and + ON_ERROR is specified with TABLE. + It also require the current COPY FROM user have INSERT privileges on all columns + of the error_saving_table. + The error_saving_table must + be a typed table derived from composite type copy_error_saving. + + + + + ++>>>>>>> theirs WHERE diff --cc src/backend/commands/copy.c index 63b86802ba2,c896fc67529..00000000000 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@@ -468,13 -416,17 +468,22 @@@ defGetCopyOnErrorChoice(DefElem *def, P errmsg("COPY %s cannot be used with %s", "ON_ERROR", "COPY TO"), parser_errposition(pstate, def->location))); ++<<<<<<< ours ++======= + /* + * Allow "stop", "ignore" or "table" values. + */ ++>>>>>>> theirs if (pg_strcasecmp(sval, "stop") == 0) return COPY_ON_ERROR_STOP; if (pg_strcasecmp(sval, "ignore") == 0) return COPY_ON_ERROR_IGNORE; + if (pg_strcasecmp(sval, "set_null") == 0) + return COPY_ON_ERROR_SET_NULL; + 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 */ diff --cc src/backend/commands/copyfrom.c index 2f42f55e229,629bdfb4b38..00000000000 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@@ -49,8 -51,9 +51,13 @@@ #include "utils/memutils.h" #include "utils/portal.h" #include "utils/rel.h" + #include "utils/regproc.h" #include "utils/snapmgr.h" ++<<<<<<< ours +#include "utils/typcache.h" ++======= + #include "utils/syscache.h" ++>>>>>>> theirs /* * No more than this many tuples per CopyMultiInsertBuffer @@@ -1473,12 -1499,13 +1499,22 @@@ CopyFrom(CopyFromState cstate "%" PRIu64 " rows were skipped due to data type incompatibility", cstate->num_errors, cstate->num_errors)); ++<<<<<<< ours + else if (cstate->opts.on_error == COPY_ON_ERROR_SET_NULL) + ereport(NOTICE, + errmsg_plural("in %" PRIu64 " row, columns were set to null due to data type incompatibility", + "in %" PRIu64 " rows, columns were set to null 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))); ++>>>>>>> theirs } if (bistate != NULL) @@@ -1626,35 -1656,108 +1665,138 @@@ BeginCopyFrom(ParseState *pstate cstate->escontext->type = T_ErrorSaveContext; cstate->escontext->error_occurred = false; ++<<<<<<< ours + if (cstate->opts.on_error == COPY_ON_ERROR_IGNORE || + cstate->opts.on_error == COPY_ON_ERROR_SET_NULL) ++======= + /* + * 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) ++>>>>>>> theirs cstate->escontext->details_wanted = false; + else if (cstate->opts.on_error == COPY_ON_ERROR_TABLE) + cstate->escontext->details_wanted = true; } else cstate->escontext = NULL; ++<<<<<<< ours + if (cstate->opts.on_error == COPY_ON_ERROR_SET_NULL) + { + int attr_count = list_length(cstate->attnumlist); + + /* + * When data type conversion fails and ON_ERROR is SET_NULL, we need + * ensure that the input column allow null values. ExecConstraints() + * will cover most of the cases, but it does not verify domain + * constraints. Therefore, for constrained domains, the null value + * check must be performed during the initial string-to-datum + * conversion (see CopyFromTextLikeOneRow()). + */ + cstate->domain_with_constraint = palloc0_array(bool, attr_count); + + foreach_int(attno, cstate->attnumlist) + { + int i = foreach_current_index(attno); + + Form_pg_attribute att = TupleDescAttr(tupDesc, attno - 1); + + cstate->domain_with_constraint[i] = DomainHasConstraints(att->atttypid); + } ++======= + if (cstate->opts.on_error == COPY_ON_ERROR_TABLE) + { + Oid reloftype; + Oid typoid; + Oid err_tbl_oid; + Datum value; + RangeVar *relvar; + List *relname_list; + HeapTuple tp; + + Assert(cstate->opts.on_error_tbl != NULL); + + relname_list = stringToQualifiedNameList(cstate->opts.on_error_tbl, NULL); + relvar = makeRangeVarFromNameList(relname_list); + + /* + * We might insert tuples into the error-saving table later, so we first + * need to check its lock status. If it is already heavily locked, our + * subsequent COPY FROM may stuck. Instead of letting COPY FROM hang, + * report an error indicating that the error-saving table is under heavy + * lock. + */ + err_tbl_oid = RangeVarGetRelidExtended(relvar, + RowExclusiveLock, + RVR_NOWAIT, + NULL, + NULL); + + 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))); + + cstate->error_saving = table_open(err_tbl_oid, NoLock); + + /* The user should have INSERT privilege on error_saving table */ + value = DirectFunctionCall3(has_table_privilege_id_id, + ObjectIdGetDatum(GetUserId()), + ObjectIdGetDatum(err_tbl_oid), + CStringGetTextDatum("INSERT")); + if (!DatumGetBool(value)) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to use table \"%s\" for COPY FROM error saving", + RelationGetRelationName(cstate->error_saving)), + errhint("Ensure the current user has INSERT privilege on table \"%s\" to use it for COPY FROM error saving.", + RelationGetRelationName(cstate->error_saving))); + + tp = SearchSysCache1(RELOID, ObjectIdGetDatum(err_tbl_oid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_class reltup = (Form_pg_class) GETSTRUCT(tp); + + reloftype = reltup->reloftype; + ReleaseSysCache(tp); + } + else + elog(ERROR, "cache lookup failed for relation %u", err_tbl_oid); + + if (!OidIsValid(reloftype)) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot use relation \"%s\" for COPY error saving", + cstate->opts.on_error_tbl), + errhint("The COPY error saving table must be a typed table base on type \"%s\".", + "copy_error_saving")); + + typoid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, + PointerGetDatum("copy_error_saving"), + ObjectIdGetDatum(PG_CATALOG_NAMESPACE)); + + if (reloftype != typoid) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot use relation \"%s\" for COPY error saving", + cstate->opts.on_error_tbl), + errdetail("relation \"%s\" is a typed table based on type \"%s\"", + cstate->opts.on_error_tbl, + format_type_be(reloftype)), + errhint("The COPY error saving table must be a typed table base on type \"%s\".", + format_type_be(typoid))); ++>>>>>>> theirs } /* Convert FORCE_NULL name list to per-column flags, check validity */ diff --cc src/backend/commands/copyfromparse.c index fbd13353efc,41bb179a9d9..00000000000 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@@ -1037,7 -1034,7 +1038,11 @@@ CopyFromTextLikeOneRow(CopyFromState cs } /* ++<<<<<<< ours + * If ON_ERROR is specified, handle the different options ++======= + * If ON_ERROR is specified with IGNORE or TABLE, skip rows with soft errors ++>>>>>>> theirs */ else if (!InputFunctionCallSafe(&in_functions[m], string, @@@ -1048,57 -1045,55 +1053,107 @@@ { Assert(cstate->opts.on_error != COPY_ON_ERROR_STOP); ++<<<<<<< ours + if (cstate->opts.on_error == COPY_ON_ERROR_IGNORE) + cstate->num_errors++; + else if (cstate->opts.on_error == COPY_ON_ERROR_SET_NULL) + { + /* + * Reset error state so the subsequent InputFunctionCallSafe + * call (for domain constraint check) can properly report + * whether it succeeded or failed. + */ + cstate->escontext->error_occurred = false; + + Assert(cstate->domain_with_constraint != NULL); + + /* + * For constrained domains, we need an additional + * InputFunctionCallSafe() to ensure that an error is thrown + * if the domain constraint rejects null values. + */ + if (!cstate->domain_with_constraint[m] || + InputFunctionCallSafe(&in_functions[m], + NULL, + typioparams[m], + att->atttypmod, + (Node *) cstate->escontext, + &values[m])) + { + nulls[m] = true; + values[m] = (Datum) 0; + } + else + ereport(ERROR, + errcode(ERRCODE_NOT_NULL_VIOLATION), + errmsg("domain %s does not allow null values", + format_type_be(typioparams[m])), + errdetail("ON_ERROR SET_NULL cannot be applied because column \"%s\" (domain %s) does not accept null values.", + cstate->cur_attname, + format_type_be(typioparams[m])), + errdatatype(typioparams[m])); + + /* + * We count only the number of rows (not fields) where + * ON_ERROR SET_NULL was applied. + */ + if (!current_row_erroneous) + { + current_row_erroneous = true; + cstate->num_errors++; + } + } ++======= + if (cstate->opts.on_error == COPY_ON_ERROR_TABLE) + { + /* + * We use ErrorSaveContext stored information to form a tuple + * and insert it to the specified error saving table. + */ + HeapTuple tuple; + TupleDesc tupdesc; + char *err_detail; + char *err_code; + Datum values[10] = {0}; + bool isnull[10] = {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 == NULL) + 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); + + tupdesc = RelationGetDescr(cstate->error_saving); + tuple = heap_form_tuple(tupdesc, values, isnull); + + simple_heap_insert(cstate->error_saving, tuple); + + heap_freetuple(tuple); + } + + cstate->num_errors++; ++>>>>>>> theirs - 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 --cc src/include/commands/copy.h index 877202af67b,80f12af600a..00000000000 --- a/src/include/commands/copy.h +++ b/src/include/commands/copy.h @@@ -35,7 -35,7 +35,11 @@@ typedef enum CopyOnErrorChoic { COPY_ON_ERROR_STOP = 0, /* immediately throw errors, default */ COPY_ON_ERROR_IGNORE, /* ignore errors */ ++<<<<<<< ours + COPY_ON_ERROR_SET_NULL, /* set error field to null */ ++======= + COPY_ON_ERROR_TABLE, /* saving errors info to table */ ++>>>>>>> theirs } CopyOnErrorChoice; /*