Thread

  1. Re: on_error table, saving error info to a table

    Zsolt Parragi <zsolt.parragi@percona.com> — 2026-05-14T17:14:29Z

    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?