Re: on_error table, saving error info to a table

From: Zsolt Parragi <zsolt(dot)parragi(at)percona(dot)com>
To: pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: on_error table, saving error info to a table
Date: 2026-05-14 17:14:29
Message-ID: CAN4CZFPOj5LhzwrmcvKd8D0v_D6EiKSBFazDBCzzq8zAP5a9Vw@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hello!

+ cstate->error_rel = table_open(err_relOid, NoLock);
+

... and shortly after ...

+
+ table_close(cstate->error_rel, NoLock);

Bot this is still used by many other calls after closing.

See the following example:

SET debug_discard_caches = 1;
CREATE TABLE src_tbl (a int, b int);
CREATE TABLE err_tbl OF copy_error_saving;
CREATE INDEX src_idx ON src_tbl(a);
COPY src_tbl FROM STDIN (FORMAT csv, ON_ERROR table, ERROR_TABLE err_tbl);
1,2
xx,3
3,4
\.
-- ERROR: relation with OID 0 does not exist

+ /* Handle queued AFTER triggers */
+ AfterTriggerEndQuery(cstate->mtcontext->estate);

Is the order of this correct? See the following snippet that crashes the server:

CREATE TABLE target_tbl (id int, val int);
CREATE TABLE err_tbl OF copy_error_saving;

CREATE OR REPLACE FUNCTION err_stmt_trans_fn() RETURNS trigger AS $$
BEGIN
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER err_stmt_trans
AFTER INSERT ON err_tbl
REFERENCING NEW TABLE AS new_rows
FOR EACH STATEMENT EXECUTE FUNCTION err_stmt_trans_fn();

\echo === COPY ===
COPY target_tbl FROM stdin WITH (on_error 'table', error_table 'err_tbl');
1 100
bad 200
3 notanumber
4 400
\.

+
+ cstate->num_errors = cstate->num_errors + estate->es_processed;

Counting seems to miss if a before trigger returns null:

\set ON_ERROR_STOP 0
CREATE TABLE t2 (a int, b int, c int);
CREATE TABLE err_tbl2 OF copy_error_saving;
CREATE FUNCTION drop_all() RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
RETURN NULL;
END;
$$;
CREATE TRIGGER drop_all_t BEFORE INSERT ON err_tbl2 FOR EACH ROW
EXECUTE FUNCTION drop_all();
COPY t2 FROM STDIN WITH (FORMAT csv, ON_ERROR table, ERROR_TABLE err_tbl2);
1,2,a
3,4,b
5,6,c
7,8,d
9,10,e
\.

SELECT count(*) AS n FROM t2;
SELECT count(*) AS n FROM err_tbl2;

+ typoid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
+ PointerGetDatum("copy_error_saving"),
+ ObjectIdGetDatum(PG_CATALOG_NAMESPACE));
...
+ if (reloftype != typoid)
+ ereport(ERROR,
...
+ errhint("The COPY error saving table must be a
typed table based on type \"%s\".",
+ format_type_be_qualified(typoid)));

Isn't an if (!OidIsValid(typoid)) check missing between the two?

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Nikita Malakhov 2026-05-14 17:22:55 Re: [(known) BUG] DELETE/UPDATE more than one row in partitioned foreign table
Previous Message Ayush Tiwari 2026-05-14 17:05:51 Re: [PATCH] Rebuild CHECK constraints after generated column SET EXPRESSION