Hello!

On Tue, Dec 16, 2025 at 3:48 AM Mihail Nikalayeu
<[email protected]> wrote:
> First version of TAP-based test in attachment. I think we should not
> hurry with push - let's make sure it really stable now.

Second  version - some issues fixed, added some log and diagnostic for
the next fail.

Mikhail.
From f532028260b8da0697772d0f5c0c16c58fc15f02 Mon Sep 17 00:00:00 2001
From: Mikhail Nikalayeu <[email protected]>
Date: Mon, 15 Dec 2025 12:03:04 +0100
Subject: [PATCH vnocfbo2] Replace flaky isolation tests with a TAP test for
 concurrent upserts

The isolation tests verifying INSERT ON CONFLICT behavior during
concurrent index creation/reindexing were prone to flakiness. The
timing dependencies on injection points were difficult to guarantee
in the isolation tester.

Remove the disabled isolation tests and their specs/expected output.
Add TAP test '010_index_concurrently_upsert.pl' to
cover these scenarios more reliably. The TAP test now includes better
session management and more robust injection point
waiting logic.
---
 src/test/modules/injection_points/Makefile    |   8 -
 .../index-concurrently-upsert-predicate.out   | 123 ---
 .../index-concurrently-upsert-predicate_1.out | 124 ---
 .../expected/index-concurrently-upsert.out    | 123 ---
 .../expected/index-concurrently-upsert_1.out  | 124 ---
 ...ndex-concurrently-upsert-on-constraint.out | 238 -----
 ...eindex-concurrently-upsert-partitioned.out | 238 -----
 .../expected/reindex-concurrently-upsert.out  | 238 -----
 src/test/modules/injection_points/meson.build |   6 -
 .../index-concurrently-upsert-predicate.spec  | 124 ---
 .../specs/index-concurrently-upsert.spec      | 123 ---
 ...dex-concurrently-upsert-on-constraint.spec | 110 ---
 ...index-concurrently-upsert-partitioned.spec | 113 ---
 .../specs/reindex-concurrently-upsert.spec    | 111 ---
 src/test/modules/test_misc/Makefile           |   3 +
 src/test/modules/test_misc/meson.build        |   3 +
 .../t/010_index_concurrently_upsert.pl        | 894 ++++++++++++++++++
 17 files changed, 900 insertions(+), 1803 deletions(-)
 delete mode 100644 src/test/modules/injection_points/expected/index-concurrently-upsert-predicate.out
 delete mode 100644 src/test/modules/injection_points/expected/index-concurrently-upsert-predicate_1.out
 delete mode 100644 src/test/modules/injection_points/expected/index-concurrently-upsert.out
 delete mode 100644 src/test/modules/injection_points/expected/index-concurrently-upsert_1.out
 delete mode 100644 src/test/modules/injection_points/expected/reindex-concurrently-upsert-on-constraint.out
 delete mode 100644 src/test/modules/injection_points/expected/reindex-concurrently-upsert-partitioned.out
 delete mode 100644 src/test/modules/injection_points/expected/reindex-concurrently-upsert.out
 delete mode 100644 src/test/modules/injection_points/specs/index-concurrently-upsert-predicate.spec
 delete mode 100644 src/test/modules/injection_points/specs/index-concurrently-upsert.spec
 delete mode 100644 src/test/modules/injection_points/specs/reindex-concurrently-upsert-on-constraint.spec
 delete mode 100644 src/test/modules/injection_points/specs/reindex-concurrently-upsert-partitioned.spec
 delete mode 100644 src/test/modules/injection_points/specs/reindex-concurrently-upsert.spec
 create mode 100644 src/test/modules/test_misc/t/010_index_concurrently_upsert.pl

diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile
index bfdb3f53377..3cb50d13e52 100644
--- a/src/test/modules/injection_points/Makefile
+++ b/src/test/modules/injection_points/Makefile
@@ -16,14 +16,6 @@ ISOLATION = basic \
 	    inplace \
 	    syscache-update-pruned
 
-# Temporarily disabled because of flakiness
-#ISOLATION =+
-#	    index-concurrently-upsert \
-#	    index-concurrently-upsert-predicate \
-#	    reindex-concurrently-upsert \
-#	    reindex-concurrently-upsert-on-constraint \
-#	    reindex-concurrently-upsert-partitioned
-
 # The injection points are cluster-wide, so disable installcheck
 NO_INSTALLCHECK = 1
 
