From fe8252c21cdb003a9b4cb38ca219331e426ac7a3 Mon Sep 17 00:00:00 2001
From: Masahiro Ikeda <Masahiro.Ikeda@nttdata.com>
Date: Mon, 8 Jul 2024 19:25:43 +0900
Subject: [PATCH v1] Fix inconsistent explain output for index only scan

This fixes the case which the index only scan could not understand
whether it should display var as cast for EXPLAIN. An unexpected
example output is the following.

                        QUERY PLAN
-----------------------------------------------------------
 Index Only Scan using btree_bpchar_f1_idx on btree_bpchar
   Index Cond: (f1 OPERATOR(pg_catalog.=) 'foo'::bpchar)
   Filter: ((f1)::bpchar ~~ 'foo'::text)
(3 rows)

TODO
* Add comments
* Rethink that set_deparse_plan() need to setup for IndexOnlyScan
---
 src/backend/commands/explain.c            |  2 +-
 src/backend/optimizer/plan/createplan.c   |  2 ++
 src/backend/optimizer/plan/setrefs.c      |  2 ++
 src/backend/optimizer/plan/subselect.c    |  5 ++++
 src/include/nodes/plannodes.h             |  1 +
 src/test/regress/expected/btree_index.out | 36 +++++++++++++++++++++++
 src/test/regress/expected/gist.out        |  6 ++--
 src/test/regress/expected/inherit.out     |  4 +--
 src/test/regress/sql/btree_index.sql      | 14 +++++++++
 9 files changed, 66 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1e80fd8b68..52882afe9b 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1981,7 +1981,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 										   planstate, es);
 			break;
 		case T_IndexOnlyScan:
