nocfbot_v2-0001-Replace-flaky-isolation-tests-with-a-TAP-te.patch
text/x-patch
Filename: nocfbot_v2-0001-Replace-flaky-isolation-tests-with-a-TAP-te.patch
Type: text/x-patch
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0001
Subject: Replace flaky isolation tests with a TAP test for concurrent upserts
| File | + | − |
|---|---|---|
| src/test/modules/injection_points/expected/index-concurrently-upsert_1.out | 0 | 124 |
| src/test/modules/injection_points/expected/index-concurrently-upsert.out | 0 | 123 |
| src/test/modules/injection_points/expected/index-concurrently-upsert-predicate_1.out | 0 | 124 |
| src/test/modules/injection_points/expected/index-concurrently-upsert-predicate.out | 0 | 123 |
| src/test/modules/injection_points/expected/reindex-concurrently-upsert-on-constraint.out | 0 | 238 |
| src/test/modules/injection_points/expected/reindex-concurrently-upsert.out | 0 | 238 |
| src/test/modules/injection_points/expected/reindex-concurrently-upsert-partitioned.out | 0 | 238 |
| src/test/modules/injection_points/Makefile | 0 | 8 |
| src/test/modules/injection_points/meson.build | 0 | 6 |
| src/test/modules/injection_points/specs/index-concurrently-upsert-predicate.spec | 0 | 124 |
| src/test/modules/injection_points/specs/index-concurrently-upsert.spec | 0 | 123 |
| src/test/modules/injection_points/specs/reindex-concurrently-upsert-on-constraint.spec | 0 | 110 |
| src/test/modules/injection_points/specs/reindex-concurrently-upsert-partitioned.spec | 0 | 113 |
| src/test/modules/injection_points/specs/reindex-concurrently-upsert.spec | 0 | 111 |
| src/test/modules/test_misc/Makefile | 3 | 0 |
| src/test/modules/test_misc/meson.build | 3 | 0 |
| src/test/modules/test_misc/t/010_index_concurrently_upsert.pl | 894 | 0 |
From f532028260b8da0697772d0f5c0c16c58fc15f02 Mon Sep 17 00:00:00 2001
From: Mikhail Nikalayeu <mihailnikalayeu@gmail.com>
Date: Mon, 15 Dec 2025 12:03:04 +0100
Subject: [PATCH vnocfbo2] Replace flaky isolation tests with a TAP test for
concurrent upserts
The isolation tests verifying INSERT ON CONFLICT behavior during
concurrent index creation/reindexing were prone to flakiness. The
timing dependencies on injection points were difficult to guarantee
in the isolation tester.
Remove the disabled isolation tests and their specs/expected output.
Add TAP test '010_index_concurrently_upsert.pl' to
cover these scenarios more reliably. The TAP test now includes better
session management and more robust injection point
waiting logic.
---
src/test/modules/injection_points/Makefile | 8 -
.../index-concurrently-upsert-predicate.out | 123 ---
.../index-concurrently-upsert-predicate_1.out | 124 ---
.../expected/index-concurrently-upsert.out | 123 ---
.../expected/index-concurrently-upsert_1.out | 124 ---
...ndex-concurrently-upsert-on-constraint.out | 238 -----
...eindex-concurrently-upsert-partitioned.out | 238 -----
.../expected/reindex-concurrently-upsert.out | 238 -----
src/test/modules/injection_points/meson.build | 6 -
.../index-concurrently-upsert-predicate.spec | 124 ---
.../specs/index-concurrently-upsert.spec | 123 ---
...dex-concurrently-upsert-on-constraint.spec | 110 ---
...index-concurrently-upsert-partitioned.spec | 113 ---
.../specs/reindex-concurrently-upsert.spec | 111 ---
src/test/modules/test_misc/Makefile | 3 +
src/test/modules/test_misc/meson.build | 3 +
.../t/010_index_concurrently_upsert.pl | 894 ++++++++++++++++++
17 files changed, 900 insertions(+), 1803 deletions(-)
delete mode 100644 src/test/modules/injection_points/expected/index-concurrently-upsert-predicate.out
delete mode 100644 src/test/modules/injection_points/expected/index-concurrently-upsert-predicate_1.out
delete mode 100644 src/test/modules/injection_points/expected/index-concurrently-upsert.out
delete mode 100644 src/test/modules/injection_points/expected/index-concurrently-upsert_1.out
delete mode 100644 src/test/modules/injection_points/expected/reindex-concurrently-upsert-on-constraint.out
delete mode 100644 src/test/modules/injection_points/expected/reindex-concurrently-upsert-partitioned.out
delete mode 100644 src/test/modules/injection_points/expected/reindex-concurrently-upsert.out
delete mode 100644 src/test/modules/injection_points/specs/index-concurrently-upsert-predicate.spec
delete mode 100644 src/test/modules/injection_points/specs/index-concurrently-upsert.spec
delete mode 100644 src/test/modules/injection_points/specs/reindex-concurrently-upsert-on-constraint.spec
delete mode 100644 src/test/modules/injection_points/specs/reindex-concurrently-upsert-partitioned.spec
delete mode 100644 src/test/modules/injection_points/specs/reindex-concurrently-upsert.spec
create mode 100644 src/test/modules/test_misc/t/010_index_concurrently_upsert.pl
diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile
index bfdb3f53377..3cb50d13e52 100644
--- a/src/test/modules/injection_points/Makefile
+++ b/src/test/modules/injection_points/Makefile
@@ -16,14 +16,6 @@ ISOLATION = basic \
inplace \
syscache-update-pruned
-# 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
diff --git a/src/test/modules/injection_points/expected/index-concurrently-upsert-predicate.out b/src/test/modules/injection_points/expected/index-concurrently-upsert-predicate.out
deleted file mode 100644
index 77e7d1a7815..00000000000
--- a/src/test/modules/injection_points/expected/index-concurrently-upsert-predicate.out
+++ /dev/null
@@ -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>
diff --git a/src/test/modules/injection_points/expected/index-concurrently-upsert-predicate_1.out b/src/test/modules/injection_points/expected/index-concurrently-upsert-predicate_1.out
deleted file mode 100644
index e72848d6a78..00000000000
--- a/src/test/modules/injection_points/expected/index-concurrently-upsert-predicate_1.out
+++ /dev/null
@@ -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>
diff --git a/src/test/modules/injection_points/expected/index-concurrently-upsert.out b/src/test/modules/injection_points/expected/index-concurrently-upsert.out
deleted file mode 100644
index a2ef122625c..00000000000
--- a/src/test/modules/injection_points/expected/index-concurrently-upsert.out
+++ /dev/null
@@ -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>
diff --git a/src/test/modules/injection_points/expected/index-concurrently-upsert_1.out b/src/test/modules/injection_points/expected/index-concurrently-upsert_1.out
deleted file mode 100644
index ee3b6641b90..00000000000
--- a/src/test/modules/injection_points/expected/index-concurrently-upsert_1.out
+++ /dev/null
@@ -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>
diff --git a/src/test/modules/injection_points/expected/reindex-concurrently-upsert-on-constraint.out b/src/test/modules/injection_points/expected/reindex-concurrently-upsert-on-constraint.out
deleted file mode 100644
index c1ac1f77c61..00000000000
--- a/src/test/modules/injection_points/expected/reindex-concurrently-upsert-on-constraint.out
+++ /dev/null
@@ -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>
diff --git a/src/test/modules/injection_points/expected/reindex-concurrently-upsert-partitioned.out b/src/test/modules/injection_points/expected/reindex-concurrently-upsert-partitioned.out
deleted file mode 100644
index 4c79a43d986..00000000000
--- a/src/test/modules/injection_points/expected/reindex-concurrently-upsert-partitioned.out
+++ /dev/null
@@ -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>
diff --git a/src/test/modules/injection_points/expected/reindex-concurrently-upsert.out b/src/test/modules/injection_points/expected/reindex-concurrently-upsert.out
deleted file mode 100644
index c9cc9989d02..00000000000
--- a/src/test/modules/injection_points/expected/reindex-concurrently-upsert.out
+++ /dev/null
@@ -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>
diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build
index 493e11053dc..2c6cf54a33e 100644
--- a/src/test/modules/injection_points/meson.build
+++ b/src/test/modules/injection_points/meson.build
@@ -46,12 +46,6 @@ tests += {
'basic',
'inplace',
'syscache-update-pruned',
- # 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
diff --git a/src/test/modules/injection_points/specs/index-concurrently-upsert-predicate.spec b/src/test/modules/injection_points/specs/index-concurrently-upsert-predicate.spec
deleted file mode 100644
index d9b8d27fd1f..00000000000
--- a/src/test/modules/injection_points/specs/index-concurrently-upsert-predicate.spec
+++ /dev/null
@@ -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
diff --git a/src/test/modules/injection_points/specs/index-concurrently-upsert.spec b/src/test/modules/injection_points/specs/index-concurrently-upsert.spec
deleted file mode 100644
index 6e08af74a93..00000000000
--- a/src/test/modules/injection_points/specs/index-concurrently-upsert.spec
+++ /dev/null
@@ -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
diff --git a/src/test/modules/injection_points/specs/reindex-concurrently-upsert-on-constraint.spec b/src/test/modules/injection_points/specs/reindex-concurrently-upsert-on-constraint.spec
deleted file mode 100644
index 4bbdda3cf04..00000000000
--- a/src/test/modules/injection_points/specs/reindex-concurrently-upsert-on-constraint.spec
+++ /dev/null
@@ -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
diff --git a/src/test/modules/injection_points/specs/reindex-concurrently-upsert-partitioned.spec b/src/test/modules/injection_points/specs/reindex-concurrently-upsert-partitioned.spec
deleted file mode 100644
index c3504b9ef38..00000000000
--- a/src/test/modules/injection_points/specs/reindex-concurrently-upsert-partitioned.spec
+++ /dev/null
@@ -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
diff --git a/src/test/modules/injection_points/specs/reindex-concurrently-upsert.spec b/src/test/modules/injection_points/specs/reindex-concurrently-upsert.spec
deleted file mode 100644
index 1b043a48ff4..00000000000
--- a/src/test/modules/injection_points/specs/reindex-concurrently-upsert.spec
+++ /dev/null
@@ -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
diff --git a/src/test/modules/test_misc/Makefile b/src/test/modules/test_misc/Makefile
index 399b9094a38..fedbef071ef 100644
--- a/src/test/modules/test_misc/Makefile
+++ b/src/test/modules/test_misc/Makefile
@@ -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
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index f258bf1ccd9..129c4ae587a 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -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,
},
}
diff --git a/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl b/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl
new file mode 100644
index 00000000000..55ea384edb3
--- /dev/null
+++ b/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl
@@ -0,0 +1,894 @@
+
+# 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;
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+ plan skip_all => 'Injection points not supported by this build';
+}
+
+# Node initialization
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init();
+$node->start;
+
+# Check if the extension injection_points is available
+if (!$node->check_extension('injection_points'))
+{
+ plan skip_all => 'Extension injection_points not installed';
+}
+
+$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
+
+# 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 //= 120;
+
+ for (my $elapsed = 0; $elapsed < $timeout; $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(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;
+}
+
+# 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 //= 15;
+
+ for (my $elapsed = 0; $elapsed < $timeout; $elapsed++)
+ {
+ my $state = $node->safe_psql('postgres', qq[
+ SELECT state FROM pg_stat_activity WHERE pid = $pid;
+ ]);
+ return 1 if $state eq 'idle';
+ sleep(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;
+}
+
+###############################################################################
+# Test 1: REINDEX CONCURRENTLY + UPSERT (wakeup at set-dead phase)
+# Based on reindex-concurrently-upsert.spec
+###############################################################################
+
+$node->safe_psql(
+ 'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+# 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.tbl_pkey;
+]);
+
+# Wait for s3 to hit injection point
+ok(wait_for_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.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+# Wait for s1 to hit injection point
+ok(wait_for_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.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+# Wait for s2 to hit injection point
+ok(wait_for_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');
+
+is(safe_quit($s1), '', 'Test 1 (REINDEX swap): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 1 (REINDEX swap): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 1 (REINDEX swap): session s3 quit successfully');
+
+# Cleanup test 1
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 2: REINDEX CONCURRENTLY + UPSERT (wakeup at swap phase)
+###############################################################################
+
+$node->safe_psql(
+ 'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+$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_pkey;
+]);
+
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-swap'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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');
+
+is(safe_quit($s1), '', 'Test 2 (REINDEX set-dead): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 2 (REINDEX set-dead): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 2 (REINDEX set-dead): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 2b: REINDEX CONCURRENTLY + UPSERT (permutation 3: s1 wakes before reindex)
+# Different timing: s2 starts, then s1 wakes, then reindex wakes, then s2 wakes
+###############################################################################
+
+$node->safe_psql(
+ 'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+$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_pkey;
+]);
+
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-set-dead'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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');
+
+is(safe_quit($s1), '', 'Test 2b (REINDEX perm3): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 2b (REINDEX perm3): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 2b (REINDEX perm3): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 3: REINDEX + UPSERT ON CONSTRAINT (set-dead phase)
+# Based on reindex-concurrently-upsert-on-constraint.spec
+###############################################################################
+
+$node->safe_psql(
+ 'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+$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_pkey;
+]);
+
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-set-dead'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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');
+
+is(safe_quit($s1), '', 'Test 3 (ON CONSTRAINT set-dead): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 3 (ON CONSTRAINT set-dead): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 3 (ON CONSTRAINT set-dead): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 4: REINDEX + UPSERT ON CONSTRAINT (swap phase)
+###############################################################################
+
+$node->safe_psql(
+ 'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+$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_pkey;
+]);
+
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-swap'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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');
+
+is(safe_quit($s1), '', 'Test 4 (ON CONSTRAINT swap): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 4 (ON CONSTRAINT swap): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 4 (ON CONSTRAINT swap): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 4b: REINDEX + UPSERT ON CONSTRAINT (permutation 3: s1 wakes before reindex)
+###############################################################################
+
+$node->safe_psql(
+ 'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+$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_pkey;
+]);
+
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-set-dead'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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');
+
+is(safe_quit($s1), '', 'Test 4b (ON CONSTRAINT perm3): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 4b (ON CONSTRAINT perm3): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 4b (ON CONSTRAINT perm3): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 5: REINDEX on partitioned table (set-dead phase)
+# Based on reindex-concurrently-upsert-partitioned.spec
+###############################################################################
+
+$node->safe_psql(
+ 'postgres', q[
+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);
+]);
+
+$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(wait_for_injection_point($node, 'reindex-relation-concurrently-before-set-dead'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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');
+
+is(safe_quit($s1), '', 'Test 5 (partitioned set-dead): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 5 (partitioned set-dead): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 5 (partitioned set-dead): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 6: REINDEX on partitioned table (swap phase)
+###############################################################################
+
+$node->safe_psql(
+ 'postgres', q[
+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);
+]);
+
+$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(wait_for_injection_point($node, 'reindex-relation-concurrently-before-swap'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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');
+
+is(safe_quit($s1), '', 'Test 6 (partitioned swap): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 6 (partitioned swap): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 6 (partitioned swap): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 6b: REINDEX on partitioned table (permutation 3: s1 wakes before reindex)
+###############################################################################
+
+$node->safe_psql(
+ 'postgres', q[
+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);
+]);
+
+$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(wait_for_injection_point($node, 'reindex-relation-concurrently-before-set-dead'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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');
+
+is(safe_quit($s1), '', 'Test 6b (partitioned perm3): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 6b (partitioned perm3): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 6b (partitioned perm3): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 7: CREATE INDEX CONCURRENTLY + UPSERT
+# Based on index-concurrently-upsert.spec
+# Uses invalidate-catalog-snapshot-end to test catalog invalidation during UPSERT
+###############################################################################
+
+$node->safe_psql(
+ 'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl(i int primary key, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+$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(wait_for_injection_point($node, 'invalidate-catalog-snapshot-end'),
+ 'Test 7: 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.tbl(i);
+]);
+
+ok(wait_for_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.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'exec-insert-before-insert-speculative'));
+
+wakeup_injection_point($node, 'invalidate-catalog-snapshot-end');
+
+ok(wait_for_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');
+
+is(safe_quit($s1), '', 'Test 7 (CREATE INDEX): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 7 (CREATE INDEX): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 7 (CREATE INDEX): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 8: CREATE INDEX CONCURRENTLY on partial index + UPSERT
+# Based on index-concurrently-upsert-predicate.spec
+# Uses invalidate-catalog-snapshot-end to test catalog invalidation during UPSERT
+###############################################################################
+
+$node->safe_psql(
+ 'postgres', q[
+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);
+]);
+
+$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(wait_for_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.tbl(abs(i)) WHERE i < 10000;
+]);
+
+ok(wait_for_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.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_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.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'exec-insert-before-insert-speculative'));
+
+wakeup_injection_point($node, 'invalidate-catalog-snapshot-end');
+
+ok(wait_for_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');
+
+is(safe_quit($s1), '', 'Test 7 (CREATE INDEX): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 7 (CREATE INDEX): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 7 (CREATE INDEX): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+done_testing();
--
2.52.0