Skip to content

Commit 69f96e6

Browse files
author
Commitfest Bot
committed
[CF 4810] v19 - COPY ON_ERROR 'NULL'
This branch was automatically generated by a robot using patches from an email thread registered at: https://commitfest.postgresql.org/patch/4810 The branch will be overwritten each time a new patch version is posted to the thread, and also periodically to check for bitrot caused by changes on the master branch. Patch(es): https://www.postgresql.org/message-id/CACJufxF0c3k5O8up9NOY-m02nyJ0f6N1tKxZwjCewTqvvFmbLw@mail.gmail.com Author(s): jian he
2 parents a95e3d8 + ed06bd2 commit 69f96e6

File tree

9 files changed

+247
-36
lines changed

9 files changed

+247
-36
lines changed

doc/src/sgml/ref/copy.sgml

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -412,23 +412,37 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
412412
Specifies how to behave when encountering an error converting a column's
413413
input value into its data type.
414414
An <replaceable class="parameter">error_action</replaceable> value of
415-
<literal>stop</literal> means fail the command, while
416-
<literal>ignore</literal> means discard the input row and continue with the next one.
415+
<literal>stop</literal> means fail the command,
416+
<literal>ignore</literal> means discard the input row and continue with the next one,
417+
and <literal>set_null</literal> means replace column containing invalid
418+
input value with <literal>NULL</literal> and move to the next field.
417419
The default is <literal>stop</literal>.
418420
</para>
419421
<para>
420-
The <literal>ignore</literal> option is applicable only for <command>COPY FROM</command>
422+
The <literal>ignore</literal> and <literal>set_null</literal>
423+
options are applicable only for <command>COPY FROM</command>
421424
when the <literal>FORMAT</literal> is <literal>text</literal> or <literal>csv</literal>.
422425
</para>
426+
<para>
427+
For <literal>ignore</literal> option, a <literal>NOTICE</literal> message
428+
containing the ignored row count is emitted at the end of the <command>COPY
429+
FROM</command> if at least one row was discarded.
430+
For <literal>set_null</literal> option,
431+
a <literal>NOTICE</literal> message indicating the number of rows
432+
where invalid input values were replaced with null is emitted
433+
at the end of the <command>COPY FROM</command> if at least one row was replaced.
434+
</para>
423435
<para>
424-
A <literal>NOTICE</literal> message containing the ignored row count is
425-
emitted at the end of the <command>COPY FROM</command> if at least one
426-
row was discarded. When <literal>LOG_VERBOSITY</literal> option is set to
427-
<literal>verbose</literal>, a <literal>NOTICE</literal> message
436+
When <literal>LOG_VERBOSITY</literal> option is set to <literal>verbose</literal>,
437+
for <literal>ignore</literal> option, a <literal>NOTICE</literal> message
428438
containing the line of the input file and the column name whose input
429-
conversion has failed is emitted for each discarded row.
439+
conversion has failed is emitted for each discarded row;
440+
for <literal>set_null</literal> option, a <literal>NOTICE</literal>
441+
message containing the line of the input file and the column name where
442+
value was replaced with <literal>NULL</literal> for each input conversion
443+
failure.
430444
When it is set to <literal>silent</literal>, no message is emitted
431-
regarding ignored rows.
445+
regarding input conversion failed rows.
432446
</para>
433447
</listitem>
434448
</varlistentry>
@@ -476,7 +490,8 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
476490
</para>
477491
<para>
478492
This is currently used in <command>COPY FROM</command> command when
479-
<literal>ON_ERROR</literal> option is set to <literal>ignore</literal>.
493+
<literal>ON_ERROR</literal> option is set to <literal>ignore</literal>
494+
or <literal>set_null</literal>.
480495
</para>
481496
</listitem>
482497
</varlistentry>

src/backend/commands/copy.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,12 +417,14 @@ defGetCopyOnErrorChoice(DefElem *def, ParseState *pstate, bool is_from)
417417
parser_errposition(pstate, def->location)));
418418

419419
/*
420-
* Allow "stop", or "ignore" values.
420+
* Allow "stop", "ignore", "set_null" values.
421421
*/
422422
if (pg_strcasecmp(sval, "stop") == 0)
423423
return COPY_ON_ERROR_STOP;
424424
if (pg_strcasecmp(sval, "ignore") == 0)
425425
return COPY_ON_ERROR_IGNORE;
426+
if (pg_strcasecmp(sval, "set_null") == 0)
427+
return COPY_ON_ERROR_SET_NULL;
426428

