Replace flaky CIC/RI isolation tests with a TAP test

The isolation tests for INSERT ON CONFLICT behavior during CREATE INDEX
CONCURRENTLY and REINDEX CONCURRENTLY (added by bc32a12e0d,
2bc7e886fc, and 90eae926ab) were disabled in 77038d6d0b due to
persistent CI flakiness, after several attempts at stabilization.

This commit removes them and introduces a TAP test in test_misc module
(010_index_concurrently_upsert.pl) that covers the same scenarios.  This
new test should hopefully be more stable while providing assurance that
the fixes in all those commits (plus 81f72115cf) continue to work.

Author: Mihail Nikalayeu <mihailnikalayeu@gmail.com>
Reported-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/ccssrhafzbp3a3beju3ptyc56a7gbfimj4vwkbokoldofckrc7@bso37rxskjtf
Discussion: https://postgr.es/m/CANtu0ogv+6wqRzPK241jik4U95s1pW3MCZ3rX5ZqbFdUysz7Qw@mail.gmail.com
Discussion: https://postgr.es/m/202512112014.icpomgc37zx4@alvherre.pgsql
This commit is contained in:
Álvaro Herrera
2026-01-07 19:44:57 -03:00
parent a516b3f00d
commit e1c971945d
17 changed files with 908 additions and 1803 deletions

View File

@ -17,14 +17,6 @@ ISOLATION = basic \
syscache-update-pruned \
heap_lock_update
# Temporarily disabled because of flakiness
#ISOLATION =+
# index-concurrently-upsert \
# index-concurrently-upsert-predicate \
# reindex-concurrently-upsert \
# reindex-concurrently-upsert-on-constraint \
# reindex-concurrently-upsert-partitioned
# The injection points are cluster-wide, so disable installcheck
NO_INSTALLCHECK = 1

View File

@ -1,123 +0,0 @@
Parsed test spec with 5 sessions
starting permutation: s1_attach_invalidate_catalog_snapshot s4_wakeup_s1_setup s3_start_create_index s1_start_upsert s4_wakeup_define_index_before_set_valid s2_start_upsert s5_wakeup_s1_from_invalidate_catalog_snapshot s4_wakeup_s2 s4_wakeup_s1
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
step s1_attach_invalidate_catalog_snapshot:
SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
injection_points_attach
-----------------------
(1 row)
step s4_wakeup_s1_setup:
SELECT CASE WHEN
(SELECT pid FROM pg_stat_activity
WHERE wait_event_type = 'InjectionPoint' AND
wait_event = 'invalidate-catalog-snapshot-end') IS NOT NULL
THEN injection_points_wakeup('invalidate-catalog-snapshot-end')
END;
case
----
(1 row)
step s3_start_create_index:
CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_special_duplicate ON test.tbl(abs(i)) WHERE i < 10000;
<waiting ...>
step s1_start_upsert:
INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_define_index_before_set_valid:
SELECT injection_points_detach('define-index-before-set-valid');
SELECT injection_points_wakeup('define-index-before-set-valid');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert:
INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
<waiting ...>
step s5_wakeup_s1_from_invalidate_catalog_snapshot:
DO $$
DECLARE
v_waiting_pid INTEGER;
BEGIN
LOOP
SELECT pid INTO v_waiting_pid
FROM pg_stat_activity
WHERE wait_event_type = 'InjectionPoint'
AND wait_event = 'invalidate-catalog-snapshot-end'
LIMIT 1;
EXIT WHEN v_waiting_pid IS NOT NULL;
PERFORM pg_sleep(100);
END LOOP;
END
$$;
SELECT injection_points_detach('invalidate-catalog-snapshot-end');
SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s2:
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s1:
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s1_start_upsert: <... completed>
step s2_start_upsert: <... completed>
step s3_start_create_index: <... completed>

View File

@ -1,124 +0,0 @@
Parsed test spec with 5 sessions
starting permutation: s1_attach_invalidate_catalog_snapshot s4_wakeup_s1_setup s3_start_create_index s1_start_upsert s4_wakeup_define_index_before_set_valid s2_start_upsert s5_wakeup_s1_from_invalidate_catalog_snapshot s4_wakeup_s2 s4_wakeup_s1
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
step s1_attach_invalidate_catalog_snapshot:
SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
<waiting ...>
step s4_wakeup_s1_setup:
SELECT CASE WHEN
(SELECT pid FROM pg_stat_activity
WHERE wait_event_type = 'InjectionPoint' AND
wait_event = 'invalidate-catalog-snapshot-end') IS NOT NULL
THEN injection_points_wakeup('invalidate-catalog-snapshot-end')
END;
case
----
(1 row)
step s1_attach_invalidate_catalog_snapshot: <... completed>
injection_points_attach
-----------------------
(1 row)
step s3_start_create_index:
CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_special_duplicate ON test.tbl(abs(i)) WHERE i < 10000;
<waiting ...>
step s1_start_upsert:
INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_define_index_before_set_valid:
SELECT injection_points_detach('define-index-before-set-valid');
SELECT injection_points_wakeup('define-index-before-set-valid');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert:
INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
<waiting ...>
step s5_wakeup_s1_from_invalidate_catalog_snapshot:
DO $$
DECLARE
v_waiting_pid INTEGER;
BEGIN
LOOP
SELECT pid INTO v_waiting_pid
FROM pg_stat_activity
WHERE wait_event_type = 'InjectionPoint'
AND wait_event = 'invalidate-catalog-snapshot-end'
LIMIT 1;
EXIT WHEN v_waiting_pid IS NOT NULL;
PERFORM pg_sleep(100);
END LOOP;
END
$$;
SELECT injection_points_detach('invalidate-catalog-snapshot-end');
SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s2:
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s1:
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s1_start_upsert: <... completed>
step s2_start_upsert: <... completed>
step s3_start_create_index: <... completed>

View File