-			show_scan_qual(((IndexOnlyScan *) plan)->indexqual,
+			show_scan_qual(((IndexOnlyScan *) plan)->indexqualorig,
 						   "Index Cond", planstate, ancestors, es);
 			if (((IndexOnlyScan *) plan)->recheckqual)
 				show_instrumentation_count("Rows Removed by Index Recheck", 2,
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 6b64c4a362..4767ddd45c 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -5584,6 +5584,7 @@ make_indexonlyscan(List *qptlist,
 {
 	IndexOnlyScan *node = makeNode(IndexOnlyScan);
 	Plan	   *plan = &node->scan.plan;
+	List	   *indexqualorig = copyObject(recheckqual);
 
 	plan->targetlist = qptlist;
 	plan->qual = qpqual;
@@ -5592,6 +5593,7 @@ make_indexonlyscan(List *qptlist,
 	node->scan.scanrelid = scanrelid;
 	node->indexid = indexid;
 	node->indexqual = indexqual;
+	node->indexqualorig = indexqualorig;
 	node->recheckqual = recheckqual;
 	node->indexorderby = indexorderby;
 	node->indextlist = indextlist;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 7aed84584c..72e79ae55e 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1372,6 +1372,8 @@ set_indexonlyscan_references(PlannerInfo *root,
 	/* indexqual is already transformed to reference index columns */
 	plan->indexqual = fix_scan_list(root, plan->indexqual,
 									rtoffset, 1);
+	plan->indexqualorig = fix_scan_list(root, plan->indexqualorig,
+										rtoffset, NUM_EXEC_QUAL((Plan *) plan));
 	/* indexorderby is already transformed to reference index columns */
 	plan->indexorderby = fix_scan_list(root, plan->indexorderby,
 									   rtoffset, 1);
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 6d003cc8e5..c0e1745a3b 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2400,6 +2400,11 @@ finalize_plan(PlannerInfo *root, Plan *plan,
 			 * we need not look at indextlist, since it cannot contain Params.
 			 */
 			context.paramids = bms_add_members(context.paramids, scan_params);
+
+			/*
+			 * we need not look at indexqualorig, since it will have the same
+			 * param references as indexqual.
+			 */
 			break;
 
 		case T_BitmapIndexScan:
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 1aeeaec95e..4a8ab48ebb 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -494,6 +494,7 @@ typedef struct IndexOnlyScan
 	Scan		scan;
 	Oid			indexid;		/* OID of index to scan */
 	List	   *indexqual;		/* list of index quals (usually OpExprs) */
+	List	   *indexqualorig;	/* the same in original form */
 	List	   *recheckqual;	/* index quals in recheckable form */
 	List	   *indexorderby;	/* list of index ORDER BY exprs */
 	List	   *indextlist;		/* TargetEntry list describing index's cols */
diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out
index 510646cbce..dddc3ca970 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -409,6 +409,42 @@ select * from btree_bpchar where f1::bpchar like 'foo%';
  fool
 (2 rows)
 
+-- Test for indexonlyscan with binary-compatible cases
+set enable_seqscan to false;
+set enable_bitmapscan to false;
+explain (costs off)
+select * from btree_bpchar where f1::bpchar like 'foo';
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Index Only Scan using btree_bpchar_f1_idx on btree_bpchar
+   Index Cond: ((f1)::bpchar = 'foo'::bpchar)
+   Filter: ((f1)::bpchar ~~ 'foo'::text)
+(3 rows)
+
+select * from btree_bpchar where f1::bpchar like 'foo';
+ f1  
+-----
+ foo
+(1 row)
+
+explain (costs off)
+select * from btree_bpchar where f1::bpchar like 'foo%';
+                                     QUERY PLAN                                     
+------------------------------------------------------------------------------------
+ Index Only Scan using btree_bpchar_f1_idx on btree_bpchar
+   Index Cond: (((f1)::bpchar >= 'foo'::bpchar) AND ((f1)::bpchar < 'fop'::bpchar))
+   Filter: ((f1)::bpchar ~~ 'foo%'::text)
+(3 rows)
+
+select * from btree_bpchar where f1::bpchar like 'foo%';
+  f1  
+------
+ foo
+ fool
+(2 rows)
+
+reset enable_seqscan;
+reset enable_bitmapscan;
 -- get test coverage for "single value" deduplication strategy:
 insert into btree_bpchar select 'foo' from generate_series(1,1500);
 --
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index c75bbb23b6..fdd525e655 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -344,11 +344,11 @@ where p <@ box(point(5, 5), point(5.3, 5.3));
 -- are done correctly.
 explain (verbose, costs off)
 select p from gist_tbl where circle(p,1) @> circle(point(0,0),0.95);
-                                      QUERY PLAN                                       
----------------------------------------------------------------------------------------
+                                     QUERY PLAN                                      
+-------------------------------------------------------------------------------------
  Index Only Scan using gist_tbl_multi_index on public.gist_tbl
    Output: p
-   Index Cond: ((circle(gist_tbl.p, '1'::double precision)) @> '<(0,0),0.95>'::circle)
+   Index Cond: (circle(gist_tbl.p, '1'::double precision) @> '<(0,0),0.95>'::circle)
 (3 rows)
 
 select p from gist_tbl where circle(p,1) @> circle(point(0,0),0.95);
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index ad73213414..cf488049bb 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -2374,11 +2374,11 @@ explain (costs off) select min(a), max(a) from parted_minmax where b = '12345';
    InitPlan 1
      ->  Limit
            ->  Index Only Scan using parted_minmax1i on parted_minmax1 parted_minmax
-                 Index Cond: ((a IS NOT NULL) AND (b = '12345'::text))
+                 Index Cond: ((a IS NOT NULL) AND ((b)::text = '12345'::text))
    InitPlan 2
      ->  Limit
            ->  Index Only Scan Backward using parted_minmax1i on parted_minmax1 parted_minmax_1
-                 Index Cond: ((a IS NOT NULL) AND (b = '12345'::text))
+                 Index Cond: ((a IS NOT NULL) AND ((b)::text = '12345'::text))
 (9 rows)
 
 select min(a), max(a) from parted_minmax where b = '12345';
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 0d2a33f370..930b7c6b2b 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -202,6 +202,20 @@ explain (costs off)
 select * from btree_bpchar where f1::bpchar like 'foo%';
 select * from btree_bpchar where f1::bpchar like 'foo%';
 
+-- Test for indexonlyscan with binary-compatible cases
+set enable_seqscan to false;
+set enable_bitmapscan to false;
+
+explain (costs off)
+select * from btree_bpchar where f1::bpchar like 'foo';
+select * from btree_bpchar where f1::bpchar like 'foo';
+explain (costs off)
+select * from btree_bpchar where f1::bpchar like 'foo%';
+select * from btree_bpchar where f1::bpchar like 'foo%';
+
+reset enable_seqscan;
+reset enable_bitmapscan;
+
 -- get test coverage for "single value" deduplication strategy:
 insert into btree_bpchar select 'foo' from generate_series(1,1500);
 
-- 
2.34.1