diff --git a/src/test/modules/injection_points/expected/index-concurrently-upsert-predicate.out b/src/test/modules/injection_points/expected/index-concurrently-upsert-predicate.out
deleted file mode 100644
index 77e7d1a7815..00000000000
--- a/src/test/modules/injection_points/expected/index-concurrently-upsert-predicate.out
+++ /dev/null
@@ -1,123 +0,0 @@
-Parsed test spec with 5 sessions
-
-starting permutation: s1_attach_invalidate_catalog_snapshot s4_wakeup_s1_setup s3_start_create_index s1_start_upsert s4_wakeup_define_index_before_set_valid s2_start_upsert s5_wakeup_s1_from_invalidate_catalog_snapshot s4_wakeup_s2 s4_wakeup_s1
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s1_attach_invalidate_catalog_snapshot: 
-	SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s1_setup: 
-	SELECT CASE WHEN
-			(SELECT pid FROM pg_stat_activity
-			      WHERE wait_event_type = 'InjectionPoint' AND
-			      wait_event = 'invalidate-catalog-snapshot-end') IS NOT NULL
-			THEN injection_points_wakeup('invalidate-catalog-snapshot-end')
-		END;
-
-case
-----
-    
-(1 row)
-
-step s3_start_create_index: 
-	CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_special_duplicate ON test.tbl(abs(i)) WHERE i < 10000;
- <waiting ...>
-step s1_start_upsert: 
-	INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_define_index_before_set_valid: 
-	SELECT injection_points_detach('define-index-before-set-valid');
-	SELECT injection_points_wakeup('define-index-before-set-valid');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: 
-	INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
- <waiting ...>
-step s5_wakeup_s1_from_invalidate_catalog_snapshot: 
-	DO $$
-		DECLARE
-			v_waiting_pid INTEGER;
-		BEGIN
-		LOOP
-			SELECT pid INTO v_waiting_pid
-			  FROM pg_stat_activity
-			 WHERE wait_event_type = 'InjectionPoint'
-				   AND wait_event = 'invalidate-catalog-snapshot-end'
-			 LIMIT 1;
-			EXIT WHEN v_waiting_pid IS NOT NULL;
-			PERFORM pg_sleep(100);
-		END LOOP;
-		END
-	$$;
-
-	SELECT injection_points_detach('invalidate-catalog-snapshot-end');
-	SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s2: 
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s1: 
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s1_start_upsert: <... completed>
-step s2_start_upsert: <... completed>
-step s3_start_create_index: <... completed>
diff --git a/src/test/modules/injection_points/expected/index-concurrently-upsert-predicate_1.out b/src/test/modules/injection_points/expected/index-concurrently-upsert-predicate_1.out
deleted file mode 100644
index e72848d6a78..00000000000
--- a/src/test/modules/injection_points/expected/index-concurrently-upsert-predicate_1.out
+++ /dev/null
@@ -1,124 +0,0 @@
-Parsed test spec with 5 sessions
-
-starting permutation: s1_attach_invalidate_catalog_snapshot s4_wakeup_s1_setup s3_start_create_index s1_start_upsert s4_wakeup_define_index_before_set_valid s2_start_upsert s5_wakeup_s1_from_invalidate_catalog_snapshot s4_wakeup_s2 s4_wakeup_s1
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s1_attach_invalidate_catalog_snapshot: 
-	SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
- <waiting ...>
-step s4_wakeup_s1_setup: 
-	SELECT CASE WHEN
-			(SELECT pid FROM pg_stat_activity
-			      WHERE wait_event_type = 'InjectionPoint' AND
-			      wait_event = 'invalidate-catalog-snapshot-end') IS NOT NULL
-			THEN injection_points_wakeup('invalidate-catalog-snapshot-end')
-		END;
-
-case
-----
-    
-(1 row)
-
-step s1_attach_invalidate_catalog_snapshot: <... completed>
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s3_start_create_index: 
-	CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_special_duplicate ON test.tbl(abs(i)) WHERE i < 10000;
- <waiting ...>
-step s1_start_upsert: 
-	INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_define_index_before_set_valid: 
-	SELECT injection_points_detach('define-index-before-set-valid');
-	SELECT injection_points_wakeup('define-index-before-set-valid');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: 
-	INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
- <waiting ...>
-step s5_wakeup_s1_from_invalidate_catalog_snapshot: 
-	DO $$
-		DECLARE
-			v_waiting_pid INTEGER;
-		BEGIN
-		LOOP
-			SELECT pid INTO v_waiting_pid
-			  FROM pg_stat_activity
-			 WHERE wait_event_type = 'InjectionPoint'
-				   AND wait_event = 'invalidate-catalog-snapshot-end'
-			 LIMIT 1;
-			EXIT WHEN v_waiting_pid IS NOT NULL;
-			PERFORM pg_sleep(100);
-		END LOOP;
-		END
-	$$;
-
-	SELECT injection_points_detach('invalidate-catalog-snapshot-end');
-	SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s2: 
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s1: 
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s1_start_upsert: <... completed>
-step s2_start_upsert: <... completed>
-step s3_start_create_index: <... completed>
diff --git a/src/test/modules/injection_points/expected/index-concurrently-upsert.out b/src/test/modules/injection_points/expected/index-concurrently-upsert.out
deleted file mode 100644
index a2ef122625c..00000000000
--- a/src/test/modules/injection_points/expected/index-concurrently-upsert.out
+++ /dev/null
@@ -1,123 +0,0 @@
-Parsed test spec with 5 sessions
-
-starting permutation: s1_attach_invalidate_catalog_snapshot s4_wakeup_s1_setup s3_start_create_index s1_start_upsert s4_wakeup_define_index_before_set_valid s2_start_upsert s5_wakeup_s1_from_invalidate_catalog_snapshot s4_wakeup_s2 s4_wakeup_s1
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s1_attach_invalidate_catalog_snapshot: 
-	SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s1_setup: 
-	SELECT CASE WHEN
-			(SELECT pid FROM pg_stat_activity
-				  WHERE wait_event_type = 'InjectionPoint' AND
-				  wait_event = 'invalidate-catalog-snapshot-end') IS NOT NULL
-			THEN injection_points_wakeup('invalidate-catalog-snapshot-end')
-		END;
-
-case
-----
-    
-(1 row)
-
-step s3_start_create_index: 
-	CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_duplicate ON test.tbl(i);
- <waiting ...>
-step s1_start_upsert: 
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_define_index_before_set_valid: 
-	SELECT injection_points_detach('define-index-before-set-valid');
-	SELECT injection_points_wakeup('define-index-before-set-valid');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: 
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s5_wakeup_s1_from_invalidate_catalog_snapshot: 
-	DO $$
-		DECLARE
-			v_waiting_pid INTEGER;
-		BEGIN
-		LOOP
-			SELECT pid INTO v_waiting_pid
-			  FROM pg_stat_activity
-			 WHERE wait_event_type = 'InjectionPoint'
-				   AND wait_event = 'invalidate-catalog-snapshot-end'
-			 LIMIT 1;
-			EXIT WHEN v_waiting_pid IS NOT NULL;
-			PERFORM pg_sleep(100);
-		END LOOP;
-		END
-	$$;
-
-	SELECT injection_points_detach('invalidate-catalog-snapshot-end');
-	SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s2: 
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s1: 
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s1_start_upsert: <... completed>
-step s2_start_upsert: <... completed>
-step s3_start_create_index: <... completed>
diff --git a/src/test/modules/injection_points/expected/index-concurrently-upsert_1.out b/src/test/modules/injection_points/expected/index-concurrently-upsert_1.out
deleted file mode 100644
index ee3b6641b90..00000000000
--- a/src/test/modules/injection_points/expected/index-concurrently-upsert_1.out
+++ /dev/null
@@ -1,124 +0,0 @@
-Parsed test spec with 5 sessions
-
-starting permutation: s1_attach_invalidate_catalog_snapshot s4_wakeup_s1_setup s3_start_create_index s1_start_upsert s4_wakeup_define_index_before_set_valid s2_start_upsert s5_wakeup_s1_from_invalidate_catalog_snapshot s4_wakeup_s2 s4_wakeup_s1
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s1_attach_invalidate_catalog_snapshot: 
-	SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
- <waiting ...>
-step s4_wakeup_s1_setup: 
-	SELECT CASE WHEN
-			(SELECT pid FROM pg_stat_activity
-				  WHERE wait_event_type = 'InjectionPoint' AND
-				  wait_event = 'invalidate-catalog-snapshot-end') IS NOT NULL
-			THEN injection_points_wakeup('invalidate-catalog-snapshot-end')
-		END;
-
-case
-----
-    
-(1 row)
-
-step s1_attach_invalidate_catalog_snapshot: <... completed>
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s3_start_create_index: 
-	CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_duplicate ON test.tbl(i);
- <waiting ...>
-step s1_start_upsert: 
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_define_index_before_set_valid: 
-	SELECT injection_points_detach('define-index-before-set-valid');
-	SELECT injection_points_wakeup('define-index-before-set-valid');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: 
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s5_wakeup_s1_from_invalidate_catalog_snapshot: 
-	DO $$
-		DECLARE
-			v_waiting_pid INTEGER;
-		BEGIN
-		LOOP
-			SELECT pid INTO v_waiting_pid
-			  FROM pg_stat_activity
-			 WHERE wait_event_type = 'InjectionPoint'
-				   AND wait_event = 'invalidate-catalog-snapshot-end'
-			 LIMIT 1;
-			EXIT WHEN v_waiting_pid IS NOT NULL;
-			PERFORM pg_sleep(100);
-		END LOOP;
-		END
-	$$;
-
-	SELECT injection_points_detach('invalidate-catalog-snapshot-end');
-	SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s2: 
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s1: 
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s1_start_upsert: <... completed>
-step s2_start_upsert: <... completed>
-step s3_start_create_index: <... completed>
diff --git a/src/test/modules/injection_points/expected/reindex-concurrently-upsert-on-constraint.out b/src/test/modules/injection_points/expected/reindex-concurrently-upsert-on-constraint.out
deleted file mode 100644
index c1ac1f77c61..00000000000
--- a/src/test/modules/injection_points/expected/reindex-concurrently-upsert-on-constraint.out
+++ /dev/null
@@ -1,238 +0,0 @@
-Parsed test spec with 4 sessions
-
-starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s4_wakeup_to_set_dead s2_start_upsert s4_wakeup_s1 s4_wakeup_s2
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_set_local
---------------------------
-                          
-(1 row)
-
-step s3_setup_wait_before_set_dead: 
-	SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s3_start_reindex: 
-	REINDEX INDEX CONCURRENTLY test.tbl_pkey;
- <waiting ...>
-step s1_start_upsert: 
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_to_set_dead: 
-	SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: 
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_s1: 
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s1_start_upsert: <... completed>
-step s4_wakeup_s2: 
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: <... completed>
-step s3_start_reindex: <... completed>
-
-starting permutation: s3_setup_wait_before_swap s3_start_reindex s1_start_upsert s4_wakeup_to_swap s2_start_upsert s4_wakeup_s2 s4_wakeup_s1
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_set_local
---------------------------
-                          
-(1 row)
-
-step s3_setup_wait_before_swap: 
-	SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s3_start_reindex: 
-	REINDEX INDEX CONCURRENTLY test.tbl_pkey;
- <waiting ...>
-step s1_start_upsert: 
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_to_swap: 
-	SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: 
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_s2: 
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s1: 
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s1_start_upsert: <... completed>
-step s2_start_upsert: <... completed>
-step s3_start_reindex: <... completed>
-
-starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s2_start_upsert s4_wakeup_s1 s4_wakeup_to_set_dead s4_wakeup_s2
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_set_local
---------------------------
-                          
-(1 row)
-
-step s3_setup_wait_before_set_dead: 
-	SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s3_start_reindex: 
-	REINDEX INDEX CONCURRENTLY test.tbl_pkey;
- <waiting ...>
-step s1_start_upsert: 
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
- <waiting ...>
-step s2_start_upsert: 
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_s1: 
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s1_start_upsert: <... completed>
-step s4_wakeup_to_set_dead: 
-	SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s2: 
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: <... completed>
-step s3_start_reindex: <... completed>
diff --git a/src/test/modules/injection_points/expected/reindex-concurrently-upsert-partitioned.out b/src/test/modules/injection_points/expected/reindex-concurrently-upsert-partitioned.out
deleted file mode 100644
index 4c79a43d986..00000000000
--- a/src/test/modules/injection_points/expected/reindex-concurrently-upsert-partitioned.out
+++ /dev/null
@@ -1,238 +0,0 @@
-Parsed test spec with 4 sessions
-
-starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s4_wakeup_to_set_dead s2_start_upsert s4_wakeup_s1 s4_wakeup_s2
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_set_local
---------------------------
-                          
-(1 row)
-
-step s3_setup_wait_before_set_dead: 
-	SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s3_start_reindex: 
-	REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
- <waiting ...>
-step s1_start_upsert: 
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_to_set_dead: 
-	SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: 
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_s1: 
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s1_start_upsert: <... completed>
-step s4_wakeup_s2: 
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: <... completed>
-step s3_start_reindex: <... completed>
-
-starting permutation: s3_setup_wait_before_swap s3_start_reindex s1_start_upsert s4_wakeup_to_swap s2_start_upsert s4_wakeup_s2 s4_wakeup_s1
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_set_local
---------------------------
-                          
-(1 row)
-
-step s3_setup_wait_before_swap: 
-	SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s3_start_reindex: 
-	REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
- <waiting ...>
-step s1_start_upsert: 
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_to_swap: 
-	SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: 
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_s2: 
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s1: 
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s1_start_upsert: <... completed>
-step s2_start_upsert: <... completed>
-step s3_start_reindex: <... completed>
-
-starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s2_start_upsert s4_wakeup_s1 s4_wakeup_to_set_dead s4_wakeup_s2
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_set_local
---------------------------
-                          
-(1 row)
-
-step s3_setup_wait_before_set_dead: 
-	SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s3_start_reindex: 
-	REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
- <waiting ...>
-step s1_start_upsert: 
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s2_start_upsert: 
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_s1: 
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s1_start_upsert: <... completed>
-step s4_wakeup_to_set_dead: 
-	SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s2: 
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: <... completed>
-step s3_start_reindex: <... completed>
diff --git a/src/test/modules/injection_points/expected/reindex-concurrently-upsert.out b/src/test/modules/injection_points/expected/reindex-concurrently-upsert.out
deleted file mode 100644
index c9cc9989d02..00000000000
--- a/src/test/modules/injection_points/expected/reindex-concurrently-upsert.out
+++ /dev/null
@@ -1,238 +0,0 @@
-Parsed test spec with 4 sessions
-
-starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s4_wakeup_to_set_dead s2_start_upsert s4_wakeup_s1 s4_wakeup_s2
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_set_local
---------------------------
-                          
-(1 row)
-
-step s3_setup_wait_before_set_dead: 
-	SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s3_start_reindex: 
-	REINDEX INDEX CONCURRENTLY test.tbl_pkey;
- <waiting ...>
-step s1_start_upsert: 
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_to_set_dead: 
-	SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: 
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_s1: 
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s1_start_upsert: <... completed>
-step s4_wakeup_s2: 
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: <... completed>
-step s3_start_reindex: <... completed>
-
-starting permutation: s3_setup_wait_before_swap s3_start_reindex s1_start_upsert s4_wakeup_to_swap s2_start_upsert s4_wakeup_s2 s4_wakeup_s1
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_set_local
---------------------------
-                          
-(1 row)
-
-step s3_setup_wait_before_swap: 
-	SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s3_start_reindex: 
-	REINDEX INDEX CONCURRENTLY test.tbl_pkey;
- <waiting ...>
-step s1_start_upsert: 
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_to_swap: 
-	SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: 
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_s2: 
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s1: 
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s1_start_upsert: <... completed>
-step s2_start_upsert: <... completed>
-step s3_start_reindex: <... completed>
-
-starting permutation: s3_setup_wait_before_set_dead s3_start_reindex s1_start_upsert s2_start_upsert s4_wakeup_s1 s4_wakeup_to_set_dead s4_wakeup_s2
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-injection_points_set_local
---------------------------
-                          
-(1 row)
-
-step s3_setup_wait_before_set_dead: 
-	SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
-
-injection_points_attach
------------------------
-                       
-(1 row)
-
-step s3_start_reindex: 
-	REINDEX INDEX CONCURRENTLY test.tbl_pkey;
- <waiting ...>
-step s1_start_upsert: 
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s2_start_upsert: 
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
- <waiting ...>
-step s4_wakeup_s1: 
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s1_start_upsert: <... completed>
-step s4_wakeup_to_set_dead: 
-	SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s4_wakeup_s2: 
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-
-injection_points_detach
------------------------
-                       
-(1 row)
-
-injection_points_wakeup
------------------------
-                       
-(1 row)
-
-step s2_start_upsert: <... completed>
-step s3_start_reindex: <... completed>
diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build
index 493e11053dc..2c6cf54a33e 100644
--- a/src/test/modules/injection_points/meson.build
+++ b/src/test/modules/injection_points/meson.build
@@ -46,12 +46,6 @@ tests += {
       'basic',
       'inplace',
       'syscache-update-pruned',
-      # temporarily disabled because of flakiness
-      # 'index-concurrently-upsert',
-      # 'index-concurrently-upsert-predicate',
-      # 'reindex-concurrently-upsert',
-      # 'reindex-concurrently-upsert-on-constraint',
-      # 'reindex-concurrently-upsert-partitioned',
     ],
     'runningcheck': false, # see syscache-update-pruned
     # Some tests wait for all snapshots, so avoid parallel execution
