mirror of
https://github.com/postgres/postgres.git
synced 2025-07-24 14:22:24 +03:00
Handle INSERT .. ON CONFLICT with partitioned tables
Commit eb7ed3f306
enabled unique constraints on partitioned tables,
but one thing that was not working properly is INSERT/ON CONFLICT.
This commit introduces a new node keeps state related to the ON CONFLICT
clause per partition, and fills it when that partition is about to be
used for tuple routing.
Author: Amit Langote, Álvaro Herrera
Reviewed-by: Etsuro Fujita, Pavan Deolasee
Discussion: https://postgr.es/m/20180228004602.cwdyralmg5ejdqkq@alvherre.pgsql
This commit is contained in:
@ -786,16 +786,102 @@ select * from selfconflict;
|
||||
(3 rows)
|
||||
|
||||
drop table selfconflict;
|
||||
-- check that the following works:
|
||||
-- insert into partitioned_table on conflict do nothing
|
||||
create table parted_conflict_test (a int, b char) partition by list (a);
|
||||
create table parted_conflict_test_1 partition of parted_conflict_test (b unique) for values in (1);
|
||||
-- check ON CONFLICT handling with partitioned tables
|
||||
create table parted_conflict_test (a int unique, b char) partition by list (a);
|
||||
create table parted_conflict_test_1 partition of parted_conflict_test (b unique) for values in (1, 2);
|
||||
-- no indexes required here
|
||||
insert into parted_conflict_test values (1, 'a') on conflict do nothing;
|
||||
insert into parted_conflict_test values (1, 'a') on conflict do nothing;
|
||||
-- however, on conflict do update is not supported yet
|
||||
insert into parted_conflict_test values (1) on conflict (b) do update set a = excluded.a;
|
||||
ERROR: ON CONFLICT DO UPDATE cannot be applied to partitioned table "parted_conflict_test"
|
||||
-- but it works OK if we target the partition directly
|
||||
insert into parted_conflict_test_1 values (1) on conflict (b) do
|
||||
update set a = excluded.a;
|
||||
-- index on a required, which does exist in parent
|
||||
insert into parted_conflict_test values (1, 'a') on conflict (a) do nothing;
|
||||
insert into parted_conflict_test values (1, 'a') on conflict (a) do update set b = excluded.b;
|
||||
-- targeting partition directly will work
|
||||
insert into parted_conflict_test_1 values (1, 'a') on conflict (a) do nothing;
|
||||
insert into parted_conflict_test_1 values (1, 'b') on conflict (a) do update set b = excluded.b;
|
||||
-- index on b required, which doesn't exist in parent
|
||||
insert into parted_conflict_test values (2, 'b') on conflict (b) do update set a = excluded.a;
|
||||
ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
|
||||
-- targeting partition directly will work
|
||||
insert into parted_conflict_test_1 values (2, 'b') on conflict (b) do update set a = excluded.a;
|
||||
-- should see (2, 'b')
|
||||
select * from parted_conflict_test order by a;
|
||||
a | b
|
||||
---+---
|
||||
2 | b
|
||||
(1 row)
|
||||
|
||||
-- now check that DO UPDATE works correctly for target partition with
|
||||
-- different attribute numbers
|
||||
create table parted_conflict_test_2 (b char, a int unique);
|
||||
alter table parted_conflict_test attach partition parted_conflict_test_2 for values in (3);
|
||||
truncate parted_conflict_test;
|
||||
insert into parted_conflict_test values (3, 'a') on conflict (a) do update set b = excluded.b;
|
||||
insert into parted_conflict_test values (3, 'b') on conflict (a) do update set b = excluded.b;
|
||||
-- should see (3, 'b')
|
||||
select * from parted_conflict_test order by a;
|
||||
a | b
|
||||
---+---
|
||||
3 | b
|
||||
(1 row)
|
||||
|
||||
-- case where parent will have a dropped column, but the partition won't
|
||||
alter table parted_conflict_test drop b, add b char;
|
||||
create table parted_conflict_test_3 partition of parted_conflict_test for values in (4);
|
||||
truncate parted_conflict_test;
|
||||
insert into parted_conflict_test (a, b) values (4, 'a') on conflict (a) do update set b = excluded.b;
|
||||
insert into parted_conflict_test (a, b) values (4, 'b') on conflict (a) do update set b = excluded.b where parted_conflict_test.b = 'a';
|
||||
-- should see (4, 'b')
|
||||
select * from parted_conflict_test order by a;
|
||||
a | b
|
||||
---+---
|
||||
4 | b
|
||||
(1 row)
|
||||
|
||||
-- case with multi-level partitioning
|
||||
create table parted_conflict_test_4 partition of parted_conflict_test for values in (5) partition by list (a);
|
||||
create table parted_conflict_test_4_1 partition of parted_conflict_test_4 for values in (5);
|
||||
truncate parted_conflict_test;
|
||||
insert into parted_conflict_test (a, b) values (5, 'a') on conflict (a) do update set b = excluded.b;
|
||||
insert into parted_conflict_test (a, b) values (5, 'b') on conflict (a) do update set b = excluded.b where parted_conflict_test.b = 'a';
|
||||
-- should see (5, 'b')
|
||||
select * from parted_conflict_test order by a;
|
||||
a | b
|
||||
---+---
|
||||
5 | b
|
||||
(1 row)
|
||||
|
||||
-- test with multiple rows
|
||||
truncate parted_conflict_test;
|
||||
insert into parted_conflict_test (a, b) values (1, 'a'), (2, 'a'), (4, 'a') on conflict (a) do update set b = excluded.b where excluded.b = 'b';
|
||||
insert into parted_conflict_test (a, b) values (1, 'b'), (2, 'c'), (4, 'b') on conflict (a) do update set b = excluded.b where excluded.b = 'b';
|
||||
-- should see (1, 'b'), (2, 'a'), (4, 'b')
|
||||
select * from parted_conflict_test order by a;
|
||||
a | b
|
||||
---+---
|
||||
1 | b
|
||||
2 | a
|
||||
4 | b
|
||||
(3 rows)
|
||||
|
||||
drop table parted_conflict_test;
|
||||
-- test behavior of inserting a conflicting tuple into an intermediate
|
||||
-- partitioning level
|
||||
create table parted_conflict (a int primary key, b text) partition by range (a);
|
||||
create table parted_conflict_1 partition of parted_conflict for values from (0) to (1000) partition by range (a);
|
||||
create table parted_conflict_1_1 partition of parted_conflict_1 for values from (0) to (500);
|
||||
insert into parted_conflict values (40, 'forty');
|
||||
insert into parted_conflict_1 values (40, 'cuarenta')
|
||||
on conflict (a) do update set b = excluded.b;
|
||||
drop table parted_conflict;
|
||||
-- same thing, but this time try to use an index that's created not in the
|
||||
-- partition
|
||||
create table parted_conflict (a int, b text) partition by range (a);
|
||||
create table parted_conflict_1 partition of parted_conflict for values from (0) to (1000) partition by range (a);
|
||||
create unique index on only parted_conflict_1 (a);
|
||||
create unique index on only parted_conflict (a);
|
||||
alter index parted_conflict_a_idx attach partition parted_conflict_1_a_idx;
|
||||
create table parted_conflict_1_1 partition of parted_conflict_1 for values from (0) to (500);
|
||||
insert into parted_conflict values (40, 'forty');
|
||||
insert into parted_conflict_1 values (40, 'cuarenta')
|
||||
on conflict (a) do update set b = excluded.b;
|
||||
ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
|
||||
drop table parted_conflict;
|
||||
|
@ -2624,6 +2624,39 @@ insert into my_table values (3, 'CCC'), (4, 'DDD')
|
||||
NOTICE: trigger = my_table_update_trig, old table = (3,CCC), (4,DDD), new table = (3,CCC:CCC), (4,DDD:DDD)
|
||||
NOTICE: trigger = my_table_insert_trig, new table = <NULL>
|
||||
--
|
||||
-- now using a partitioned table
|
||||
--
|
||||
create table iocdu_tt_parted (a int primary key, b text) partition by list (a);
|
||||
create table iocdu_tt_parted1 partition of iocdu_tt_parted for values in (1);
|
||||
create table iocdu_tt_parted2 partition of iocdu_tt_parted for values in (2);
|
||||
create table iocdu_tt_parted3 partition of iocdu_tt_parted for values in (3);
|
||||
create table iocdu_tt_parted4 partition of iocdu_tt_parted for values in (4);
|
||||
create trigger iocdu_tt_parted_insert_trig
|
||||
after insert on iocdu_tt_parted referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger iocdu_tt_parted_update_trig
|
||||
after update on iocdu_tt_parted referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
-- inserts only
|
||||
insert into iocdu_tt_parted values (1, 'AAA'), (2, 'BBB')
|
||||
on conflict (a) do
|
||||
update set b = iocdu_tt_parted.b || ':' || excluded.b;
|
||||
NOTICE: trigger = iocdu_tt_parted_update_trig, old table = <NULL>, new table = <NULL>
|
||||
NOTICE: trigger = iocdu_tt_parted_insert_trig, new table = (1,AAA), (2,BBB)
|
||||
-- mixture of inserts and updates
|
||||
insert into iocdu_tt_parted values (1, 'AAA'), (2, 'BBB'), (3, 'CCC'), (4, 'DDD')
|
||||
on conflict (a) do
|
||||
update set b = iocdu_tt_parted.b || ':' || excluded.b;
|
||||
NOTICE: trigger = iocdu_tt_parted_update_trig, old table = (1,AAA), (2,BBB), new table = (1,AAA:AAA), (2,BBB:BBB)
|
||||
NOTICE: trigger = iocdu_tt_parted_insert_trig, new table = (3,CCC), (4,DDD)
|
||||
-- updates only
|
||||
insert into iocdu_tt_parted values (3, 'CCC'), (4, 'DDD')
|
||||
on conflict (a) do
|
||||
update set b = iocdu_tt_parted.b || ':' || excluded.b;
|
||||
NOTICE: trigger = iocdu_tt_parted_update_trig, old table = (3,CCC), (4,DDD), new table = (3,CCC:CCC), (4,DDD:DDD)
|
||||
NOTICE: trigger = iocdu_tt_parted_insert_trig, new table = <NULL>
|
||||
drop table iocdu_tt_parted;
|
||||
--
|
||||
-- Verify that you can't create a trigger with transition tables for
|
||||
-- more than one event.
|
||||
--
|
||||
|
@ -472,15 +472,90 @@ select * from selfconflict;
|
||||
|
||||
drop table selfconflict;
|
||||
|
||||
-- check that the following works:
|
||||
-- insert into partitioned_table on conflict do nothing
|
||||
create table parted_conflict_test (a int, b char) partition by list (a);
|
||||
create table parted_conflict_test_1 partition of parted_conflict_test (b unique) for values in (1);
|
||||
-- check ON CONFLICT handling with partitioned tables
|
||||
create table parted_conflict_test (a int unique, b char) partition by list (a);
|
||||
create table parted_conflict_test_1 partition of parted_conflict_test (b unique) for values in (1, 2);
|
||||
|
||||
-- no indexes required here
|
||||
insert into parted_conflict_test values (1, 'a') on conflict do nothing;
|
||||
insert into parted_conflict_test values (1, 'a') on conflict do nothing;
|
||||
-- however, on conflict do update is not supported yet
|
||||
insert into parted_conflict_test values (1) on conflict (b) do update set a = excluded.a;
|
||||
-- but it works OK if we target the partition directly
|
||||
insert into parted_conflict_test_1 values (1) on conflict (b) do
|
||||
update set a = excluded.a;
|
||||
|
||||
-- index on a required, which does exist in parent
|
||||
insert into parted_conflict_test values (1, 'a') on conflict (a) do nothing;
|
||||
insert into parted_conflict_test values (1, 'a') on conflict (a) do update set b = excluded.b;
|
||||
|
||||
-- targeting partition directly will work
|
||||
insert into parted_conflict_test_1 values (1, 'a') on conflict (a) do nothing;
|
||||
insert into parted_conflict_test_1 values (1, 'b') on conflict (a) do update set b = excluded.b;
|
||||
|
||||
-- index on b required, which doesn't exist in parent
|
||||
insert into parted_conflict_test values (2, 'b') on conflict (b) do update set a = excluded.a;
|
||||
|
||||
-- targeting partition directly will work
|
||||
insert into parted_conflict_test_1 values (2, 'b') on conflict (b) do update set a = excluded.a;
|
||||
|
||||
-- should see (2, 'b')
|
||||
select * from parted_conflict_test order by a;
|
||||
|
||||
-- now check that DO UPDATE works correctly for target partition with
|
||||
-- different attribute numbers
|
||||
create table parted_conflict_test_2 (b char, a int unique);
|
||||
alter table parted_conflict_test attach partition parted_conflict_test_2 for values in (3);
|
||||
truncate parted_conflict_test;
|
||||
insert into parted_conflict_test values (3, 'a') on conflict (a) do update set b = excluded.b;
|
||||
insert into parted_conflict_test values (3, 'b') on conflict (a) do update set b = excluded.b;
|
||||
|
||||
-- should see (3, 'b')
|
||||
select * from parted_conflict_test order by a;
|
||||
|
||||
-- case where parent will have a dropped column, but the partition won't
|
||||
alter table parted_conflict_test drop b, add b char;
|
||||
create table parted_conflict_test_3 partition of parted_conflict_test for values in (4);
|
||||
truncate parted_conflict_test;
|
||||
insert into parted_conflict_test (a, b) values (4, 'a') on conflict (a) do update set b = excluded.b;
|
||||
insert into parted_conflict_test (a, b) values (4, 'b') on conflict (a) do update set b = excluded.b where parted_conflict_test.b = 'a';
|
||||
|
||||
-- should see (4, 'b')
|
||||
select * from parted_conflict_test order by a;
|
||||
|
||||
-- case with multi-level partitioning
|
||||
create table parted_conflict_test_4 partition of parted_conflict_test for values in (5) partition by list (a);
|
||||
create table parted_conflict_test_4_1 partition of parted_conflict_test_4 for values in (5);
|
||||
truncate parted_conflict_test;
|
||||
insert into parted_conflict_test (a, b) values (5, 'a') on conflict (a) do update set b = excluded.b;
|
||||
insert into parted_conflict_test (a, b) values (5, 'b') on conflict (a) do update set b = excluded.b where parted_conflict_test.b = 'a';
|
||||
|
||||
-- should see (5, 'b')
|
||||
select * from parted_conflict_test order by a;
|
||||
|
||||
-- test with multiple rows
|
||||
truncate parted_conflict_test;
|
||||
insert into parted_conflict_test (a, b) values (1, 'a'), (2, 'a'), (4, 'a') on conflict (a) do update set b = excluded.b where excluded.b = 'b';
|
||||
insert into parted_conflict_test (a, b) values (1, 'b'), (2, 'c'), (4, 'b') on conflict (a) do update set b = excluded.b where excluded.b = 'b';
|
||||
|
||||
-- should see (1, 'b'), (2, 'a'), (4, 'b')
|
||||
select * from parted_conflict_test order by a;
|
||||
|
||||
drop table parted_conflict_test;
|
||||
|
||||
-- test behavior of inserting a conflicting tuple into an intermediate
|
||||
-- partitioning level
|
||||
create table parted_conflict (a int primary key, b text) partition by range (a);
|
||||
create table parted_conflict_1 partition of parted_conflict for values from (0) to (1000) partition by range (a);
|
||||
create table parted_conflict_1_1 partition of parted_conflict_1 for values from (0) to (500);
|
||||
insert into parted_conflict values (40, 'forty');
|
||||
insert into parted_conflict_1 values (40, 'cuarenta')
|
||||
on conflict (a) do update set b = excluded.b;
|
||||
drop table parted_conflict;
|
||||
|
||||
-- same thing, but this time try to use an index that's created not in the
|
||||
-- partition
|
||||
create table parted_conflict (a int, b text) partition by range (a);
|
||||
create table parted_conflict_1 partition of parted_conflict for values from (0) to (1000) partition by range (a);
|
||||
create unique index on only parted_conflict_1 (a);
|
||||
create unique index on only parted_conflict (a);
|
||||
alter index parted_conflict_a_idx attach partition parted_conflict_1_a_idx;
|
||||
create table parted_conflict_1_1 partition of parted_conflict_1 for values from (0) to (500);
|
||||
insert into parted_conflict values (40, 'forty');
|
||||
insert into parted_conflict_1 values (40, 'cuarenta')
|
||||
on conflict (a) do update set b = excluded.b;
|
||||
drop table parted_conflict;
|
||||
|
@ -1982,6 +1982,39 @@ insert into my_table values (3, 'CCC'), (4, 'DDD')
|
||||
on conflict (a) do
|
||||
update set b = my_table.b || ':' || excluded.b;
|
||||
|
||||
--
|
||||
-- now using a partitioned table
|
||||
--
|
||||
|
||||
create table iocdu_tt_parted (a int primary key, b text) partition by list (a);
|
||||
create table iocdu_tt_parted1 partition of iocdu_tt_parted for values in (1);
|
||||
create table iocdu_tt_parted2 partition of iocdu_tt_parted for values in (2);
|
||||
create table iocdu_tt_parted3 partition of iocdu_tt_parted for values in (3);
|
||||
create table iocdu_tt_parted4 partition of iocdu_tt_parted for values in (4);
|
||||
create trigger iocdu_tt_parted_insert_trig
|
||||
after insert on iocdu_tt_parted referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger iocdu_tt_parted_update_trig
|
||||
after update on iocdu_tt_parted referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
|
||||
-- inserts only
|
||||
insert into iocdu_tt_parted values (1, 'AAA'), (2, 'BBB')
|
||||
on conflict (a) do
|
||||
update set b = iocdu_tt_parted.b || ':' || excluded.b;
|
||||
|
||||
-- mixture of inserts and updates
|
||||
insert into iocdu_tt_parted values (1, 'AAA'), (2, 'BBB'), (3, 'CCC'), (4, 'DDD')
|
||||
on conflict (a) do
|
||||
update set b = iocdu_tt_parted.b || ':' || excluded.b;
|
||||
|
||||
-- updates only
|
||||
insert into iocdu_tt_parted values (3, 'CCC'), (4, 'DDD')
|
||||
on conflict (a) do
|
||||
update set b = iocdu_tt_parted.b || ':' || excluded.b;
|
||||
|
||||
drop table iocdu_tt_parted;
|
||||
|
||||
--
|
||||
-- Verify that you can't create a trigger with transition tables for
|
||||
-- more than one event.
|
||||
|
Reference in New Issue
Block a user