@ -1,123 +0,0 @@
Parsed test spec with 5 sessions
starting permutation: s1_attach_invalidate_catalog_snapshot s4_wakeup_s1_setup s3_start_create_index s1_start_upsert s4_wakeup_define_index_before_set_valid s2_start_upsert s5_wakeup_s1_from_invalidate_catalog_snapshot s4_wakeup_s2 s4_wakeup_s1
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
step s1_attach_invalidate_catalog_snapshot:
SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
injection_points_attach
-----------------------
(1 row)
step s4_wakeup_s1_setup:
SELECT CASE WHEN
(SELECT pid FROM pg_stat_activity
WHERE wait_event_type = 'InjectionPoint' AND
wait_event = 'invalidate-catalog-snapshot-end') IS NOT NULL
THEN injection_points_wakeup('invalidate-catalog-snapshot-end')
END;
case
----
(1 row)
step s3_start_create_index:
CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_duplicate ON test.tbl(i);
<waiting ...>
step s1_start_upsert:
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_define_index_before_set_valid:
SELECT injection_points_detach('define-index-before-set-valid');
SELECT injection_points_wakeup('define-index-before-set-valid');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert:
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s5_wakeup_s1_from_invalidate_catalog_snapshot:
DO $$
DECLARE
v_waiting_pid INTEGER;
BEGIN
LOOP
SELECT pid INTO v_waiting_pid
FROM pg_stat_activity
WHERE wait_event_type = 'InjectionPoint'
AND wait_event = 'invalidate-catalog-snapshot-end'
LIMIT 1;
EXIT WHEN v_waiting_pid IS NOT NULL;
PERFORM pg_sleep(100);
END LOOP;
END
$$;
SELECT injection_points_detach('invalidate-catalog-snapshot-end');
SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s2:
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s1:
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s1_start_upsert: <... completed>
step s2_start_upsert: <... completed>
step s3_start_create_index: <... completed>

View File

@ -1,124 +0,0 @@
Parsed test spec with 5 sessions
starting permutation: s1_attach_invalidate_catalog_snapshot s4_wakeup_s1_setup s3_start_create_index s1_start_upsert s4_wakeup_define_index_before_set_valid s2_start_upsert s5_wakeup_s1_from_invalidate_catalog_snapshot s4_wakeup_s2 s4_wakeup_s1
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
step s1_attach_invalidate_catalog_snapshot:
SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
<waiting ...>
step s4_wakeup_s1_setup:
SELECT CASE WHEN
(SELECT pid FROM pg_stat_activity
WHERE wait_event_type = 'InjectionPoint' AND
wait_event = 'invalidate-catalog-snapshot-end') IS NOT NULL
THEN injection_points_wakeup('invalidate-catalog-snapshot-end')
END;
case
----
(1 row)
step s1_attach_invalidate_catalog_snapshot: <... completed>
injection_points_attach
-----------------------
(1 row)
step s3_start_create_index:
CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_duplicate ON test.tbl(i);
<waiting ...>
step s1_start_upsert:
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_define_index_before_set_valid:
SELECT injection_points_detach('define-index-before-set-valid');
SELECT injection_points_wakeup('define-index-before-set-valid');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert:
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s5_wakeup_s1_from_invalidate_catalog_snapshot:
DO $$
DECLARE
v_waiting_pid INTEGER;
BEGIN
LOOP
SELECT pid INTO v_waiting_pid
FROM pg_stat_activity
WHERE wait_event_type = 'InjectionPoint'
AND wait_event = 'invalidate-catalog-snapshot-end'
LIMIT 1;
EXIT WHEN v_waiting_pid IS NOT NULL;
PERFORM pg_sleep(100);
END LOOP;
END
$$;
SELECT injection_points_detach('invalidate-catalog-snapshot-end');
SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s2:
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s1:
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s1_start_upsert: <... completed>
step s2_start_upsert: <... completed>
step s3_start_create_index: <... completed>

View File

@ -1,238 +0,0 @@
Parsed test spec with 4 sessions
starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s4_wakeup_to_set_dead s2_start_upsert s4_wakeup_s1 s4_wakeup_s2
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
injection_points_set_local
--------------------------
(1 row)
step s3_setup_wait_before_set_dead:
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
injection_points_attach
-----------------------
(1 row)
step s3_start_reindex:
REINDEX INDEX CONCURRENTLY test.tbl_pkey;
<waiting ...>
step s1_start_upsert:
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_to_set_dead:
SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert:
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_s1:
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s1_start_upsert: <... completed>
step s4_wakeup_s2:
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert: <... completed>
step s3_start_reindex: <... completed>
starting permutation: s3_setup_wait_before_swap s3_start_reindex s1_start_upsert s4_wakeup_to_swap s2_start_upsert s4_wakeup_s2 s4_wakeup_s1
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
injection_points_set_local
--------------------------
(1 row)
step s3_setup_wait_before_swap:
SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
injection_points_attach
-----------------------
(1 row)
step s3_start_reindex:
REINDEX INDEX CONCURRENTLY test.tbl_pkey;
<waiting ...>
step s1_start_upsert:
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_to_swap:
SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert:
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_s2:
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s1:
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s1_start_upsert: <... completed>
step s2_start_upsert: <... completed>
step s3_start_reindex: <... completed>
starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s2_start_upsert s4_wakeup_s1 s4_wakeup_to_set_dead s4_wakeup_s2
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
injection_points_set_local
--------------------------
(1 row)
step s3_setup_wait_before_set_dead:
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
injection_points_attach
-----------------------
(1 row)
step s3_start_reindex:
REINDEX INDEX CONCURRENTLY test.tbl_pkey;
<waiting ...>
step s1_start_upsert:
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
<waiting ...>
step s2_start_upsert:
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_s1:
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s1_start_upsert: <... completed>
step s4_wakeup_to_set_dead:
SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s2:
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert: <... completed>
step s3_start_reindex: <... completed>

View File

@ -1,238 +0,0 @@
Parsed test spec with 4 sessions
starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s4_wakeup_to_set_dead s2_start_upsert s4_wakeup_s1 s4_wakeup_s2
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
injection_points_set_local
--------------------------
(1 row)
step s3_setup_wait_before_set_dead:
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
injection_points_attach
-----------------------
(1 row)
step s3_start_reindex:
REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
<waiting ...>
step s1_start_upsert:
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_to_set_dead:
SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert:
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_s1:
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s1_start_upsert: <... completed>
step s4_wakeup_s2:
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert: <... completed>
step s3_start_reindex: <... completed>
starting permutation: s3_setup_wait_before_swap s3_start_reindex s1_start_upsert s4_wakeup_to_swap s2_start_upsert s4_wakeup_s2 s4_wakeup_s1
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
injection_points_set_local
--------------------------
(1 row)
step s3_setup_wait_before_swap:
SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
injection_points_attach
-----------------------
(1 row)
step s3_start_reindex:
REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
<waiting ...>
step s1_start_upsert:
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_to_swap:
SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert:
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_s2:
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s1:
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s1_start_upsert: <... completed>
step s2_start_upsert: <... completed>
step s3_start_reindex: <... completed>
starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s2_start_upsert s4_wakeup_s1 s4_wakeup_to_set_dead s4_wakeup_s2
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
injection_points_set_local
--------------------------
(1 row)
step s3_setup_wait_before_set_dead:
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
injection_points_attach
-----------------------
(1 row)
step s3_start_reindex:
REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
<waiting ...>
step s1_start_upsert:
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s2_start_upsert:
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_s1:
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s1_start_upsert: <... completed>
step s4_wakeup_to_set_dead:
SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s2:
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert: <... completed>
step s3_start_reindex: <... completed>