diff --git a/src/test/modules/injection_points/specs/index-concurrently-upsert-predicate.spec b/src/test/modules/injection_points/specs/index-concurrently-upsert-predicate.spec
deleted file mode 100644
index d9b8d27fd1f..00000000000
--- a/src/test/modules/injection_points/specs/index-concurrently-upsert-predicate.spec
+++ /dev/null
@@ -1,124 +0,0 @@
-# This test verifies INSERT ON CONFLICT DO UPDATE behavior concurrent with
-# CREATE INDEX CONCURRENTLY a partial index.
-#
-# - s1: UPSERT a tuple
-# - s2: UPSERT the same tuple
-# - s3: CREATE UNIQUE INDEX CONCURRENTLY (with a predicate)
-#
-# - s4 and s5: control concurrency via injection points
-
-setup
-{
-	CREATE EXTENSION injection_points;
-	CREATE SCHEMA test;
-	CREATE UNLOGGED TABLE test.tbl(i int, updated_at timestamp);
-	CREATE UNIQUE INDEX tbl_pkey_special ON test.tbl(abs(i)) WHERE i < 1000;
-	ALTER TABLE test.tbl SET (parallel_workers=0);
-}
-
-teardown
-{
-	DROP SCHEMA test CASCADE;
-	DROP EXTENSION injection_points;
-}
-
-session s1
-setup
-{
-	SELECT injection_points_set_local();
-	SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
-}
-step s1_attach_invalidate_catalog_snapshot
-{
-	SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
-}
-step s1_start_upsert
-{
-	INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
-}
-
-session s2
-setup
-{
-	SELECT injection_points_set_local();
-	SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
-}
-step s2_start_upsert
-{
-	INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
-}
-
-session s3
-setup
-{
-	SELECT injection_points_set_local();
-	SELECT injection_points_attach('define-index-before-set-valid', 'wait');
-}
-step s3_start_create_index
-{
-	CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_special_duplicate ON test.tbl(abs(i)) WHERE i < 10000;
-}
-
-session s4
-# Step s1_attach_invalidate_catalog_snapshot sleeps or not depending on
-# build conditions (CATCACHE_FORCE_RELEASE). Here we send a wakeup signal if
-# it's sleeping or do nothing otherwise, and print a null value in either
-# case.
-step s4_wakeup_s1_setup
-{
-	SELECT CASE WHEN
-			(SELECT pid FROM pg_stat_activity
-			      WHERE wait_event_type = 'InjectionPoint' AND
-			      wait_event = 'invalidate-catalog-snapshot-end') IS NOT NULL
-			THEN injection_points_wakeup('invalidate-catalog-snapshot-end')
-		END;
-}
-step s4_wakeup_s1
-{
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-}
-step s4_wakeup_s2
-{
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-}
-step s4_wakeup_define_index_before_set_valid
-{
-	SELECT injection_points_detach('define-index-before-set-valid');
-	SELECT injection_points_wakeup('define-index-before-set-valid');
-}
-
-session s5
-step s5_wakeup_s1_from_invalidate_catalog_snapshot
-{
-	DO $$
-		DECLARE
-			v_waiting_pid INTEGER;
-		BEGIN
-		LOOP
-			SELECT pid INTO v_waiting_pid
-			  FROM pg_stat_activity
-			 WHERE wait_event_type = 'InjectionPoint'
-				   AND wait_event = 'invalidate-catalog-snapshot-end'
-			 LIMIT 1;
-			EXIT WHEN v_waiting_pid IS NOT NULL;
-			PERFORM pg_sleep(100);
-		END LOOP;
-		END
-	$$;
-
-	SELECT injection_points_detach('invalidate-catalog-snapshot-end');
-	SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
-}
-
-permutation
-	s1_attach_invalidate_catalog_snapshot
-	s4_wakeup_s1_setup
-	s3_start_create_index(s1_start_upsert, s2_start_upsert)
-	s1_start_upsert
-	s4_wakeup_define_index_before_set_valid
-	s2_start_upsert(s1_start_upsert)
-	s5_wakeup_s1_from_invalidate_catalog_snapshot
-	s4_wakeup_s2
-	s4_wakeup_s1
diff --git a/src/test/modules/injection_points/specs/index-concurrently-upsert.spec b/src/test/modules/injection_points/specs/index-concurrently-upsert.spec
deleted file mode 100644
index 6e08af74a93..00000000000
--- a/src/test/modules/injection_points/specs/index-concurrently-upsert.spec
+++ /dev/null
@@ -1,123 +0,0 @@
-# This test verifies INSERT ON CONFLICT DO UPDATE behavior concurrent with
-# CREATE INDEX CONCURRENTLY.
-#
-# - s1: UPSERT a tuple
-# - s2: UPSERT the same tuple
-# - s3: CREATE UNIQUE INDEX CONCURRENTLY
-#
-# - s4: Control concurrency using injection points
-
-setup
-{
-	CREATE EXTENSION injection_points;
-	CREATE SCHEMA test;
-	CREATE UNLOGGED TABLE test.tbl(i int primary key, updated_at timestamp);
-	ALTER TABLE test.tbl SET (parallel_workers=0);
-}
-
-teardown
-{
-	DROP SCHEMA test CASCADE;
-	DROP EXTENSION injection_points;
-}
-
-session s1
-setup
-{
-	SELECT injection_points_set_local();
-	SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
-}
-step s1_attach_invalidate_catalog_snapshot
-{
-	SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
-}
-step s1_start_upsert
-{
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
-}
-
-session s2
-setup
-{
-	SELECT injection_points_set_local();
-	SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
-}
-step s2_start_upsert
-{
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
-}
-
-session s3
-setup
-{
-	SELECT injection_points_set_local();
-	SELECT injection_points_attach('define-index-before-set-valid', 'wait');
-}
-step s3_start_create_index
-{
-	CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_duplicate ON test.tbl(i);
-}
-
-session s4
-# Step s1_attach_invalidate_catalog_snapshot sleeps or not depending on
-# build conditions (CATCACHE_FORCE_RELEASE). Here we send a wakeup signal if
-# it's sleeping or do nothing otherwise, and print a null value in either
-# case.
-step s4_wakeup_s1_setup
-{
-	SELECT CASE WHEN
-			(SELECT pid FROM pg_stat_activity
-				  WHERE wait_event_type = 'InjectionPoint' AND
-				  wait_event = 'invalidate-catalog-snapshot-end') IS NOT NULL
-			THEN injection_points_wakeup('invalidate-catalog-snapshot-end')
-		END;
-}
-step s4_wakeup_s1
-{
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-}
-step s4_wakeup_s2
-{
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-}
-step s4_wakeup_define_index_before_set_valid
-{
-	SELECT injection_points_detach('define-index-before-set-valid');
-	SELECT injection_points_wakeup('define-index-before-set-valid');
-}
-
-session s5
-step s5_wakeup_s1_from_invalidate_catalog_snapshot
-{
-	DO $$
-		DECLARE
-			v_waiting_pid INTEGER;
-		BEGIN
-		LOOP
-			SELECT pid INTO v_waiting_pid
-			  FROM pg_stat_activity
-			 WHERE wait_event_type = 'InjectionPoint'
-				   AND wait_event = 'invalidate-catalog-snapshot-end'
-			 LIMIT 1;
-			EXIT WHEN v_waiting_pid IS NOT NULL;
-			PERFORM pg_sleep(100);
-		END LOOP;
-		END
-	$$;
-
-	SELECT injection_points_detach('invalidate-catalog-snapshot-end');
-	SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
-}
-
-permutation
-	s1_attach_invalidate_catalog_snapshot
-	s4_wakeup_s1_setup
-	s3_start_create_index(s1_start_upsert, s2_start_upsert)
-	s1_start_upsert
-	s4_wakeup_define_index_before_set_valid
-	s2_start_upsert(s1_start_upsert)
-	s5_wakeup_s1_from_invalidate_catalog_snapshot
-	s4_wakeup_s2
-	s4_wakeup_s1
diff --git a/src/test/modules/injection_points/specs/reindex-concurrently-upsert-on-constraint.spec b/src/test/modules/injection_points/specs/reindex-concurrently-upsert-on-constraint.spec
deleted file mode 100644
index 4bbdda3cf04..00000000000
--- a/src/test/modules/injection_points/specs/reindex-concurrently-upsert-on-constraint.spec
+++ /dev/null
@@ -1,110 +0,0 @@
-# Test race conditions involving:
-#
-# - s1: UPSERT a tuple
-# - s2: UPSERT the same tuple
-# - s3: concurrently REINDEX the primary key
-#
-# - s4: operations with injection points
-
-setup
-{
-	CREATE EXTENSION injection_points;
-	CREATE SCHEMA test;
-	CREATE UNLOGGED TABLE test.tbl(i int primary key, updated_at timestamp);
-	ALTER TABLE test.tbl SET (parallel_workers=0);
-}
-
-teardown
-{
-	DROP SCHEMA test CASCADE;
-	DROP EXTENSION injection_points;
-}
-
-session s1
-setup
-{
-	SELECT injection_points_set_local();
-	SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
-}
-step s1_start_upsert
-{
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
-}
-
-session s2
-setup
-{
-	SELECT injection_points_set_local();
-	SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
-}
-step s2_start_upsert
-{
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
-}
-
-session s3
-setup
-{
-	SELECT injection_points_set_local();
-}
-step s3_setup_wait_before_set_dead
-{
-	SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
-}
-step s3_setup_wait_before_swap
-{
-	SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
-}
-step s3_start_reindex
-{
-	REINDEX INDEX CONCURRENTLY test.tbl_pkey;
-}
-
-session s4
-step s4_wakeup_to_swap
-{
-	SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
-}
-step s4_wakeup_s1
-{
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-}
-step s4_wakeup_s2
-{
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-}
-step s4_wakeup_to_set_dead
-{
-	SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
-}
-
-permutation
-	s3_setup_wait_before_set_dead
-	s3_start_reindex(s1_start_upsert, s2_start_upsert)
-	s1_start_upsert(s4_wakeup_s2)
-	s4_wakeup_to_set_dead
-	s2_start_upsert(s1_start_upsert)
-	s4_wakeup_s1
-	s4_wakeup_s2
-
-permutation
-	s3_setup_wait_before_swap
-	s3_start_reindex(s1_start_upsert, s2_start_upsert)
-	s1_start_upsert(s4_wakeup_s2)
-	s4_wakeup_to_swap
-	s2_start_upsert(s1_start_upsert)
-	s4_wakeup_s2
-	s4_wakeup_s1
-
-permutation
-	s3_setup_wait_before_set_dead
-	s3_start_reindex(s1_start_upsert, s2_start_upsert)
-	s1_start_upsert(s4_wakeup_s2)
-	s2_start_upsert(s1_start_upsert)
-	s4_wakeup_s1
-	s4_wakeup_to_set_dead
-	s4_wakeup_s2
diff --git a/src/test/modules/injection_points/specs/reindex-concurrently-upsert-partitioned.spec b/src/test/modules/injection_points/specs/reindex-concurrently-upsert-partitioned.spec
deleted file mode 100644
index c3504b9ef38..00000000000
--- a/src/test/modules/injection_points/specs/reindex-concurrently-upsert-partitioned.spec
+++ /dev/null
@@ -1,113 +0,0 @@
-# This test verifies INSERT ON CONFLICT DO UPDATE behavior on partitioned
-# tables concurrent with REINDEX CONCURRENTLY.
-#
-# - s1: UPSERT a tuple
-# - s2: UPSERT the same tuple
-# - s3: concurrently REINDEX the primary key index
-#
-# - s4: controls concurrency via injection points
-
-setup
-{
-	CREATE EXTENSION injection_points;
-	CREATE SCHEMA test;
-	CREATE TABLE test.tbl(i int primary key, updated_at timestamp) PARTITION BY RANGE (i);
-	CREATE TABLE test.tbl_partition PARTITION OF test.tbl
-		FOR VALUES FROM (0) TO (10000)
-		WITH (parallel_workers = 0);
-}
-
-teardown
-{
-	DROP SCHEMA test CASCADE;
-	DROP EXTENSION injection_points;
-}
-
-session s1
-setup
-{
-	SELECT injection_points_set_local();
-	SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
-}
-step s1_start_upsert
-{
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
-}
-
-session s2
-setup
-{
-	SELECT injection_points_set_local();
-	SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
-}
-step s2_start_upsert
-{
-	INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
-}
-
-session s3
-setup
-{
-	SELECT injection_points_set_local();
-}
-step s3_setup_wait_before_set_dead
-{
-	SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
-}
-step s3_setup_wait_before_swap
-{
-	SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
-}
-step s3_start_reindex
-{
-	REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
-}
-
-session s4
-step s4_wakeup_to_swap
-{
-	SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
-}
-step s4_wakeup_s1
-{
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-}
-step s4_wakeup_s2
-{
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-}
-step s4_wakeup_to_set_dead
-{
-	SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
-}
-
-permutation
-	s3_setup_wait_before_set_dead
-	s3_start_reindex(s1_start_upsert, s2_start_upsert)
-	s1_start_upsert(s4_wakeup_s2)
-	s4_wakeup_to_set_dead
-	s2_start_upsert(s1_start_upsert)
-	s4_wakeup_s1
-	s4_wakeup_s2
-
-permutation
-	s3_setup_wait_before_swap
-	s3_start_reindex(s1_start_upsert, s2_start_upsert)
-	s1_start_upsert(s4_wakeup_s2)
-	s4_wakeup_to_swap
-	s2_start_upsert(s1_start_upsert)
-	s4_wakeup_s2
-	s4_wakeup_s1
-
-permutation
-	s3_setup_wait_before_set_dead
-	s3_start_reindex(s1_start_upsert, s2_start_upsert)
-	s1_start_upsert(s4_wakeup_s2)
-	s2_start_upsert(s1_start_upsert)
-	s4_wakeup_s1
-	s4_wakeup_to_set_dead
-	s4_wakeup_s2
diff --git a/src/test/modules/injection_points/specs/reindex-concurrently-upsert.spec b/src/test/modules/injection_points/specs/reindex-concurrently-upsert.spec
deleted file mode 100644
index 1b043a48ff4..00000000000
--- a/src/test/modules/injection_points/specs/reindex-concurrently-upsert.spec
+++ /dev/null
@@ -1,111 +0,0 @@
-# This test verifies INSERT ON CONFLICT DO UPDATE behavior concurrent with
-# REINDEX CONCURRENTLY.
-#
-# - s1: UPSERT a tuple
-# - s2: UPSERT the same tuple
-# - s3: REINDEX concurrent primary key index
-#
-# - s4: controls concurrency via injection points
-
-setup
-{
-	CREATE EXTENSION injection_points;
-	CREATE SCHEMA test;
-	CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
-	ALTER TABLE test.tbl SET (parallel_workers=0);
-}
-
-teardown
-{
-	DROP SCHEMA test CASCADE;
-	DROP EXTENSION injection_points;
-}
-
-session s1
-setup
-{
-	SELECT injection_points_set_local();
-	SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
-}
-step s1_start_upsert
-{
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
-}
-
-session s2
-setup
-{
-	SELECT injection_points_set_local();
-	SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
-}
-step s2_start_upsert
-{
-	INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
-}
-
-session s3
-setup
-{
-	SELECT injection_points_set_local();
-}
-step s3_setup_wait_before_set_dead
-{
-	SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
-}
-step s3_setup_wait_before_swap
-{
-	SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
-}
-step s3_start_reindex
-{
-	REINDEX INDEX CONCURRENTLY test.tbl_pkey;
-}
-
-session s4
-step s4_wakeup_to_swap
-{
-	SELECT injection_points_detach('reindex-relation-concurrently-before-swap');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-swap');
-}
-step s4_wakeup_s1
-{
-	SELECT injection_points_detach('check-exclusion-or-unique-constraint-no-conflict');
-	SELECT injection_points_wakeup('check-exclusion-or-unique-constraint-no-conflict');
-}
-step s4_wakeup_s2
-{
-	SELECT injection_points_detach('exec-insert-before-insert-speculative');
-	SELECT injection_points_wakeup('exec-insert-before-insert-speculative');
-}
-step s4_wakeup_to_set_dead
-{
-	SELECT injection_points_detach('reindex-relation-concurrently-before-set-dead');
-	SELECT injection_points_wakeup('reindex-relation-concurrently-before-set-dead');
-}
-
-permutation
-	s3_setup_wait_before_set_dead
-	s3_start_reindex(s1_start_upsert, s2_start_upsert)
-	s1_start_upsert(s4_wakeup_s2)
-	s4_wakeup_to_set_dead
-	s2_start_upsert(s1_start_upsert)
-	s4_wakeup_s1
-	s4_wakeup_s2
-
-permutation
-	s3_setup_wait_before_swap
-	s3_start_reindex(s1_start_upsert, s2_start_upsert)
-	s1_start_upsert(s4_wakeup_s2)
-	s4_wakeup_to_swap
-	s2_start_upsert(s1_start_upsert)
-	s4_wakeup_s2
-	s4_wakeup_s1
-
-permutation
-	s3_setup_wait_before_set_dead
-	s3_start_reindex(s1_start_upsert, s2_start_upsert)
-	s1_start_upsert(s4_wakeup_s2)
-	s2_start_upsert(s1_start_upsert)
-	s4_wakeup_s1
-	s4_wakeup_to_set_dead
-	s4_wakeup_s2
diff --git a/src/test/modules/test_misc/Makefile b/src/test/modules/test_misc/Makefile
index 399b9094a38..fedbef071ef 100644
--- a/src/test/modules/test_misc/Makefile
+++ b/src/test/modules/test_misc/Makefile
@@ -5,6 +5,9 @@ TAP_TESTS = 1
 EXTRA_INSTALL=src/test/modules/injection_points \
 	contrib/test_decoding
 
