From 113f1ad1040c3646f6a990a14f5304db903bfe15 Mon Sep 17 00:00:00 2001
From: Mikhail Nikalayeu <mihailnikalayeu@gmail.com>
Date: Mon, 15 Dec 2025 12:03:04 +0100
Subject: [PATCH v2 1/2] Replace flaky CIC/RI isolation tests with stable TAP
 tests
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The isolation tests for INSERT ON CONFLICT behavior during CREATE INDEX CONCURRENTLY and REINDEX CONCURRENTLY were disabled in 77038d6d0b4 due to persistent CI flakiness.

The isolation tester based solution was struggling to reliably ensure exact the same test output. The tests were commented out pending a complete rewrite.

This commit removes the disabled isolation tests and their spec files, originally added in bc32a12e0db, 2bc7e886fc1 and 90eae926abb and replaces them with a TAP test in test_misc module (010_index_concurrently_upsert.pl) that covers the same scenarios.

These tests verify the fixes from referenced commits, and 81f72115cf1 remain effective, preventing "duplicate key value violates unique constraint" errors when concurrent transactions select different arbiter indexes during index state transitions.

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
---
 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..e3feb6f2861
--- /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 set-dead): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 1 (REINDEX set-dead): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 1 (REINDEX set-dead): 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 swap): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 2 (REINDEX swap): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 2 (REINDEX swap): 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 8 (CREATE INDEX predicate): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 8 (CREATE INDEX predicate): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 8 (CREATE INDEX predicate): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+done_testing();
-- 
2.52.0

