From 8857dbf0e0d16a3fca56e65bd9eeea726682f417 Mon Sep 17 00:00:00 2001
From: Haibo Yan <tristan.yim@gmail.com>
Date: Thu, 15 Oct 2026 05:37:56 +0000
Subject: [PATCH v3 1/2] Add isolation test for an index-only scan under
 EvalPlanQual

No existing test ran an index-only scan inside an EPQ recheck.  Add one
where the correlated subquery of an UPDATE has to be re-evaluated after
a concurrent update changed the outer keys, so that the index-only scan
runs in the EPQ estate with refreshed runtime keys, a qual and a
projection, including a NULL runtime key.

Author: Haibo Yan <tristan.yim@gmail.com>
---
 .../eval-plan-qual-index-only-scan.out        | 23 ++++++++++++
 src/test/isolation/isolation_schedule         |  1 +
 .../specs/eval-plan-qual-index-only-scan.spec | 35 +++++++++++++++++++
 3 files changed, 59 insertions(+)
 create mode 100644 src/test/isolation/expected/eval-plan-qual-index-only-scan.out
 create mode 100644 src/test/isolation/specs/eval-plan-qual-index-only-scan.spec

diff --git a/src/test/isolation/expected/eval-plan-qual-index-only-scan.out b/src/test/isolation/expected/eval-plan-qual-index-only-scan.out
new file mode 100644
index 00000000000..511c51cac05
--- /dev/null
+++ b/src/test/isolation/expected/eval-plan-qual-index-only-scan.out
@@ -0,0 +1,23 @@
+Parsed test spec with 2 sessions
+
+starting permutation: change_keys update_values commit_keys check_values
+step change_keys: 
+  UPDATE epq_ios_target
+    SET k = CASE id WHEN 1 THEN 3 WHEN 2 THEN 2 ELSE NULL END;
+
+step update_values: 
+  UPDATE epq_ios_target t
+    SET v = (SELECT k + 10 FROM epq_ios_lookup
+             WHERE k = t.k AND k % 3 <> 2)
+    WHERE id >= 1;
+ <waiting ...>
+step commit_keys: COMMIT;
+step update_values: <... completed>
+step check_values: SELECT * FROM epq_ios_target ORDER BY id;
+id|k| v
+--+-+--
+ 1|3|13
+ 2|2|  
+ 3| |  
+(3 rows)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index bf9a2037c70..608643b0b62 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -44,6 +44,7 @@ test: subxid-overflow
 test: eval-plan-qual
 test: eval-plan-qual-trigger
 test: eval-plan-qual-partition-prune
+test: eval-plan-qual-index-only-scan
 test: inplace-inval
 test: intra-grant-inplace
 test: intra-grant-inplace-db
diff --git a/src/test/isolation/specs/eval-plan-qual-index-only-scan.spec b/src/test/isolation/specs/eval-plan-qual-index-only-scan.spec
new file mode 100644
index 00000000000..3b6f19863c4
--- /dev/null
+++ b/src/test/isolation/specs/eval-plan-qual-index-only-scan.spec
@@ -0,0 +1,35 @@
+# Run a correlated index-only scan in EPQ using the updated outer keys.
+# The three rows respectively need projection, fail the scan qual, and use
+# a NULL runtime key.  All are rechecked in the same EPQ state tree.
+setup
+{
+  CREATE TABLE epq_ios_target (id int PRIMARY KEY, k int, v int);
+  CREATE TABLE epq_ios_lookup (k int PRIMARY KEY);
+  INSERT INTO epq_ios_target VALUES (1, 1, 0), (2, 1, 0), (3, 1, 0);
+  -- Enough rows to choose an index-only scan without planner overrides.
+  INSERT INTO epq_ios_lookup SELECT generate_series(0, 999);
+}
+setup { VACUUM ANALYZE epq_ios_lookup; }
+
+teardown { DROP TABLE epq_ios_target, epq_ios_lookup; }
+
+session s1
+setup { BEGIN; }
+step change_keys
+{
+  UPDATE epq_ios_target
+    SET k = CASE id WHEN 1 THEN 3 WHEN 2 THEN 2 ELSE NULL END;
+}
+step commit_keys { COMMIT; }
+
+session s2
+step update_values
+{
+  UPDATE epq_ios_target t
+    SET v = (SELECT k + 10 FROM epq_ios_lookup
+             WHERE k = t.k AND k % 3 <> 2)
+    WHERE id >= 1;
+}
+step check_values { SELECT * FROM epq_ios_target ORDER BY id; }
+
+permutation change_keys update_values(commit_keys) commit_keys check_values
-- 
2.39.5