427429
ereport(ERROR,
428430
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -932,7 +934,7 @@ ProcessCopyOptions(ParseState *pstate,
932934
(errcode(ERRCODE_SYNTAX_ERROR),
933935
errmsg("only ON_ERROR STOP is allowed in BINARY mode")));
934936

935-
if (opts_out->reject_limit && !opts_out->on_error)
937+
if (opts_out->reject_limit && opts_out->on_error != COPY_ON_ERROR_IGNORE)
936938
ereport(ERROR,
937939
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
938940
/*- translator: first and second %s are the names of COPY option, e.g.

src/backend/commands/copyfrom.c

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,14 +1467,22 @@ CopyFrom(CopyFromState cstate)
14671467
/* Done, clean up */
14681468
error_context_stack = errcallback.previous;
14691469

1470-
if (cstate->opts.on_error != COPY_ON_ERROR_STOP &&
1471-
cstate->num_errors > 0 &&
1470+
if (cstate->num_errors > 0 &&
14721471
cstate->opts.log_verbosity >= COPY_LOG_VERBOSITY_DEFAULT)
1473-
ereport(NOTICE,
1474-
errmsg_plural("%" PRIu64 " row was skipped due to data type incompatibility",
1475-
"%" PRIu64 " rows were skipped due to data type incompatibility",
1476-
cstate->num_errors,
1477-
cstate->num_errors));
1472+
{
1473+
if (cstate->opts.on_error == COPY_ON_ERROR_IGNORE)
1474+
ereport(NOTICE,
1475+
errmsg_plural("%" PRIu64 " row was skipped due to data type incompatibility",
1476+
"%" PRIu64 " rows were skipped due to data type incompatibility",
1477+
cstate->num_errors,
1478+
cstate->num_errors));
1479+
else if (cstate->opts.on_error == COPY_ON_ERROR_SET_NULL)
1480+
ereport(NOTICE,
1481+
errmsg_plural("invalid values in %" PRIu64 " row was replaced with null due to data type incompatibility",
1482+
"invalid values in %" PRIu64 " rows were replaced with null due to data type incompatibility",
1483+
cstate->num_errors,
1484+
cstate->num_errors));
1485+
}
14781486

14791487
if (bistate != NULL)
14801488
FreeBulkInsertState(bistate);
@@ -1614,6 +1622,19 @@ BeginCopyFrom(ParseState *pstate,
16141622
}
16151623
}
16161624

1625+
if (cstate->opts.on_error == COPY_ON_ERROR_SET_NULL)
1626+
{
1627+
int attr_count = list_length(cstate->attnumlist);
1628+
1629+
cstate->domain_with_constraint = (bool *) palloc0(attr_count * sizeof(bool));
1630+
foreach_int(attno, cstate->attnumlist)
1631+
{
1632+
int i = foreach_current_index(attno);
1633+
Form_pg_attribute att = TupleDescAttr(tupDesc, attno - 1);
1634+
cstate->domain_with_constraint[i] = DomainHasConstraints(att->atttypid);
1635+
}
1636+
}
1637+
16171638
/* Set up soft error handler for ON_ERROR */
16181639
if (cstate->opts.on_error != COPY_ON_ERROR_STOP)
16191640
{
@@ -1622,10 +1643,11 @@ BeginCopyFrom(ParseState *pstate,
16221643
cstate->escontext->error_occurred = false;
16231644

16241645
/*
1625-
* Currently we only support COPY_ON_ERROR_IGNORE. We'll add other
1626-
* options later
1646+
* Currently we only support COPY_ON_ERROR_IGNORE, COPY_ON_ERROR_SET_NULL.
1647+
* We'll add other options later
16271648
*/
1628-
if (cstate->opts.on_error == COPY_ON_ERROR_IGNORE)
1649+
if (cstate->opts.on_error == COPY_ON_ERROR_IGNORE ||
1650+
cstate->opts.on_error == COPY_ON_ERROR_SET_NULL)
16291651
cstate->escontext->details_wanted = false;
16301652
}
16311653
else

src/backend/commands/copyfromparse.c

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,7 @@ CopyFromTextLikeOneRow(CopyFromState cstate, ExprContext *econtext,
956956
int fldct;
957957
int fieldno;
958958
char *string;
959+
bool current_row_erroneous = false;
959960

960961
tupDesc = RelationGetDescr(cstate->rel);
961962
attr_count = list_length(cstate->attnumlist);
@@ -1033,7 +1034,8 @@ CopyFromTextLikeOneRow(CopyFromState cstate, ExprContext *econtext,
10331034
}
10341035

10351036
/*
1036-
* If ON_ERROR is specified with IGNORE, skip rows with soft errors
1037+
* If ON_ERROR is specified with IGNORE, skip rows with soft errors.
1038+
* If ON_ERROR is specified with SET_NULL, try to replace with null.
10371039
*/
10381040
else if (!InputFunctionCallSafe(&in_functions[m],
10391041
string,
@@ -1044,7 +1046,50 @@ CopyFromTextLikeOneRow(CopyFromState cstate, ExprContext *econtext,
10441046
{
10451047
Assert(cstate->opts.on_error != COPY_ON_ERROR_STOP);
10461048

1047-
cstate->num_errors++;
1049+
if (cstate->opts.on_error == COPY_ON_ERROR_IGNORE)
1050+
cstate->num_errors++;
1051+
else if (cstate->opts.on_error == COPY_ON_ERROR_SET_NULL)
1052+
{
1053+
cstate->escontext->error_occurred = false;
1054+
Assert(cstate->domain_with_constraint != NULL);
1055+
1056+
/*
1057+
* If the column type is a domain with constraints, an
1058+
* additional InputFunctionCallSafe may be needed to raise
1059+
* errors for domain constraint violations.
1060+
*/
1061+
if (!cstate->domain_with_constraint[m] ||
1062+
InputFunctionCallSafe(&in_functions[m],
1063+
NULL,
1064+
typioparams[m],
1065+
att->atttypmod,
1066+
(Node *) cstate->escontext,
1067+
&values[m]))
1068+
{
1069+
nulls[m] = true;
1070+
values[m] = (Datum) 0;
1071+
}
1072+
else if (string == NULL)
1073+
ereport(ERROR,
1074+
errcode(ERRCODE_NOT_NULL_VIOLATION),
1075+
errmsg("domain %s does not allow null values", format_type_be(typioparams[m])),
1076+
errdatatype(typioparams[m]));
1077+
else
1078+
ereport(ERROR,
1079+
errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1080+
errmsg("invalid input value for domain %s: \"%s\"",
1081+
format_type_be(typioparams[m]), string));
1082+
1083+
/*
1084+
* We count only the number of rows (not individual fields)
1085+
* where ON_ERROR SET_NULL was successfully applied.
1086+
*/
1087+
if (!current_row_erroneous)
1088+
{
1089+
current_row_erroneous = true;
1090+
cstate->num_errors++;
1091+
}
1092+
}
10481093

10491094
if (cstate->opts.log_verbosity == COPY_LOG_VERBOSITY_VERBOSE)
10501095
{
@@ -1061,24 +1106,37 @@ CopyFromTextLikeOneRow(CopyFromState cstate, ExprContext *econtext,
10611106
char *attval;
10621107

10631108
attval = CopyLimitPrintoutLength(cstate->cur_attval);
1064-
ereport(NOTICE,
1065-
errmsg("skipping row due to data type incompatibility at line %" PRIu64 " for column \"%s\": \"%s\"",
1066-
cstate->cur_lineno,
1067-
cstate->cur_attname,
1068-
attval));
1109+
1110+
if (cstate->opts.on_error == COPY_ON_ERROR_IGNORE)
1111+
ereport(NOTICE,
1112+
errmsg("skipping row due to data type incompatibility at line %" PRIu64 " for column \"%s\": \"%s\"",
1113+
cstate->cur_lineno,
1114+
cstate->cur_attname,
1115+
attval));
1116+
else if (cstate->opts.on_error == COPY_ON_ERROR_SET_NULL)
1117+
ereport(NOTICE,
1118+
errmsg("setting to null due to data type incompatibility at line %" PRIu64 " for column \"%s\": \"%s\"",
1119+
cstate->cur_lineno,
1120+
cstate->cur_attname,
1121+
attval));
10691122
pfree(attval);
10701123
}
10711124
else
1072-
ereport(NOTICE,
1073-
errmsg("skipping row due to data type incompatibility at line %" PRIu64 " for column \"%s\": null input",
1074-
cstate->cur_lineno,
1075-
cstate->cur_attname));
1076-
1125+
{
1126+
if (cstate->opts.on_error == COPY_ON_ERROR_IGNORE)
1127+
ereport(NOTICE,
1128+
errmsg("skipping row due to data type incompatibility at line %" PRIu64 " for column \"%s\": null input",
1129+
cstate->cur_lineno,
1130+
cstate->cur_attname));
1131+
}
10771132
/* reset relname_only */
10781133
cstate->relname_only = false;
10791134
}
10801135

1081-
return true;
1136+
if (cstate->opts.on_error == COPY_ON_ERROR_IGNORE)
1137+
return true;
1138+
else if (cstate->opts.on_error == COPY_ON_ERROR_SET_NULL)
1139+
continue;
10821140
}
10831141

10841142
cstate->cur_attname = NULL;

src/bin/psql/tab-complete.in.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3353,7 +3353,7 @@ match_previous_words(int pattern_id,
33533353

33543354
/* Complete COPY <sth> FROM filename WITH (ON_ERROR */
33553355
else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAny, "WITH", "(", "ON_ERROR"))
3356-
COMPLETE_WITH("stop", "ignore");
3356+
COMPLETE_WITH("stop", "ignore", "set_null");
33573357

33583358
/* Complete COPY <sth> FROM filename WITH (LOG_VERBOSITY */
33593359
else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAny, "WITH", "(", "LOG_VERBOSITY"))

src/include/commands/copy.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ typedef enum CopyOnErrorChoice
3535
{
3636
COPY_ON_ERROR_STOP = 0, /* immediately throw errors, default */
3737
COPY_ON_ERROR_IGNORE, /* ignore errors */
38+
COPY_ON_ERROR_SET_NULL, /* set error field to null */
3839
} CopyOnErrorChoice;
3940

4041
/*

src/include/commands/copyfrom_internal.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ typedef struct CopyFromStateData
108108
* att */
109109
bool *defaults; /* if DEFAULT marker was found for
110110
* corresponding att */
111+
/*
112+
* Set to true if the corresponding attribute's data type is a domain with
113+
* constraints. This field is usually NULL, except when ON_ERROR is set to
114+
* SET_NULL.
115+
*/
116+
bool *domain_with_constraint;
117+
111118
bool volatile_defexprs; /* is any of defexprs volatile? */
112119
List *range_table; /* single element list of RangeTblEntry */
113120
List *rteperminfos; /* single element list of RTEPermissionInfo */

src/test/regress/expected/copy2.out

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ COPY x from stdin (on_error ignore, on_error ignore);
8181
ERROR: conflicting or redundant options
8282
LINE 1: COPY x from stdin (on_error ignore, on_error ignore);
8383
^
84+
COPY x from stdin (on_error set_null, on_error ignore);
85+
ERROR: conflicting or redundant options
86+
LINE 1: COPY x from stdin (on_error set_null, on_error ignore);
87+
^
8488
COPY x from stdin (log_verbosity default, log_verbosity verbose);
8589
ERROR: conflicting or redundant options
8690
LINE 1: COPY x from stdin (log_verbosity default, log_verbosity verb...
@@ -92,6 +96,10 @@ COPY x from stdin (format BINARY, null 'x');
9296
ERROR: cannot specify NULL in BINARY mode
9397
COPY x from stdin (format BINARY, on_error ignore);
9498
ERROR: only ON_ERROR STOP is allowed in BINARY mode
99+
COPY x from stdin (format BINARY, on_error set_null);
100+
ERROR: only ON_ERROR STOP is allowed in BINARY mode
101+
COPY x from stdin (on_error set_null, reject_limit 2);
102+
ERROR: COPY REJECT_LIMIT requires ON_ERROR to be set to IGNORE
95103
COPY x from stdin (on_error unsupported);
96104
ERROR: COPY ON_ERROR "unsupported" not recognized
97105
LINE 1: COPY x from stdin (on_error unsupported);
@@ -124,6 +132,10 @@ COPY x to stdout (format BINARY, on_error unsupported);
124132
ERROR: COPY ON_ERROR cannot be used with COPY TO
125133
LINE 1: COPY x to stdout (format BINARY, on_error unsupported);
126134
^
135+
COPY x to stdout (on_error set_null);
136+
ERROR: COPY ON_ERROR cannot be used with COPY TO
137+
LINE 1: COPY x to stdout (on_error set_null);
138+
^
127139
COPY x from stdin (log_verbosity unsupported);
128140
ERROR: COPY LOG_VERBOSITY "unsupported" not recognized
129141
LINE 1: COPY x from stdin (log_verbosity unsupported);
@@ -776,6 +788,51 @@ CONTEXT: COPY check_ign_err
776788
NOTICE: skipping row due to data type incompatibility at line 8 for column "k": "a"
777789
CONTEXT: COPY check_ign_err
778790
NOTICE: 6 rows were skipped due to data type incompatibility
791+
CREATE DOMAIN d_int_not_null AS INT NOT NULL CHECK(value > 0);
792+
CREATE DOMAIN d_int_positive_maybe_null AS INT CHECK(value > 0);
793+
CREATE TABLE t_on_error_null (a d_int_not_null, b d_int_positive_maybe_null, c INT);
794+
\pset null NULL
795+
--fail, column a cannot set to null value
796+
COPY t_on_error_null FROM STDIN WITH (on_error set_null);
797+
ERROR: domain d_int_not_null does not allow null values
798+
CONTEXT: COPY t_on_error_null, line 1, column a: null input
799+
--fail, column a is domain with not-null constraint
800+
COPY t_on_error_null FROM STDIN WITH (on_error set_null);
801+
ERROR: invalid input value for domain d_int_not_null: "ss"
802+
CONTEXT: COPY t_on_error_null, line 1, column a: "ss"
803+
--fail, column a cannot set to null value
804+
COPY t_on_error_null FROM STDIN WITH (on_error set_null);
805+
ERROR: invalid input value for domain d_int_not_null: "-1"
806+
CONTEXT: COPY t_on_error_null, line 1, column a: "-1"
807+
--fail. less data
808+
COPY t_on_error_null FROM STDIN WITH (delimiter ',', on_error set_null);
809+
ERROR: missing data for column "c"
810+
CONTEXT: COPY t_on_error_null, line 1: "1,1"
811+
--fail. extra data
812+
COPY t_on_error_null FROM STDIN WITH (delimiter ',', on_error set_null);
813+
ERROR: extra data after last expected column
814+
CONTEXT: COPY t_on_error_null, line 1: "1,2,3,4"
815+
--ok
816+
COPY t_on_error_null FROM STDIN WITH (on_error set_null, log_verbosity verbose);
817+
NOTICE: setting to null due to data type incompatibility at line 1 for column "b": "x1"
818+
CONTEXT: COPY t_on_error_null
819+
NOTICE: setting to null due to data type incompatibility at line 1 for column "c": "yx"
820+
CONTEXT: COPY t_on_error_null
821+
NOTICE: setting to null due to data type incompatibility at line 2 for column "b": "zx"
822+
CONTEXT: COPY t_on_error_null
823+
NOTICE: setting to null due to data type incompatibility at line 3 for column "c": "ea"
824+
CONTEXT: COPY t_on_error_null
825+
NOTICE: invalid values in 3 rows were replaced with null due to data type incompatibility
826+
-- check inserted content
827+
select * from t_on_error_null;
828+
a | b | c
829+
----+------+------
830+
10 | NULL | NULL
831+
11 | NULL | 12
832+
13 | 14 | NULL
833+
(3 rows)
834+
835+
\pset null ''
779836
-- tests for on_error option with log_verbosity and null constraint via domain
780837
CREATE DOMAIN dcheck_ign_err2 varchar(15) NOT NULL;
781838
CREATE TABLE check_ign_err2 (n int, m int[], k int, l dcheck_ign_err2);
@@ -835,6 +892,9 @@ DROP VIEW instead_of_insert_tbl_view;
835892
DROP VIEW instead_of_insert_tbl_view_2;
836893
DROP FUNCTION fun_instead_of_insert_tbl();
837894
DROP TABLE check_ign_err;
895+
DROP TABLE t_on_error_null;
896+
DROP DOMAIN d_int_not_null;
897+
DROP DOMAIN d_int_positive_maybe_null;
838898
DROP TABLE check_ign_err2;
839899
DROP DOMAIN dcheck_ign_err2;
840900
DROP TABLE hard_err;

0 commit comments

Comments
 (0)