+# The injection points are cluster-wide, so disable installcheck
+NO_INSTALLCHECK = 1
+
 export enable_injection_points
 
 ifdef USE_PGXS
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index f258bf1ccd9..129c4ae587a 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -18,6 +18,9 @@ tests += {
       't/007_catcache_inval.pl',
       't/008_replslot_single_user.pl',
       't/009_log_temp_files.pl',
+      't/010_index_concurrently_upsert.pl',
     ],
+    # The injection points are cluster-wide, so disable installcheck
+    'runningcheck': false,
   },
 }
diff --git a/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl b/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl
new file mode 100644
index 00000000000..55ea384edb3
--- /dev/null
+++ b/src/test/modules/test_misc/t/010_index_concurrently_upsert.pl
@@ -0,0 +1,894 @@
+
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+# Test INSERT ON CONFLICT DO UPDATE behavior concurrent with
+# CREATE INDEX CONCURRENTLY and REINDEX CONCURRENTLY.
+#
+# These tests verify the fix for "duplicate key value violates unique constraint"
+# errors that occurred when infer_arbiter_indexes() only considered indisvalid
+# indexes, causing different transactions to use different arbiter indexes.
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+# Node initialization
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init();
+$node->start;
+
+# Check if the extension injection_points is available
+if (!$node->check_extension('injection_points'))
+{
+	plan skip_all => 'Extension injection_points not installed';
+}
+
+$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
+
+# Helper: Wait for a session to hit an injection point.
+# Optional second argument is timeout in seconds.
+# Returns true if found, false if timeout.
+# On timeout, logs diagnostic information about all active queries.
+sub wait_for_injection_point
+{
+	my ($node, $point_name, $timeout) = @_;
+	$timeout //= 120;
+
+	for (my $elapsed = 0; $elapsed < $timeout; $elapsed++)
+	{
+		my $pid = $node->safe_psql('postgres', qq[
+			SELECT pid FROM pg_stat_activity
+			WHERE wait_event_type = 'InjectionPoint'
+			  AND wait_event = '$point_name'
+			LIMIT 1;
+		]);
+		return 1 if $pid ne '';
+		sleep(1);
+	}
+
+	# Timeout - report diagnostic information
+    my $activity = $node->safe_psql('postgres', q[
+        SELECT format('pid=%s, state=%s, wait_event_type=%s, wait_event=%s, backend_xmin=%s, backend_xid=%s, query=%s',
+            pid, state, wait_event_type, wait_event, backend_xmin, backend_xid, left(query, 100))
+        FROM pg_stat_activity
+        ORDER BY pid;
+    ]);
+    diag("wait_for_injection_point timeout waiting for: $point_name\n" .
+         "Current queries in pg_stat_activity:\n$activity");
+
+	return 0;
+}
+
+# Helper: Wait for a specific backend to become idle.
+# Returns true if idle, false if timeout.
+sub wait_for_idle
+{
+	my ($node, $pid, $timeout) = @_;
+	$timeout //= 15;
+
+	for (my $elapsed = 0; $elapsed < $timeout; $elapsed++)
+	{
+		my $state = $node->safe_psql('postgres', qq[
+			SELECT state FROM pg_stat_activity WHERE pid = $pid;
+		]);
+		return 1 if $state eq 'idle';
+		sleep(1);
+	}
+	return 0;
+}
+
+# Helper: Detach and wakeup an injection point
+sub wakeup_injection_point
+{
+	my ($node, $point_name) = @_;
+	$node->safe_psql(
+		'postgres', qq[
+SELECT injection_points_detach('$point_name');
+SELECT injection_points_wakeup('$point_name');
+]);
+}
+
+# Wait for any pending query to complete, capture stderr, and close the session.
+# Returns the stderr output (excluding internal markers).
+sub safe_quit
+{
+	my ($session) = @_;
+
+	# Send a marker and wait for it to ensure any pending query completes
+	my $banner = "safe_quit_marker";
+	my $banner_match = qr/(^|\n)$banner\r?\n/;
+
+	$session->{stdin} .= "\\echo $banner\n\\warn $banner\n";
+
+	pump_until($session->{run}, $session->{timeout},
+		\$session->{stdout}, $banner_match);
+	pump_until($session->{run}, $session->{timeout},
+		\$session->{stderr}, $banner_match);
+
+	# Capture stderr (excluding the banner)
+	my $stderr = $session->{stderr};
+	$stderr =~ s/$banner_match//;
+
+	# Close the session
+	$session->quit;
+
+	return $stderr;
+}
+
+###############################################################################
+# Test 1: REINDEX CONCURRENTLY + UPSERT (wakeup at set-dead phase)
+# Based on reindex-concurrently-upsert.spec
+###############################################################################
+
+$node->safe_psql(
+	'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+# Create sessions with on_error_stop => 0 so psql doesn't exit on SQL errors.
+# This allows us to collect stderr and detect errors after the test completes.
+my $s1 = $node->background_psql('postgres', on_error_stop => 0);
+my $s2 = $node->background_psql('postgres', on_error_stop => 0);
+my $s3 = $node->background_psql('postgres', on_error_stop => 0);
+
+# Setup injection points for each session
+$s1->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
+]);
+
+$s2->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
+]);
+
+$s3->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
+]);
+
+# s3 starts REINDEX (will block on reindex-relation-concurrently-before-set-dead)
+$s3->query_until(qr/starting_reindex/, q[
+\echo starting_reindex
+REINDEX INDEX CONCURRENTLY test.tbl_pkey;
+]);
+
+# Wait for s3 to hit injection point
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-set-dead'));
+
+# s1 starts UPSERT (will block on check-exclusion-or-unique-constraint-no-conflict)
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+# Wait for s1 to hit injection point
+ok(wait_for_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'));
+
+# Wakeup s3 to continue (reindex-relation-concurrently-before-set-dead)
+wakeup_injection_point($node, 'reindex-relation-concurrently-before-set-dead');
+
+# s2 starts UPSERT (will block on exec-insert-before-insert-speculative)
+$s2->query_until(qr/starting_upsert_s2/, q[
+\echo starting_upsert_s2
+INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+# Wait for s2 to hit injection point
+ok(wait_for_injection_point($node, 'exec-insert-before-insert-speculative'));
+
+# Wakeup s1 (check-exclusion-or-unique-constraint-no-conflict)
+wakeup_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
+
+# Wakeup s2 (exec-insert-before-insert-speculative)
+wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
+
+is(safe_quit($s1), '', 'Test 1 (REINDEX swap): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 1 (REINDEX swap): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 1 (REINDEX swap): session s3 quit successfully');
+
+# Cleanup test 1
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 2: REINDEX CONCURRENTLY + UPSERT (wakeup at swap phase)
+###############################################################################
+
+$node->safe_psql(
+	'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+$s1 = $node->background_psql('postgres', on_error_stop => 0);
+$s2 = $node->background_psql('postgres', on_error_stop => 0);
+$s3 = $node->background_psql('postgres', on_error_stop => 0);
+
+$s1->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
+]);
+
+$s2->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
+]);
+
+$s3->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
+]);
+
+$s3->query_until(qr/starting_reindex/, q[
+\echo starting_reindex
+REINDEX INDEX CONCURRENTLY test.tbl_pkey;
+]);
+
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-swap'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'));
+
+wakeup_injection_point($node, 'reindex-relation-concurrently-before-swap');
+
+$s2->query_until(qr/starting_upsert_s2/, q[
+\echo starting_upsert_s2
+INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'exec-insert-before-insert-speculative'));
+
+wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
+wakeup_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
+
+is(safe_quit($s1), '', 'Test 2 (REINDEX set-dead): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 2 (REINDEX set-dead): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 2 (REINDEX set-dead): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 2b: REINDEX CONCURRENTLY + UPSERT (permutation 3: s1 wakes before reindex)
+# Different timing: s2 starts, then s1 wakes, then reindex wakes, then s2 wakes
+###############################################################################
+
+$node->safe_psql(
+	'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+$s1 = $node->background_psql('postgres', on_error_stop => 0);
+$s2 = $node->background_psql('postgres', on_error_stop => 0);
+$s3 = $node->background_psql('postgres', on_error_stop => 0);
+
+$s1->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
+]);
+
+$s2->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
+]);
+
+$s3->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
+]);
+
+$s3->query_until(qr/starting_reindex/, q[
+\echo starting_reindex
+REINDEX INDEX CONCURRENTLY test.tbl_pkey;
+]);
+
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-set-dead'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'));
+
+# Start s2 BEFORE waking reindex (key difference from permutation 1)
+$s2->query_until(qr/starting_upsert_s2/, q[
+\echo starting_upsert_s2
+INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'exec-insert-before-insert-speculative'));
+
+# Wake s1 first, then reindex, then s2
+wakeup_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
+wakeup_injection_point($node, 'reindex-relation-concurrently-before-set-dead');
+wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
+
+is(safe_quit($s1), '', 'Test 2b (REINDEX perm3): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 2b (REINDEX perm3): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 2b (REINDEX perm3): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 3: REINDEX + UPSERT ON CONSTRAINT (set-dead phase)
+# Based on reindex-concurrently-upsert-on-constraint.spec
+###############################################################################
+
+$node->safe_psql(
+	'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+$s1 = $node->background_psql('postgres', on_error_stop => 0);
+$s2 = $node->background_psql('postgres', on_error_stop => 0);
+$s3 = $node->background_psql('postgres', on_error_stop => 0);
+
+$s1->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
+]);
+
+$s2->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
+]);
+
+$s3->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
+]);
+
+$s3->query_until(qr/starting_reindex/, q[
+\echo starting_reindex
+REINDEX INDEX CONCURRENTLY test.tbl_pkey;
+]);
+
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-set-dead'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'));
+
+wakeup_injection_point($node, 'reindex-relation-concurrently-before-set-dead');
+
+$s2->query_until(qr/starting_upsert_s2/, q[
+\echo starting_upsert_s2
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'exec-insert-before-insert-speculative'));
+
+wakeup_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
+wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
+
+is(safe_quit($s1), '', 'Test 3 (ON CONSTRAINT set-dead): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 3 (ON CONSTRAINT set-dead): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 3 (ON CONSTRAINT set-dead): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 4: REINDEX + UPSERT ON CONSTRAINT (swap phase)
+###############################################################################
+
+$node->safe_psql(
+	'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+$s1 = $node->background_psql('postgres', on_error_stop => 0);
+$s2 = $node->background_psql('postgres', on_error_stop => 0);
+$s3 = $node->background_psql('postgres', on_error_stop => 0);
+
+$s1->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
+]);
+
+$s2->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
+]);
+
+$s3->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
+]);
+
+$s3->query_until(qr/starting_reindex/, q[
+\echo starting_reindex
+REINDEX INDEX CONCURRENTLY test.tbl_pkey;
+]);
+
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-swap'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'));
+
+wakeup_injection_point($node, 'reindex-relation-concurrently-before-swap');
+
+$s2->query_until(qr/starting_upsert_s2/, q[
+\echo starting_upsert_s2
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'exec-insert-before-insert-speculative'));
+
+wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
+wakeup_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
+
+is(safe_quit($s1), '', 'Test 4 (ON CONSTRAINT swap): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 4 (ON CONSTRAINT swap): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 4 (ON CONSTRAINT swap): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 4b: REINDEX + UPSERT ON CONSTRAINT (permutation 3: s1 wakes before reindex)
+###############################################################################
+
+$node->safe_psql(
+	'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl (i int PRIMARY KEY, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+$s1 = $node->background_psql('postgres', on_error_stop => 0);
+$s2 = $node->background_psql('postgres', on_error_stop => 0);
+$s3 = $node->background_psql('postgres', on_error_stop => 0);
+
+$s1->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
+]);
+
+$s2->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
+]);
+
+$s3->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
+]);
+
+$s3->query_until(qr/starting_reindex/, q[
+\echo starting_reindex
+REINDEX INDEX CONCURRENTLY test.tbl_pkey;
+]);
+
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-set-dead'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'));
+
+# Start s2 BEFORE waking reindex
+$s2->query_until(qr/starting_upsert_s2/, q[
+\echo starting_upsert_s2
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT ON CONSTRAINT tbl_pkey DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'exec-insert-before-insert-speculative'));
+
+# Wake s1 first, then reindex, then s2
+wakeup_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
+wakeup_injection_point($node, 'reindex-relation-concurrently-before-set-dead');
+wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
+
+is(safe_quit($s1), '', 'Test 4b (ON CONSTRAINT perm3): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 4b (ON CONSTRAINT perm3): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 4b (ON CONSTRAINT perm3): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 5: REINDEX on partitioned table (set-dead phase)
+# Based on reindex-concurrently-upsert-partitioned.spec
+###############################################################################
+
+$node->safe_psql(
+	'postgres', q[
+CREATE SCHEMA test;
+CREATE TABLE test.tbl(i int primary key, updated_at timestamp) PARTITION BY RANGE (i);
+CREATE TABLE test.tbl_partition PARTITION OF test.tbl
+    FOR VALUES FROM (0) TO (10000)
+    WITH (parallel_workers = 0);
+]);
+
+$s1 = $node->background_psql('postgres', on_error_stop => 0);
+$s2 = $node->background_psql('postgres', on_error_stop => 0);
+$s3 = $node->background_psql('postgres', on_error_stop => 0);
+
+$s1->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
+]);
+
+$s2->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
+]);
+
+$s3->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
+]);
+
+$s3->query_until(qr/starting_reindex/, q[
+\echo starting_reindex
+REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
+]);
+
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-set-dead'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'));
+
+wakeup_injection_point($node, 'reindex-relation-concurrently-before-set-dead');
+
+$s2->query_until(qr/starting_upsert_s2/, q[
+\echo starting_upsert_s2
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'exec-insert-before-insert-speculative'));
+
+wakeup_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
+wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
+
+is(safe_quit($s1), '', 'Test 5 (partitioned set-dead): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 5 (partitioned set-dead): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 5 (partitioned set-dead): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 6: REINDEX on partitioned table (swap phase)
+###############################################################################
+
+$node->safe_psql(
+	'postgres', q[
+CREATE SCHEMA test;
+CREATE TABLE test.tbl(i int primary key, updated_at timestamp) PARTITION BY RANGE (i);
+CREATE TABLE test.tbl_partition PARTITION OF test.tbl
+    FOR VALUES FROM (0) TO (10000)
+    WITH (parallel_workers = 0);
+]);
+
+$s1 = $node->background_psql('postgres', on_error_stop => 0);
+$s2 = $node->background_psql('postgres', on_error_stop => 0);
+$s3 = $node->background_psql('postgres', on_error_stop => 0);
+
+$s1->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
+]);
+
+$s2->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
+]);
+
+$s3->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('reindex-relation-concurrently-before-swap', 'wait');
+]);
+
+$s3->query_until(qr/starting_reindex/, q[
+\echo starting_reindex
+REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
+]);
+
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-swap'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'));
+
+wakeup_injection_point($node, 'reindex-relation-concurrently-before-swap');
+
+$s2->query_until(qr/starting_upsert_s2/, q[
+\echo starting_upsert_s2
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'exec-insert-before-insert-speculative'));
+
+wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
+wakeup_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
+
+is(safe_quit($s1), '', 'Test 6 (partitioned swap): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 6 (partitioned swap): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 6 (partitioned swap): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 6b: REINDEX on partitioned table (permutation 3: s1 wakes before reindex)
+###############################################################################
+
+$node->safe_psql(
+	'postgres', q[
+CREATE SCHEMA test;
+CREATE TABLE test.tbl(i int primary key, updated_at timestamp) PARTITION BY RANGE (i);
+CREATE TABLE test.tbl_partition PARTITION OF test.tbl
+    FOR VALUES FROM (0) TO (10000)
+    WITH (parallel_workers = 0);
+]);
+
+$s1 = $node->background_psql('postgres', on_error_stop => 0);
+$s2 = $node->background_psql('postgres', on_error_stop => 0);
+$s3 = $node->background_psql('postgres', on_error_stop => 0);
+
+$s1->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
+]);
+
+$s2->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
+]);
+
+$s3->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('reindex-relation-concurrently-before-set-dead', 'wait');
+]);
+
+$s3->query_until(qr/starting_reindex/, q[
+\echo starting_reindex
+REINDEX INDEX CONCURRENTLY test.tbl_partition_pkey;
+]);
+
+ok(wait_for_injection_point($node, 'reindex-relation-concurrently-before-set-dead'));
+
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'));
+
+# Start s2 BEFORE waking reindex
+$s2->query_until(qr/starting_upsert_s2/, q[
+\echo starting_upsert_s2
+INSERT INTO test.tbl VALUES (13, now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'exec-insert-before-insert-speculative'));
+
+# Wake s1 first, then reindex, then s2
+wakeup_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
+wakeup_injection_point($node, 'reindex-relation-concurrently-before-set-dead');
+wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
+
+is(safe_quit($s1), '', 'Test 6b (partitioned perm3): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 6b (partitioned perm3): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 6b (partitioned perm3): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 7: CREATE INDEX CONCURRENTLY + UPSERT
+# Based on index-concurrently-upsert.spec
+# Uses invalidate-catalog-snapshot-end to test catalog invalidation during UPSERT
+###############################################################################
+
+$node->safe_psql(
+	'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl(i int primary key, updated_at timestamp);
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+$s1 = $node->background_psql('postgres', on_error_stop => 0);
+$s2 = $node->background_psql('postgres', on_error_stop => 0);
+$s3 = $node->background_psql('postgres', on_error_stop => 0);
+
+my $s1_pid = $s1->query_safe('SELECT pg_backend_pid()');
+
+# s1 attaches BOTH injection points - the unique constraint check AND catalog snapshot
+$s1->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
+]);
+
+$s1->query_until(qr/attaching_injection_point/, q[
+\echo attaching_injection_point
+SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
+]);
+# In case of CLOBBER_CACHE_ALWAYS - s1 may hit the injection point during attach.
+# Wait for s1 to become idle (attach completed) or wakeup if stuck on injection point.
+if (!wait_for_idle($node, $s1_pid))
+{
+	ok(wait_for_injection_point($node, 'invalidate-catalog-snapshot-end'),
+		'Test 7: s1 hit injection point during attach (CLOBBER_CACHE_ALWAYS)');
+	$node->safe_psql('postgres', q[
+		SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
+	]);
+}
+
+$s2->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
+]);
+
+$s3->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('define-index-before-set-valid', 'wait');
+]);
+
+# s3: Start CREATE INDEX CONCURRENTLY (blocks on define-index-before-set-valid)
+$s3->query_until(qr/starting_create_index/, q[
+\echo starting_create_index
+CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_duplicate ON test.tbl(i);
+]);
+
+ok(wait_for_injection_point($node, 'define-index-before-set-valid'));
+
+# s1: Start UPSERT (blocks on invalidate-catalog-snapshot-end)
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'invalidate-catalog-snapshot-end'));
+
+# Wakeup s3 (CREATE INDEX continues, triggers catalog invalidation)
+wakeup_injection_point($node, 'define-index-before-set-valid');
+
+# s2: Start UPSERT (blocks on exec-insert-before-insert-speculative)
+$s2->query_until(qr/starting_upsert_s2/, q[
+\echo starting_upsert_s2
+INSERT INTO test.tbl VALUES (13,now()) ON CONFLICT (i) DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'exec-insert-before-insert-speculative'));
+
+wakeup_injection_point($node, 'invalidate-catalog-snapshot-end');
+
+ok(wait_for_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'));
+
+wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
+
+wakeup_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
+
+is(safe_quit($s1), '', 'Test 7 (CREATE INDEX): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 7 (CREATE INDEX): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 7 (CREATE INDEX): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+###############################################################################
+# Test 8: CREATE INDEX CONCURRENTLY on partial index + UPSERT
+# Based on index-concurrently-upsert-predicate.spec
+# Uses invalidate-catalog-snapshot-end to test catalog invalidation during UPSERT
+###############################################################################
+
+$node->safe_psql(
+	'postgres', q[
+CREATE SCHEMA test;
+CREATE UNLOGGED TABLE test.tbl(i int, updated_at timestamp);
+CREATE UNIQUE INDEX tbl_pkey_special ON test.tbl(abs(i)) WHERE i < 1000;
+ALTER TABLE test.tbl SET (parallel_workers=0);
+]);
+
+$s1 = $node->background_psql('postgres', on_error_stop => 0);
+$s2 = $node->background_psql('postgres', on_error_stop => 0);
+$s3 = $node->background_psql('postgres', on_error_stop => 0);
+
+$s1_pid = $s1->query_safe('SELECT pg_backend_pid()');
+
+# s1 attaches BOTH injection points - the unique constraint check AND catalog snapshot
+$s1->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('check-exclusion-or-unique-constraint-no-conflict', 'wait');
+]);
+
+$s1->query_until(qr/attaching_injection_point/, q[
+\echo attaching_injection_point
+SELECT injection_points_attach('invalidate-catalog-snapshot-end', 'wait');
+]);
+# In case of CLOBBER_CACHE_ALWAYS - s1 may hit the injection point during attach.
+# Wait for s1 to become idle (attach completed) or wakeup if stuck on injection point.
+if (!wait_for_idle($node, $s1_pid))
+{
+	ok(wait_for_injection_point($node, 'invalidate-catalog-snapshot-end'),
+		'Test 8: s1 hit injection point during attach (CLOBBER_CACHE_ALWAYS)');
+	$node->safe_psql('postgres', q[
+		SELECT injection_points_wakeup('invalidate-catalog-snapshot-end');
+	]);
+}
+
+$s2->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('exec-insert-before-insert-speculative', 'wait');
+]);
+
+$s3->query_safe(q[
+SELECT injection_points_set_local();
+SELECT injection_points_attach('define-index-before-set-valid', 'wait');
+]);
+
+# s3: Start CREATE INDEX CONCURRENTLY (blocks on define-index-before-set-valid)
+$s3->query_until(qr/starting_create_index/, q[
+\echo starting_create_index
+CREATE UNIQUE INDEX CONCURRENTLY tbl_pkey_special_duplicate ON test.tbl(abs(i)) WHERE i < 10000;
+]);
+
+ok(wait_for_injection_point($node, 'define-index-before-set-valid'));
+
+# s1: Start UPSERT (blocks on invalidate-catalog-snapshot-end)
+$s1->query_until(qr/starting_upsert_s1/, q[
+\echo starting_upsert_s1
+INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'invalidate-catalog-snapshot-end'));
+
+# Wakeup s3 (CREATE INDEX continues, triggers catalog invalidation)
+wakeup_injection_point($node, 'define-index-before-set-valid');
+
+# s2: Start UPSERT (blocks on exec-insert-before-insert-speculative)
+$s2->query_until(qr/starting_upsert_s2/, q[
+\echo starting_upsert_s2
+INSERT INTO test.tbl VALUES(13,now()) ON CONFLICT (abs(i)) WHERE i < 100 DO UPDATE SET updated_at = now();
+]);
+
+ok(wait_for_injection_point($node, 'exec-insert-before-insert-speculative'));
+
+wakeup_injection_point($node, 'invalidate-catalog-snapshot-end');
+
+ok(wait_for_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict'));
+
+wakeup_injection_point($node, 'exec-insert-before-insert-speculative');
+
+wakeup_injection_point($node, 'check-exclusion-or-unique-constraint-no-conflict');
+
+is(safe_quit($s1), '', 'Test 7 (CREATE INDEX): session s1 quit successfully');
+is(safe_quit($s2), '', 'Test 7 (CREATE INDEX): session s2 quit successfully');
+is(safe_quit($s3), '', 'Test 7 (CREATE INDEX): session s3 quit successfully');
+
+$node->safe_psql('postgres', 'DROP SCHEMA test CASCADE;');
+
+done_testing();
-- 
2.52.0

Reply via email to