View File

@ -1,238 +0,0 @@
Parsed test spec with 4 sessions
starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s4_wakeup_to_set_dead s2_start_upsert s4_wakeup_s1 s4_wakeup_s2
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
injection_points_set_local
--------------------------
(1 row)
step s3_setup_wait_before_set_dead:
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
injection_points_attach
-----------------------
(1 row)
step s3_start_reindex:
REINDEX INDEX CONCURRENTLY test.tbl_pkey;
<waiting ...>
step s1_start_upsert:
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_to_set_dead:
SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert:
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_s1:
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s1_start_upsert: <... completed>
step s4_wakeup_s2:
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert: <... completed>
step s3_start_reindex: <... completed>
starting permutation: s3_setup_wait_before_swap s3_start_reindex s1_start_upsert s4_wakeup_to_swap s2_start_upsert s4_wakeup_s2 s4_wakeup_s1
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
injection_points_set_local
--------------------------
(1 row)
step s3_setup_wait_before_swap:
SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
injection_points_attach
-----------------------
(1 row)
step s3_start_reindex:
REINDEX INDEX CONCURRENTLY test.tbl_pkey;
<waiting ...>
step s1_start_upsert:
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_to_swap:
SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert:
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_s2:
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s1:
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s1_start_upsert: <... completed>
step s2_start_upsert: <... completed>
step s3_start_reindex: <... completed>
starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s2_start_upsert s4_wakeup_s1 s4_wakeup_to_set_dead s4_wakeup_s2
injection_points_attach
-----------------------
(1 row)
injection_points_attach
-----------------------
(1 row)
injection_points_set_local
--------------------------
(1 row)
step s3_setup_wait_before_set_dead:
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
injection_points_attach
-----------------------
(1 row)
step s3_start_reindex:
REINDEX INDEX CONCURRENTLY test.tbl_pkey;
<waiting ...>
step s1_start_upsert:
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s2_start_upsert:
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
<waiting ...>
step s4_wakeup_s1:
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s1_start_upsert: <... completed>
step s4_wakeup_to_set_dead:
SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s4_wakeup_s2:
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
injection_points_detach
-----------------------
(1 row)
injection_points_wakeup
-----------------------
(1 row)
step s2_start_upsert: <... completed>
step s3_start_reindex: <... completed>

View File

@ -47,12 +47,6 @@ tests += {
'inplace',
'syscache-update-pruned',
'heap_lock_update',
# temporarily disabled because of flakiness
# 'index-concurrently-upsert',
# 'index-concurrently-upsert-predicate',
# 'reindex-concurrently-upsert',
# 'reindex-concurrently-upsert-on-constraint',
# 'reindex-concurrently-upsert-partitioned',
],
'runningcheck': false, # see syscache-update-pruned
# Some tests wait for all snapshots, so avoid parallel execution

View File

@ -1,124 +0,0 @@
# This test verifies INSERT ON CONFLICT DO UPDATE behavior concurrent with
# CREATE INDEX CONCURRENTLY a partial index.
#
# - s1: UPSERT a tuple
# - s2: UPSERT the same tuple
# - s3: CREATE UNIQUE INDEX CONCURRENTLY (with a predicate)
#
# - s4 and s5: control concurrency via injection points
setup
{
CREATE EXTENSION injection_points;
CREATE SCHEMA test;
CREATE UNLOGGED TABLE test.tbl(i int, updated_at timestamp);
CREATE UNIQUE INDEX tbl_pkey_special ON test.tbl(abs(i)) WHERE i < 1000;
ALTER TABLE test.tbl SET (parallel_workers=0);
}
teardown
{
DROP SCHEMA test CASCADE;
DROP EXTENSION injection_points;
}
session s1
setup
{
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
}
step s1_attach_invalidate_catalog_snapshot
{
SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
}
step s1_start_upsert
{
INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
}
session s2
setup
{
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
}
step s2_start_upsert
{
INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
}
session s3
setup
{
SELECT injection_points_set_local();
SELECT injection_points_attach('define-index-before-set-valid', 'wait');
}
step s3_start_create_index
{
CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_special_duplicate ON test.tbl(abs(i)) WHERE i < 10000;
}
session s4
# Step s1_attach_invalidate_catalog_snapshot sleeps or not depending on
# build conditions (CATCACHE_FORCE_RELEASE). Here we send a wakeup signal if
# it's sleeping or do nothing otherwise, and print a null value in either
# case.
step s4_wakeup_s1_setup
{
SELECT CASE WHEN
(SELECT pid FROM pg_stat_activity
WHERE wait_event_type = 'InjectionPoint' AND
wait_event = 'invalidate-catalog-snapshot-end') IS NOT NULL
THEN injection_points_wakeup('invalidate-catalog-snapshot-end')
END;
}
step s4_wakeup_s1
{
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
}
step s4_wakeup_s2
{
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
}
step s4_wakeup_define_index_before_set_valid
{
SELECT injection_points_detach('define-index-before-set-valid');
SELECT injection_points_wakeup('define-index-before-set-valid');
}
session s5
step s5_wakeup_s1_from_invalidate_catalog_snapshot
{
DO $$
DECLARE
v_waiting_pid INTEGER;
BEGIN
LOOP
SELECT pid INTO v_waiting_pid
FROM pg_stat_activity
WHERE wait_event_type = 'InjectionPoint'
AND wait_event = 'invalidate-catalog-snapshot-end'
LIMIT 1;
EXIT WHEN v_waiting_pid IS NOT NULL;
PERFORM pg_sleep(100);
END LOOP;
END
$$;
SELECT injection_points_detach('invalidate-catalog-snapshot-end');
SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
}
permutation
s1_attach_invalidate_catalog_snapshot
s4_wakeup_s1_setup
s3_start_create_index(s1_start_upsert, s2_start_upsert)
s1_start_upsert
s4_wakeup_define_index_before_set_valid
s2_start_upsert(s1_start_upsert)
s5_wakeup_s1_from_invalidate_catalog_snapshot
s4_wakeup_s2
s4_wakeup_s1

