Re: COPY ON_CONFLICT TABLE; save duplicated record to another table.
Re: COPY ON_CONFLICT TABLE; save duplicated record to another table.
От:
Jim Jones <jim.jones@uni-muenster.de>
Дата:
Hi Jian
On 25/04/2026 06:12, jian he wrote:
> Comments are welcome!
Thanks for the patch!
A few comments:
== double defGet in function name ==
defGetdefGetCopyOnConflictChoice should probably be
defGetCopyOnConflictChoice
== identical error message in ProcessCopyOptions() ==
The same error message is raised for conflict_tbl_specified and
!conflict_tbl_specified
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("COPY %s requires %s option", "CONFLICT_TABLE", "ON_CONFLICT"));
== unused enum in CopyOnErrorChoice ==
The enum COPY_ON_ERROR_TABLE is introduced, but is never used anywhere.
== unconditional ExecOpenIndices() ==
ExecOpenIndices(resultRelInfo, true);
I'm not very familiar with this part of the code, but it looks like that
this change would affect other COPY FROM operations. If I'm mistaken, a
comment would add some value here. Or perhaps something like:
ExecOpenIndices(resultRelInfo, cstate->opts.on_conflict != ONCONFLICT_NONE);
== ON_CONFLICT TABLE not rejected in COPY TO ==
CONFLICT_TABLE is silently ignored, even if the table does not exist:
postgres=# COPY t TO '/dev/null' (ON_CONFLICT TABLE, CONFLICT_TABLE
table_does_not_exist);
COPY 1
I guess adding a is_from to defGetdefGetCopyOnConflictChoice() is the
way to go.
== redundant condition in CopyFrom() ==
The second condition seems unnecessary, as the previous if already tests
for cstate->opts.on_conflict == ONCONFLICT_NONE:
else if (resultRelInfo->ri_NumIndices > 0 &&
cstate->opts.on_conflict != ONCONFLICT_NONE)
== typos ==
regular realtion > regular relation
vertification > verification
resouces > resources
unqiue > unique
== unnecessary pnstrdup (?) ==
newvalues[i] = CStringGetTextDatum(pnstrdup(cstate->line_buf.data,
cstate->line_buf.len));
Is the duplication really necessary? Wouldn't it suffice to use
cstring_to_text_with_len() instead? Something like:
newvalues[i] =
PointerGetDatum(cstring_to_text_with_len(cstate->line_buf.data,
cstate->line_buf.len));
or even
newvalues[i] = CStringGetTextDatum(cstate->line_buf.data)
I'll check the documentation after we get more feedback on the syntax.
Thanks!
Best, Jim
Re: COPY ON_CONFLICT TABLE; save duplicated record to another table.
От:
jian he <jian.universality@gmail.com>
Дата:
On Fri, May 15, 2026 at 7:56 PM Zsolt Parragi wrote: > > I also noticed the early relation close mentioned by Jim, which can > crash the patch. > fixed. > + This uses the same mechanism as linkend="sql-on-conflict">INSERT ... ON > CONFLICT. > + However, exclusion constraints are not supported; only > NOT DEFERRABLE > + unique constraints are checked for violations. > > EXCLUDE USING gist (... WITH =, ... WITH &&) seems to work fine? > Except that the message mentions unique constraint violation. > I double-checked ExecCheckIndexConstraints, ExecInsertIndexTuples and added some dummy regression tests to confirm that INSERT ON CONFLICT DO NOTHING works fine with exclusion constraints. > I also checked the same trigger behaviors as in the other thread[1], > especially before triggers on the conflict table, and this patch > behaves similarly, it silently drops rows. > I think this could also use some more visibility/documentation about that. > > 1: https://www.postgresql.org/message-id/CAN4CZFPoohFvQTSE0wC%2BwcrfYiZOxFmUdOq0%2B9TCVR6Hk8n6iw%40mail.gmail.com > With the attached v4, row-level and statement-level triggers are now fired for every insertion to conflict_table In v3, there was a performance regression when a table don't have any unique or exclusion constraint, but ON_CONFLICT was still specified as 'TABLE'. I have attached an SQL test script demonstrating this. With v4, this regression is now very very minimal for COPY operations where ON_CONFLICT is set to 'TABLE' on a target table without any unique or exclusion constraints. I also polished the documentation. Comments from Jim Jones also addressed. -- jian https://www.enterprisedb.com/
COPY ON_CONFLICT TABLE; save duplicated record to another table.
От:
jian he <jian.universality@gmail.com>
Дата:
Hi,
This is for v20.
Reference: https://web.archive.org/web/20240328094030/https://riggs.business/blog/f/postgresql-todo-2023
COPY enhancement:
Detect duplicate rows and redirect them to a separate table without
aborting the load.
While reviewing this TODO, I quickly noticed this idea closely aligns with
https://commitfest.postgresql.org/patch/4817.
Both ideas share common elements: allowing a user-specified table,
validating its metadata, and storing rows in it.
Based on that, I spent some time working on the implementation.
Proposed syntax:
COPY FROM (ON_CONFLICT TABLE, CONFLICT_TABLE conflict_tbl);
The CONFLICT_TABLE requires exactly four columns: COPY target table, COPY
filename, the line number of the duplicate, and the duplicate record itself.
This structure is fixed, a pre-defined data type is unnecessary. Validation is
based solely on the column data types (pg_attribute.atttypid) rather than their
names (pg_attribute.attname). The expected types are OID, TEXT, INT8, and TEXT,
respectively.
This uses INSERT ON CONFLICT infrastructure under the hood.
Demo:
CREATE TABLE t_copy_tbl(a int, b int, c text);
CREATE TABLE err_tbl1(copy_tbl oid, filename text, lineno bigint, line text);
CREATE UNIQUE INDEX ON t_copy_tbl (c);
COPY t_copy_tbl(b,a, c) FROM STDIN (DELIMITER ',', ON_CONFLICT TABLE,
CONFLICT_TABLE err_tbl1, log_verbosity verbose);
4,17,aaaaaa
6,11,aaaaaa
11,1,xxxxxxxx
12,1,xxxxxxxx
13,1,xxxxxxxx
\.
table err_tbl1 ;
copy_tbl | filename | lineno | line
----------+----------+--------+---------------
18231 | STDIN | 2 | 6,11,aaaaaa
18231 | STDIN | 4 | 12,1,xxxxxxxx
18231 | STDIN | 5 | 13,1,xxxxxxxx
(3 rows)
(I need to double-check the exclusion unique constraint)
Comments are welcome!
--
jian
https://www.enterprisedb.com/
Re: COPY ON_CONFLICT TABLE; save duplicated record to another table.
От:
Zsolt Parragi <zsolt.parragi@percona.com>
Дата:
Hello!
I tried the patch and found a few issues.
1. Two of them are null pointer dereference crashes, one with
partitioned tables:
CREATE TABLE part_t (a int PRIMARY KEY, b text) PARTITION BY RANGE (a);
CREATE TABLE part_t_p1 PARTITION OF part_t FOR VALUES FROM (0) TO (1000);
CREATE TABLE conflict_log (
rel oid,
file_name text,
line_no bigint,
raw_line text
);
INSERT INTO part_t VALUES (1, 'pre-existing');
COPY part_t (a, b) FROM stdin WITH (on_conflict 'table',
conflict_table 'conflict_log');
2 row-two
1 dup
3 row-three
\.
2. And another with repeateable reads:
CREATE TABLE t_rr (a int PRIMARY KEY, b text);
CREATE TABLE conflict_log (rel oid, fname text, ln bigint, raw text);
INSERT INTO t_rr VALUES (1, 'pre-committed');
BEGIN ISOLATION LEVEL REPEATABLE READ;
COPY t_rr FROM stdin WITH (on_conflict 'table', conflict_table 'conflict_log');
1 dup-row
\.
3. There's also a possible data loss scenario, reports 3 copied 0 actual:
CREATE TABLE conf_log (
relname oid,
fname text,
lineno bigint,
rawline text
);
CREATE TABLE no_idx_tgt (id int, payload text);
CREATE FUNCTION noop_trig() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
RETURN NEW;
END;
$$;
CREATE TRIGGER noop_before BEFORE INSERT ON no_idx_tgt
FOR EACH ROW EXECUTE FUNCTION noop_trig();
COPY no_idx_tgt (id, payload) FROM STDIN
WITH (ON_CONFLICT TABLE, CONFLICT_TABLE conf_log);
1 alpha
2 beta
3 gamma
\.
SELECT 'A: no_idx_tgt count' AS scenario, count(*) AS rows FROM no_idx_tgt;
SELECT 'A: conf_log count' AS scenario, count(*) AS rows FROM conf_log;
SELECT * FROM no_idx_tgt ORDER BY id;
4. Shouldn't the following error out?
CREATE TABLE t (a int PRIMARY KEY, b text);
COPY t TO '/dev/null' (ON_CONFLICT TABLE, CONFLICT_TABLE no_such_table);