Skip to content

Commit d6be317

Browse files
jianhe-funCommitfest Bot
authored and
Commitfest Bot
committed
COPY FROM (on_error table)
the syntax is {on_error table, table error_saving_tbl}. we first check table error_saving_tbl's existence and data definition. if it does not meet our criteria, then we quickly error out. we also did preliminary check the lock of error saving table so the insert to error saving table won't stuck. once there is a error happened, we save the error metedata and insert it to the error_saving_table. and continue to the next row. That means for one row, we can only catch the first field that have errors. discussion: https://postgr.es/m/CACJufxH_OJpVra%3D0c4ow8fbxHj7heMcVaTNEPa5vAurSeNA-6Q%40mail.gmail.com context: https://www.postgresql.org/message-id/752672.1699474336%40sss.pgh.pa.us commitfest: https://commitfest.postgresql.org/51/4817/
1 parent f8c115a commit d6be317

File tree

9 files changed

+582
-13
lines changed

9 files changed

+582
-13
lines changed

doc/src/sgml/ref/copy.sgml

+116-3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
4444
FORCE_NOT_NULL { ( <replaceable class="parameter">column_name</replaceable> [, ...] ) | * }
4545
FORCE_NULL { ( <replaceable class="parameter">column_name</replaceable> [, ...] ) | * }
4646
ON_ERROR <replaceable class="parameter">error_action</replaceable>
47+
TABLE <replaceable class="parameter">error_saving_table</replaceable>
4748
REJECT_LIMIT <replaceable class="parameter">maxerror</replaceable>
4849
ENCODING '<replaceable class="parameter">encoding_name</replaceable>'
4950
LOG_VERBOSITY <replaceable class="parameter">verbosity</replaceable>
@@ -395,15 +396,25 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
395396
input value into its data type.
396397
An <replaceable class="parameter">error_action</replaceable> value of
397398
<literal>stop</literal> means fail the command, while
398-
<literal>ignore</literal> means discard the input row and continue with the next one.
399+
<literal>ignore</literal> means discard the input row and continue with the next one,
400+
<literal>table</literal> means save error details to <replaceable class="parameter">error_saving_table</replaceable>
401+
and continue with the next one.
399402
The default is <literal>stop</literal>.
400403
</para>
401404
<para>
402-
The <literal>ignore</literal> option is applicable only for <command>COPY FROM</command>
405+
The <literal>ignore</literal> and <literal>table</literal> option are applicable only for <command>COPY FROM</command>
403406
when the <literal>FORMAT</literal> is <literal>text</literal> or <literal>csv</literal>.
404407
</para>
405408
<para>
406-
A <literal>NOTICE</literal> message containing the ignored row count is
409+
If <literal>ON_ERROR</literal>=<literal>table</literal>,
410+
a <literal>NOTICE</literal> message containing the row count that is saved to
411+
<replaceable class="parameter">error_saving_table</replaceable> is
412+
emitted at the end of the <command>COPY FROM</command> if at least one
413+
row was saved.
414+
</para>
415+
416+
<para>
417+
If <literal>ON_ERROR</literal>=<literal>ignore</literal>, a <literal>NOTICE</literal> message containing the ignored row count is
407418
emitted at the end of the <command>COPY FROM</command> if at least one
408419
row was discarded. When <literal>LOG_VERBOSITY</literal> option is set to
409420
<literal>verbose</literal>, a <literal>NOTICE</literal> message
@@ -463,6 +474,108 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
463474
</listitem>
464475
</varlistentry>
465476