View File

@ -1,123 +0,0 @@
# This test verifies INSERT ON CONFLICT DO UPDATE behavior concurrent with
# CREATE INDEX CONCURRENTLY.
#
# - s1: UPSERT a tuple
# - s2: UPSERT the same tuple
# - s3: CREATE UNIQUE INDEX CONCURRENTLY
#
# - s4: Control concurrency using injection points
setup
{
CREATE EXTENSION injection_points;
CREATE SCHEMA test;
CREATE UNLOGGED TABLE test.tbl(i int primary key, updated_at timestamp);
ALTER TABLE test.tbl SET (parallel_workers=0);
}
teardown
{
DROP SCHEMA test CASCADE;
DROP EXTENSION injection_points;
}
session s1
setup
{
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
}
step s1_attach_invalidate_catalog_snapshot
{
SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
}
step s1_start_upsert
{
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
}
session s2
setup
{
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
}
step s2_start_upsert
{
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
}
session s3
setup
{
SELECT injection_points_set_local();
SELECT injection_points_attach('define-index-before-set-valid', 'wait');
}
step s3_start_create_index
{
CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_duplicate ON test.tbl(i);
}
session s4
# Step s1_attach_invalidate_catalog_snapshot sleeps or not depending on
# build conditions (CATCACHE_FORCE_RELEASE). Here we send a wakeup signal if
# it's sleeping or do nothing otherwise, and print a null value in either
# case.
step s4_wakeup_s1_setup
{
SELECT CASE WHEN
(SELECT pid FROM pg_stat_activity
WHERE wait_event_type = 'InjectionPoint' AND
wait_event = 'invalidate-catalog-snapshot-end') IS NOT NULL
THEN injection_points_wakeup('invalidate-catalog-snapshot-end')
END;
}
step s4_wakeup_s1
{
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
}
step s4_wakeup_s2
{
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
}
step s4_wakeup_define_index_before_set_valid
{
SELECT injection_points_detach('define-index-before-set-valid');
SELECT injection_points_wakeup('define-index-before-set-valid');
}
session s5
step s5_wakeup_s1_from_invalidate_catalog_snapshot
{
DO $$
DECLARE
v_waiting_pid INTEGER;
BEGIN
LOOP
SELECT pid INTO v_waiting_pid
FROM pg_stat_activity
WHERE wait_event_type = 'InjectionPoint'
AND wait_event = 'invalidate-catalog-snapshot-end'
LIMIT 1;
EXIT WHEN v_waiting_pid IS NOT NULL;
PERFORM pg_sleep(100);
END LOOP;
END
$$;
SELECT injection_points_detach('invalidate-catalog-snapshot-end');
SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
}
permutation
s1_attach_invalidate_catalog_snapshot
s4_wakeup_s1_setup
s3_start_create_index(s1_start_upsert, s2_start_upsert)
s1_start_upsert
s4_wakeup_define_index_before_set_valid
s2_start_upsert(s1_start_upsert)
s5_wakeup_s1_from_invalidate_catalog_snapshot
s4_wakeup_s2
s4_wakeup_s1

View File

@ -1,110 +0,0 @@
# Test race conditions involving:
#
# - s1: UPSERT a tuple
# - s2: UPSERT the same tuple
# - s3: concurrently REINDEX the primary key
#
# - s4: operations with injection points
setup
{
CREATE EXTENSION injection_points;
CREATE SCHEMA test;
CREATE UNLOGGED TABLE test.tbl(i int primary key, updated_at timestamp);
ALTER TABLE test.tbl SET (parallel_workers=0);
}
teardown
{
DROP SCHEMA test CASCADE;
DROP EXTENSION injection_points;
}
session s1
setup
{
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
}
step s1_start_upsert
{
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
}
session s2
setup
{
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
}
step s2_start_upsert
{
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
}
session s3
setup
{
SELECT injection_points_set_local();
}
step s3_setup_wait_before_set_dead
{
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
}
step s3_setup_wait_before_swap
{
SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
}
step s3_start_reindex
{
REINDEX INDEX CONCURRENTLY test.tbl_pkey;
}
session s4
step s4_wakeup_to_swap
{
SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
}
step s4_wakeup_s1
{
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
}
step s4_wakeup_s2
{
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
}
step s4_wakeup_to_set_dead
{
SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
}
permutation
s3_setup_wait_before_set_dead
s3_start_reindex(s1_start_upsert, s2_start_upsert)
s1_start_upsert(s4_wakeup_s2)
s4_wakeup_to_set_dead
s2_start_upsert(s1_start_upsert)
s4_wakeup_s1
s4_wakeup_s2
permutation
s3_setup_wait_before_swap
s3_start_reindex(s1_start_upsert, s2_start_upsert)
s1_start_upsert(s4_wakeup_s2)
s4_wakeup_to_swap
s2_start_upsert(s1_start_upsert)
s4_wakeup_s2
s4_wakeup_s1
permutation
s3_setup_wait_before_set_dead
s3_start_reindex(s1_start_upsert, s2_start_upsert)
s1_start_upsert(s4_wakeup_s2)
s2_start_upsert(s1_start_upsert)
s4_wakeup_s1
s4_wakeup_to_set_dead
s4_wakeup_s2

View File

@ -1,113 +0,0 @@
# This test verifies INSERT ON CONFLICT DO UPDATE behavior on partitioned
# tables concurrent with REINDEX CONCURRENTLY.
#
# - s1: UPSERT a tuple
# - s2: UPSERT the same tuple
# - s3: concurrently REINDEX the primary key index
#
# - s4: controls concurrency via injection points
setup
{
CREATE EXTENSION injection_points;
CREATE SCHEMA test;
CREATE TABLE test.tbl(i int primary key, updated_at timestamp) PARTITION BY RANGE (i);
CREATE TABLE test.tbl_partition PARTITION OF test.tbl
FOR VALUES FROM (0) TO (10000)
WITH (parallel_workers = 0);
}
teardown
{
DROP SCHEMA test CASCADE;
DROP EXTENSION injection_points;
}
session s1
setup
{
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
}
step s1_start_upsert
{
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
}
session s2
setup
{
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
}
step s2_start_upsert
{
INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
}
session s3
setup
{
SELECT injection_points_set_local();
}
step s3_setup_wait_before_set_dead
{
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
}
step s3_setup_wait_before_swap
{
SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
}
step s3_start_reindex
{
REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
}
session s4
step s4_wakeup_to_swap
{
SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
}
step s4_wakeup_s1
{
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
}
step s4_wakeup_s2
{
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
}
step s4_wakeup_to_set_dead
{
SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
}
permutation
s3_setup_wait_before_set_dead
s3_start_reindex(s1_start_upsert, s2_start_upsert)
s1_start_upsert(s4_wakeup_s2)
s4_wakeup_to_set_dead
s2_start_upsert(s1_start_upsert)
s4_wakeup_s1
s4_wakeup_s2
permutation
s3_setup_wait_before_swap
s3_start_reindex(s1_start_upsert, s2_start_upsert)
s1_start_upsert(s4_wakeup_s2)
s4_wakeup_to_swap
s2_start_upsert(s1_start_upsert)
s4_wakeup_s2
s4_wakeup_s1
permutation
s3_setup_wait_before_set_dead
s3_start_reindex(s1_start_upsert, s2_start_upsert)
s1_start_upsert(s4_wakeup_s2)
s2_start_upsert(s1_start_upsert)
s4_wakeup_s1
s4_wakeup_to_set_dead
s4_wakeup_s2

View File

@ -1,111 +0,0 @@
# This test verifies INSERT ON CONFLICT DO UPDATE behavior concurrent with
# REINDEX CONCURRENTLY.
#
# - s1: UPSERT a tuple
# - s2: UPSERT the same tuple
# - s3: REINDEX concurrent primary key index
#
# - s4: controls concurrency via injection points
setup
{
CREATE EXTENSION injection_points;
CREATE SCHEMA test;
CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
ALTER TABLE test.tbl SET (parallel_workers=0);
}
teardown
{
DROP SCHEMA test CASCADE;
DROP EXTENSION injection_points;
}
session s1
setup
{
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
}
step s1_start_upsert
{
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
}
session s2
setup
{
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
}
step s2_start_upsert
{
INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
}
session s3
setup
{
SELECT injection_points_set_local();
}
step s3_setup_wait_before_set_dead
{
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
}
step s3_setup_wait_before_swap
{
SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
}
step s3_start_reindex
{
REINDEX INDEX CONCURRENTLY test.tbl_pkey;
}
session s4
step s4_wakeup_to_swap
{
SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
}
step s4_wakeup_s1
{
SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
}
step s4_wakeup_s2
{
SELECT injection_points_detach('exec-insert-before-insert-speculative');
SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
}
step s4_wakeup_to_set_dead
{
SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
}
permutation
s3_setup_wait_before_set_dead
s3_start_reindex(s1_start_upsert, s2_start_upsert)
s1_start_upsert(s4_wakeup_s2)
s4_wakeup_to_set_dead
s2_start_upsert(s1_start_upsert)
s4_wakeup_s1
s4_wakeup_s2
permutation
s3_setup_wait_before_swap
s3_start_reindex(s1_start_upsert, s2_start_upsert)
s1_start_upsert(s4_wakeup_s2)
s4_wakeup_to_swap
s2_start_upsert(s1_start_upsert)
s4_wakeup_s2
s4_wakeup_s1
permutation
s3_setup_wait_before_set_dead
s3_start_reindex(s1_start_upsert, s2_start_upsert)
s1_start_upsert(s4_wakeup_s2)
s2_start_upsert(s1_start_upsert)
s4_wakeup_s1
s4_wakeup_to_set_dead
s4_wakeup_s2

View File

@ -5,6 +5,9 @@ TAP_TESTS = 1
EXTRA_INSTALL=src/test/modules/injection_points \
contrib/test_decoding
# The injection points are cluster-wide, so disable installcheck
NO_INSTALLCHECK = 1
export enable_injection_points
ifdef USE_PGXS

View File

@ -18,6 +18,9 @@ tests += {
't/007_catcache_inval.pl',
't/008_replslot_single_user.pl',
't/009_log_temp_files.pl',
't/010_index_concurrently_upsert.pl',
],
# The injection points are cluster-wide, so disable installcheck
'runningcheck': false,
},
}

View File