477+
<varlistentry>
478+
<term><literal>TABLE</literal></term>
479+
<listitem>
480+
<para>
481+
Save error context details to the table <replaceable class="parameter">error_saving_table</replaceable>.
482+
This option is allowed only in <command>COPY FROM</command> and
483+
<literal>ON_ERROR</literal> is specified with <literal>TABLE</literal>.
484+
It also require user have <literal>INSERT</literal> privileges on all columns
485+
in the <replaceable class="parameter">error_saving_table</replaceable>.
486+
</para>
487+
488+
<para>
489+
If table <replaceable class="parameter">error_saving_table</replaceable> does meet the following definition
490+
(column ordinal position should be the same as the below), an error will be raised.
491+
492+
<informaltable>
493+
<tgroup cols="3">
494+
<thead>
495+
<row>
496+
<entry>Column name</entry>
497+
<entry>Data type</entry>
498+
<entry>Description</entry>
499+
</row>
500+
</thead>
501+
502+
<tbody>
503+
<row>
504+
<entry> <literal>userid</literal> </entry>
505+
<entry><type>oid</type></entry>
506+
<entry>The user generated the error.
507+
Reference <link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.<structfield>oid</structfield>,
508+
however there is no hard dependency with catalog <literal>pg_authid</literal>.
509+
If the corresponding row on <literal>pg_authid</literal> is deleted, this value becomes stale.
510+
</entry>
511+
</row>
512+
513+
<row>
514+
<entry> <literal>copy_tbl</literal> </entry>
515+
<entry><type>oid</type></entry>
516+
<entry>The <command>COPY FROM</command> operation destination table oid.
517+
Reference <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>,
518+
however there is no hard dependency with catalog <literal>pg_class</literal>.
519+
If the corresponding row on <literal>pg_class</literal> is deleted, this value becomes stale.
520+
</entry>
521+
</row>
522+
523+
<row>
524+
<entry> <literal>filename</literal> </entry>
525+
<entry><type>text</type></entry>
526+
<entry>The path name of the <command>COPY FROM</command> input</entry>
527+
</row>
528+
529+
<row>
530+
<entry> <literal>lineno</literal> </entry>
531+
<entry><type>bigint</type></entry>
532+
<entry>Line number where the error occurred, counting from 1</entry>
533+
</row>
534+
535+
<row>
536+
<entry> <literal>line</literal> </entry>
537+
<entry><type>text</type></entry>
538+
<entry>Raw content of the error occurred line</entry>
539+
</row>
540+
541+
<row>
542+
<entry> <literal>colname</literal> </entry>
543+
<entry><type>text</type></entry>
544+
<entry>Field where the error occurred</entry>
545+
</row>
546+
547+
<row>
548+
<entry> <literal>raw_field_value</literal> </entry>
549+
<entry><type>text</type></entry>
550+
<entry>Raw content of the error occurred field</entry>
551+
</row>
552+
553+
<row>
554+
<entry> <literal>err_message </literal> </entry>
555+
<entry><type>text</type></entry>
556+
<entry>The error message</entry>
557+
</row>
558+
559+
<row>
560+
<entry> <literal>err_detail</literal> </entry>
561+
<entry><type>text</type></entry>
562+
<entry>Detailed error message </entry>
563+
</row>
564+
565+
<row>
566+
<entry> <literal>errorcode </literal> </entry>
567+
<entry><type>text</type></entry>
568+
<entry>The error code </entry>
569+
</row>
570+
571+
</tbody>
572+
</tgroup>
573+
</informaltable>
574+
575+
</para>
576+
</listitem>
577+
</varlistentry>
578+
466579
<varlistentry>
467580
<term><literal>WHERE</literal></term>
468581
<listitem>

src/backend/commands/copy.c

+29
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,8 @@ defGetCopyOnErrorChoice(DefElem *def, ParseState *pstate, bool is_from)
410410
if (pg_strcasecmp(sval, "ignore") == 0)
411411
return COPY_ON_ERROR_IGNORE;
412412

413+
if (pg_strcasecmp(sval, "table") == 0)
414+
return COPY_ON_ERROR_TABLE;
413415
ereport(ERROR,
414416
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
415417
/*- translator: first %s is the name of a COPY option, e.g. ON_ERROR */
@@ -502,6 +504,7 @@ ProcessCopyOptions(ParseState *pstate,
502504
bool freeze_specified = false;
503505
bool header_specified = false;
504506
bool on_error_specified = false;
507+
bool on_error_tbl_specified = false;
505508
bool log_verbosity_specified = false;
506509
bool reject_limit_specified = false;
507510
ListCell *option;
@@ -677,6 +680,13 @@ ProcessCopyOptions(ParseState *pstate,
677680
reject_limit_specified = true;
678681
opts_out->reject_limit = defGetCopyRejectLimitOption(defel);
679682
}
683+
else if (strcmp(defel->defname, "table") == 0)
684+
{
685+
if (on_error_tbl_specified)
686+
errorConflictingDefElem(defel, pstate);
687+
on_error_tbl_specified = true;
688+
opts_out->on_error_tbl = defGetString(defel);
689+
}
680690
else
681691
ereport(ERROR,
682692
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -685,6 +695,25 @@ ProcessCopyOptions(ParseState *pstate,
685695
parser_errposition(pstate, defel->location)));
686696
}
687697

698+
if (opts_out->on_error == COPY_ON_ERROR_TABLE)
699+
{
700+
if (opts_out->on_error_tbl == NULL)
701+
ereport(ERROR,
702+
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
703+
errmsg("cannot specify %s option value to \"%s\" when %s is not specified", "ON_ERROR", "TABLE", "TABLE"),
704+
errhint("You may need also specify \"%s\" option.", "TABLE"));
705+
706+
if (opts_out->reject_limit)
707+
ereport(ERROR,
708+
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
709+
errmsg("cannot specify %s option when %s option is specified as \"%s\"", "REJECT_LIMIT", "ON_ERROR", "TABLE"));
710+
}
711+
712+
if (opts_out->on_error != COPY_ON_ERROR_TABLE && opts_out->on_error_tbl != NULL)
713+
ereport(ERROR,
714+
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
715+
errmsg("COPY %s option can only be used when %s option is specified as \"%s\"", "TABLE", "ON_ERROR", "TABLE"));
716+
688717
/*
689718
* Check for incompatible options (must do these three before inserting
690719
* defaults)

0 commit comments

Comments
 (0)