@ -0,0 +1,902 @@
# Copyright (c) 2025, PostgreSQL Global Development Group
# Test INSERT ON CONFLICT DO UPDATE behavior concurrent with
# CREATE INDEX CONCURRENTLY and REINDEX CONCURRENTLY.
#
# These tests verify the fix for "duplicate key value violates unique
# constraint" errors that occurred when infer_arbiter_indexes() only considered
# indisvalid indexes, causing different transactions to use different arbiter
# indexes.
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
plan skip_all => 'Injection points not supported by this build'
unless $ENV{enable_injection_points} eq 'yes';
# Node initialization
my $node = PostgreSQL::Test::Cluster->new('node');
$node->init();
$node->start();
# Check if the extension injection_points is available
plan skip_all => 'Extension injection_points not installed'
unless $node->check_extension('injection_points');
$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
$node->safe_psql(
'postgres', q[
CREATE SCHEMA test;
CREATE UNLOGGED TABLE test.tblpk (i int PRIMARY KEY, updated_at timestamp);
ALTER TABLE test.tblpk SET (parallel_workers=0);
CREATE TABLE test.tblparted(i int primary key, updated_at timestamp) PARTITION BY RANGE (i);
CREATE TABLE test.tbl_partition PARTITION OF test.tblparted
FOR VALUES FROM (0) TO (10000)
WITH (parallel_workers = 0);
CREATE UNLOGGED TABLE test.tblexpr(i int, updated_at timestamp);
CREATE UNIQUE INDEX tbl_pkey_special ON test.tblexpr(abs(i)) WHERE i < 1000;
ALTER TABLE test.tblexpr SET (parallel_workers=0);
]);
############################################################################
note('Test: REINDEX CONCURRENTLY + UPSERT (wakeup at set-dead phase)');
# Create sessions with on_error_stop => 0 so psql doesn't exit on SQL errors.
# This allows us to collect stderr and detect errors after the test completes.
my $s1 = $node->background_psql('postgres', on_error_stop => 0);
my $s2 = $node->background_psql('postgres', on_error_stop => 0);
my $s3 = $node->background_psql('postgres', on_error_stop => 0);
# Setup injection points for each session
$s1->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
]);
$s2->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
]);
$s3->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
]);
# s3 starts REINDEX (will block on reindex-relation-concurrently-before-set-dead)
$s3->query_until(
qr/starting_reindex/, q[
\echo starting_reindex
REINDEX INDEX CONCURRENTLY test.tblpk_pkey;
]);
# Wait for s3 to hit injection point
ok_injection_point($node, 'reindex-relation-concurrently-before-set-dead');
# s1 starts UPSERT (will block on check-exclusion-or-unique-constraint-no-conflict)
$s1->query_until(
qr/starting_upsert_s1/, q[
\echo starting_upsert_s1
INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
# Wait for s1 to hit injection point
ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
# Wakeup s3 to continue (reindex-relation-concurrently-before-set-dead)
wakeup_injection_point($node,
'reindex-relation-concurrently-before-set-dead');
# s2 starts UPSERT (will block on exec-insert-before-insert-speculative)
$s2->query_until(
qr/starting_upsert_s2/, q[
\echo starting_upsert_s2
INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
# Wait for s2 to hit injection point
ok_injection_point($node, 'exec-insert-before-insert-speculative');
# Wakeup s1 (check-exclusion-or-unique-constraint-no-conflict)
wakeup_injection_point($node,
'check-exclusion-or-unique-constraint-no-conflict');
# Wakeup s2 (exec-insert-before-insert-speculative)
wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
clean_safe_quit_ok($s1, $s2, $s3);
# Cleanup test 1
$node->safe_psql('postgres', 'TRUNCATE TABLE test.tblpk');
############################################################################
note('Test: REINDEX CONCURRENTLY + UPSERT (wakeup at swap phase)');
$s1 = $node->background_psql('postgres', on_error_stop => 0);
$s2 = $node->background_psql('postgres', on_error_stop => 0);
$s3 = $node->background_psql('postgres', on_error_stop => 0);
$s1->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
]);
$s2->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
]);
$s3->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
]);
$s3->query_until(
qr/starting_reindex/, q[
\echo starting_reindex
REINDEX INDEX CONCURRENTLY test.tblpk_pkey;
]);
ok_injection_point($node, 'reindex-relation-concurrently-before-swap');
$s1->query_until(
qr/starting_upsert_s1/, q[
\echo starting_upsert_s1
INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
wakeup_injection_point($node, 'reindex-relation-concurrently-before-swap');
$s2->query_until(
qr/starting_upsert_s2/, q[
\echo starting_upsert_s2
INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'exec-insert-before-insert-speculative');
wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
wakeup_injection_point($node,
'check-exclusion-or-unique-constraint-no-conflict');
clean_safe_quit_ok($s1, $s2, $s3);
$node->safe_psql('postgres', 'TRUNCATE TABLE test.tblpk');
############################################################################
note('Test: REINDEX CONCURRENTLY + UPSERT (s1 wakes before reindex)');
$s1 = $node->background_psql('postgres', on_error_stop => 0);
$s2 = $node->background_psql('postgres', on_error_stop => 0);
$s3 = $node->background_psql('postgres', on_error_stop => 0);
$s1->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
]);
$s2->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
]);
$s3->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
]);
$s3->query_until(
qr/starting_reindex/, q[
\echo starting_reindex
REINDEX INDEX CONCURRENTLY test.tblpk_pkey;
]);
ok_injection_point($node, 'reindex-relation-concurrently-before-set-dead');
$s1->query_until(
qr/starting_upsert_s1/, q[
\echo starting_upsert_s1
INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
# Start s2 BEFORE waking reindex (key difference from permutation 1)
$s2->query_until(
qr/starting_upsert_s2/, q[
\echo starting_upsert_s2
INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'exec-insert-before-insert-speculative');
# Wake s1 first, then reindex, then s2
wakeup_injection_point($node,
'check-exclusion-or-unique-constraint-no-conflict');
wakeup_injection_point($node,
'reindex-relation-concurrently-before-set-dead');
wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
clean_safe_quit_ok($s1, $s2, $s3);
$node->safe_psql('postgres', 'TRUNCATE TABLE test.tblpk');
############################################################################
note('Test: REINDEX + UPSERT ON CONSTRAINT (set-dead phase)');
$s1 = $node->background_psql('postgres', on_error_stop => 0);
$s2 = $node->background_psql('postgres', on_error_stop => 0);
$s3 = $node->background_psql('postgres', on_error_stop => 0);
$s1->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
]);
$s2->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
]);
$s3->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
]);
$s3->query_until(
qr/starting_reindex/, q[
\echo starting_reindex
REINDEX INDEX CONCURRENTLY test.tblpk_pkey;
]);
ok_injection_point($node, 'reindex-relation-concurrently-before-set-dead');
$s1->query_until(
qr/starting_upsert_s1/, q[
\echo starting_upsert_s1
INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
wakeup_injection_point($node,
'reindex-relation-concurrently-before-set-dead');
$s2->query_until(
qr/starting_upsert_s2/, q[
\echo starting_upsert_s2
INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'exec-insert-before-insert-speculative');
wakeup_injection_point($node,
'check-exclusion-or-unique-constraint-no-conflict');
wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
clean_safe_quit_ok($s1, $s2, $s3);
$node->safe_psql('postgres', 'TRUNCATE TABLE test.tblpk');
############################################################################
note('Test: REINDEX + UPSERT ON CONSTRAINT (swap phase)');
$s1 = $node->background_psql('postgres', on_error_stop => 0);
$s2 = $node->background_psql('postgres', on_error_stop => 0);
$s3 = $node->background_psql('postgres', on_error_stop => 0);
$s1->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
]);
$s2->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
]);
$s3->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
]);
$s3->query_until(
qr/starting_reindex/, q[
\echo starting_reindex
REINDEX INDEX CONCURRENTLY test.tblpk_pkey;
]);
ok_injection_point($node, 'reindex-relation-concurrently-before-swap');
$s1->query_until(
qr/starting_upsert_s1/, q[
\echo starting_upsert_s1
INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
wakeup_injection_point($node, 'reindex-relation-concurrently-before-swap');
$s2->query_until(
qr/starting_upsert_s2/, q[
\echo starting_upsert_s2
INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'exec-insert-before-insert-speculative');
wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
wakeup_injection_point($node,
'check-exclusion-or-unique-constraint-no-conflict');
clean_safe_quit_ok($s1, $s2, $s3);
$node->safe_psql('postgres', 'TRUNCATE TABLE test.tblpk');
############################################################################
note('Test: REINDEX + UPSERT ON CONSTRAINT (s1 wakes before reindex)');
$s1 = $node->background_psql('postgres', on_error_stop => 0);
$s2 = $node->background_psql('postgres', on_error_stop => 0);
$s3 = $node->background_psql('postgres', on_error_stop => 0);
$s1->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
]);
$s2->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
]);
$s3->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
]);
$s3->query_until(
qr/starting_reindex/, q[
\echo starting_reindex
REINDEX INDEX CONCURRENTLY test.tblpk_pkey;
]);
ok_injection_point($node, 'reindex-relation-concurrently-before-set-dead');
$s1->query_until(
qr/starting_upsert_s1/, q[
\echo starting_upsert_s1
INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
# Start s2 BEFORE waking reindex
$s2->query_until(
qr/starting_upsert_s2/, q[
\echo starting_upsert_s2
INSERT INTO test.tblpk VALUES (13, now()) ON CONFLICT ON CONSTRAINT tblpk_pkey DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'exec-insert-before-insert-speculative');
# Wake s1 first, then reindex, then s2
wakeup_injection_point($node,
'check-exclusion-or-unique-constraint-no-conflict');
wakeup_injection_point($node,
'reindex-relation-concurrently-before-set-dead');
wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
clean_safe_quit_ok($s1, $s2, $s3);
$node->safe_psql('postgres', 'TRUNCATE TABLE test.tblpk');
############################################################################
note('Test: REINDEX on partitioned table (set-dead phase)');
$s1 = $node->background_psql('postgres', on_error_stop => 0);
$s2 = $node->background_psql('postgres', on_error_stop => 0);
$s3 = $node->background_psql('postgres', on_error_stop => 0);
$s1->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
]);
$s2->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
]);
$s3->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
]);
$s3->query_until(
qr/starting_reindex/, q[
\echo starting_reindex
REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
]);
ok_injection_point($node, 'reindex-relation-concurrently-before-set-dead');
$s1->query_until(
qr/starting_upsert_s1/, q[
\echo starting_upsert_s1
INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
wakeup_injection_point($node,
'reindex-relation-concurrently-before-set-dead');
$s2->query_until(
qr/starting_upsert_s2/, q[
\echo starting_upsert_s2
INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'exec-insert-before-insert-speculative');
wakeup_injection_point($node,
'check-exclusion-or-unique-constraint-no-conflict');
wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
clean_safe_quit_ok($s1, $s2, $s3);
$node->safe_psql('postgres', 'TRUNCATE TABLE test.tblparted');
############################################################################
note('Test: REINDEX on partitioned table (swap phase)');
$s1 = $node->background_psql('postgres', on_error_stop => 0);
$s2 = $node->background_psql('postgres', on_error_stop => 0);
$s3 = $node->background_psql('postgres', on_error_stop => 0);
$s1->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
]);
$s2->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
]);
$s3->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
]);
$s3->query_until(
qr/starting_reindex/, q[
\echo starting_reindex
REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
]);
ok_injection_point($node, 'reindex-relation-concurrently-before-swap');
$s1->query_until(
qr/starting_upsert_s1/, q[
\echo starting_upsert_s1
INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
wakeup_injection_point($node, 'reindex-relation-concurrently-before-swap');
$s2->query_until(
qr/starting_upsert_s2/, q[
\echo starting_upsert_s2
INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'exec-insert-before-insert-speculative');
wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
wakeup_injection_point($node,
'check-exclusion-or-unique-constraint-no-conflict');
clean_safe_quit_ok($s1, $s2, $s3);
$node->safe_psql('postgres', 'TRUNCATE TABLE test.tblparted');
############################################################################
note('Test: REINDEX on partitioned table (s1 wakes before reindex)');
$s1 = $node->background_psql('postgres', on_error_stop => 0);
$s2 = $node->background_psql('postgres', on_error_stop => 0);
$s3 = $node->background_psql('postgres', on_error_stop => 0);
$s1->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
]);
$s2->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
]);
$s3->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
]);
$s3->query_until(
qr/starting_reindex/, q[
\echo starting_reindex
REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
]);
ok_injection_point($node, 'reindex-relation-concurrently-before-set-dead');
$s1->query_until(
qr/starting_upsert_s1/, q[
\echo starting_upsert_s1
INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
# Start s2 BEFORE waking reindex
$s2->query_until(
qr/starting_upsert_s2/, q[
\echo starting_upsert_s2
INSERT INTO test.tblparted VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'exec-insert-before-insert-speculative');
# Wake s1 first, then reindex, then s2
wakeup_injection_point($node,
'check-exclusion-or-unique-constraint-no-conflict');
wakeup_injection_point($node,
'reindex-relation-concurrently-before-set-dead');
wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
clean_safe_quit_ok($s1, $s2, $s3);
$node->safe_psql('postgres', 'TRUNCATE TABLE test.tblparted');
############################################################################
note('Test: CREATE INDEX CONCURRENTLY + UPSERT');
# Uses invalidate-catalog-snapshot-end to test catalog invalidation
# during UPSERT
$s1 = $node->background_psql('postgres', on_error_stop => 0);
$s2 = $node->background_psql('postgres', on_error_stop => 0);
$s3 = $node->background_psql('postgres', on_error_stop => 0);
my $s1_pid = $s1->query_safe('SELECT pg_backend_pid()');
# s1 attaches BOTH injection points - the unique constraint check AND catalog snapshot
$s1->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
]);
$s1->query_until(
qr/attaching_injection_point/, q[
\echo attaching_injection_point
SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
]);
# In case of CLOBBER_CACHE_ALWAYS - s1 may hit the injection point during attach.
# Wait for s1 to become idle (attach completed) or wakeup if stuck on injection point.
if (!wait_for_idle($node, $s1_pid))
{
ok_injection_point(
$node,
'invalidate-catalog-snapshot-end',
's1 hit injection point during attach (CLOBBER_CACHE_ALWAYS)');
$node->safe_psql(
'postgres', q[
SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
]);
}
$s2->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
]);
$s3->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('define-index-before-set-valid', 'wait');
]);
# s3: Start CREATE INDEX CONCURRENTLY (blocks on define-index-before-set-valid)
$s3->query_until(
qr/starting_create_index/, q[
\echo starting_create_index
CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_duplicate ON test.tblpk(i);
]);
ok_injection_point($node, 'define-index-before-set-valid');
# s1: Start UPSERT (blocks on invalidate-catalog-snapshot-end)
$s1->query_until(
qr/starting_upsert_s1/, q[
\echo starting_upsert_s1
INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'invalidate-catalog-snapshot-end');
# Wakeup s3 (CREATE INDEX continues, triggers catalog invalidation)
wakeup_injection_point($node, 'define-index-before-set-valid');
# s2: Start UPSERT (blocks on exec-insert-before-insert-speculative)
$s2->query_until(
qr/starting_upsert_s2/, q[
\echo starting_upsert_s2
INSERT INTO test.tblpk VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'exec-insert-before-insert-speculative');
wakeup_injection_point($node, 'invalidate-catalog-snapshot-end');
ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
wakeup_injection_point($node,
'check-exclusion-or-unique-constraint-no-conflict');
clean_safe_quit_ok($s1, $s2, $s3);
$node->safe_psql('postgres', 'TRUNCATE TABLE test.tblparted');
############################################################################
note('Test: CREATE INDEX CONCURRENTLY on partial index + UPSERT');
# Uses invalidate-catalog-snapshot-end to test catalog invalidation during UPSERT
$s1 = $node->background_psql('postgres', on_error_stop => 0);
$s2 = $node->background_psql('postgres', on_error_stop => 0);
$s3 = $node->background_psql('postgres', on_error_stop => 0);
$s1_pid = $s1->query_safe('SELECT pg_backend_pid()');
# s1 attaches BOTH injection points - the unique constraint check AND catalog snapshot
$s1->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
]);
$s1->query_until(
qr/attaching_injection_point/, q[
\echo attaching_injection_point
SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
]);
# In case of CLOBBER_CACHE_ALWAYS - s1 may hit the injection point during attach.
# Wait for s1 to become idle (attach completed) or wakeup if stuck on injection point.
if (!wait_for_idle($node, $s1_pid))
{
ok_injection_point($node, 'invalidate-catalog-snapshot-end',
'Test 8: s1 hit injection point during attach (CLOBBER_CACHE_ALWAYS)'
);
$node->safe_psql(
'postgres', q[
SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
]);
}
$s2->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
]);
$s3->query_safe(
q[
SELECT injection_points_set_local();
SELECT injection_points_attach('define-index-before-set-valid', 'wait');
]);
# s3: Start CREATE INDEX CONCURRENTLY (blocks on define-index-before-set-valid)
$s3->query_until(
qr/starting_create_index/, q[
\echo starting_create_index
CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_special_duplicate ON test.tblexpr(abs(i)) WHERE i < 10000;
]);
ok_injection_point($node, 'define-index-before-set-valid');
# s1: Start UPSERT (blocks on invalidate-catalog-snapshot-end)
$s1->query_until(
qr/starting_upsert_s1/, q[
\echo starting_upsert_s1
INSERT INTO test.tblexpr VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'invalidate-catalog-snapshot-end');
# Wakeup s3 (CREATE INDEX continues, triggers catalog invalidation)
wakeup_injection_point($node, 'define-index-before-set-valid');
# s2: Start UPSERT (blocks on exec-insert-before-insert-speculative)
$s2->query_until(
qr/starting_upsert_s2/, q[
\echo starting_upsert_s2
INSERT INTO test.tblexpr VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
]);
ok_injection_point($node, 'exec-insert-before-insert-speculative');
wakeup_injection_point($node, 'invalidate-catalog-snapshot-end');
ok_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
wakeup_injection_point($node,
'check-exclusion-or-unique-constraint-no-conflict');
clean_safe_quit_ok($s1, $s2, $s3);
$node->safe_psql('postgres', 'TRUNCATE TABLE test.tblexpr');
done_testing();
############################################################################
# Helper functions
#
############################################################################
# Helper: Wait for a session to hit an injection point.
# Optional second argument is timeout in seconds.
# Returns true if found, false if timeout.
# On timeout, logs diagnostic information about all active queries.
sub wait_for_injection_point
{
my ($node, $point_name, $timeout) = @_;
$timeout //= $PostgreSQL::Test::Utils::timeout_default;
for (my $elapsed = 0; $elapsed < $timeout * 10; $elapsed++)
{
my $pid = $node->safe_psql(
'postgres', qq[
SELECT pid FROM pg_stat_activity
WHERE wait_event_type = 'InjectionPoint'
AND wait_event = '$point_name'
LIMIT 1;
]);
return 1 if $pid ne '';
sleep(0.1);
}
# Timeout - report diagnostic information
my $activity = $node->safe_psql(
'postgres', q[
SELECT format('pid=%s, state=%s, wait_event_type=%s, wait_event=%s, backend_xmin=%s, backend_xid=%s, query=%s',
pid, state, wait_event_type, wait_event, backend_xmin, backend_xid, left(query, 100))
FROM pg_stat_activity
ORDER BY pid;
]);
diag( "wait_for_injection_point timeout waiting for: $point_name\n"
. "Current queries in pg_stat_activity:\n$activity");
return 0;
}
# Test helper: ok() a wait for the given injection point
# Third argument is an optional test name.
sub ok_injection_point
{
my ($node, $injection_point, $testname) = @_;
$testname //= "hit injection point $injection_point";
ok(wait_for_injection_point($node, $injection_point), $testname);
}
# Helper: Wait for a specific backend to become idle.
# Returns true if idle, false if timeout.
sub wait_for_idle
{
my ($node, $pid, $timeout) = @_;
$timeout //= $PostgreSQL::Test::Utils::timeout_default;
for (my $elapsed = 0; $elapsed < $timeout * 10; $elapsed++)
{
my $state = $node->safe_psql(
'postgres', qq[
SELECT state FROM pg_stat_activity WHERE pid = $pid;
]);
return 1 if $state eq 'idle';
sleep(0.1);
}
return 0;
}
# Helper: Detach and wakeup an injection point
sub wakeup_injection_point
{
my ($node, $point_name) = @_;
$node->safe_psql(
'postgres', qq[
SELECT injection_points_detach('$point_name');
SELECT injection_points_wakeup('$point_name');
]);
}
# Wait for any pending query to complete, capture stderr, and close the session.
# Returns the stderr output (excluding internal markers).
sub safe_quit
{
my ($session) = @_;
# Send a marker and wait for it to ensure any pending query completes
my $banner = "safe_quit_marker";
my $banner_match = qr/(^|\n)$banner\r?\n/;
$session->{stdin} .= "\\echo $banner\n\\warn $banner\n";
pump_until(
$session->{run}, $session->{timeout},
\$session->{stdout}, $banner_match);
pump_until(
$session->{run}, $session->{timeout},
\$session->{stderr}, $banner_match);
# Capture stderr (excluding the banner)
my $stderr = $session->{stderr};
$stderr =~ s/$banner_match//;
# Close the session
$session->quit;
return $stderr;
}
# Helper function: verify that the given sessions exit cleanly.
sub clean_safe_quit_ok
{
my $i = 1;
foreach my $session (@_)
{
is(safe_quit($session), '', "session " . $i++ . " quit cleanly");
}
}