v0-0002-Show-hashed-ScalarArrayOpExpr-decision-in-EXPLAIN.patch
text/plain
Filename: v0-0002-Show-hashed-ScalarArrayOpExpr-decision-in-EXPLAIN.patch
Type: text/plain
Part: 1
Patch
Format: format-patch
Series: patch v0-0002
Subject: Show hashed ScalarArrayOpExpr decision in EXPLAIN
| File | + | − |
|---|---|---|
| src/backend/utils/adt/ruleutils.c | 9 | 1 |
| src/test/regress/expected/expressions.out | 9 | 0 |
| src/test/regress/sql/expressions.sql | 3 | 0 |
From 241dee85f1094ce797861cd1d082691fd7ec41ab Mon Sep 17 00:00:00 2001
From: "Andrei V. Lepikhov" <lepihov@gmail.com>
Date: Fri, 5 Jun 2026 11:07:21 +0200
Subject: [PATCH v0 2/3] Show hashed ScalarArrayOpExpr decision in EXPLAIN
When the planner converts a ScalarArrayOpExpr to hash-table evaluation
(convert_saop_to_hashed_saop), the resulting node deparses identically
to a linear one, so EXPLAIN gives no indication of which strategy the
executor will use. That made the hashed path invisible and awkward to
test.
---
src/backend/utils/adt/ruleutils.c | 10 +++++++++-
src/test/regress/expected/expressions.out | 9 +++++++++
src/test/regress/sql/expressions.sql | 3 +++
3 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 88de5c0481c..0567ba0886a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9989,10 +9989,18 @@ get_rule_expr(Node *node, deparse_context *context,
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren(arg1, context, true, node);
- appendStringInfo(buf, " %s %s (",
+
+ /*
+ * Surface hashed decision in EXPLAIN.
+ * hashfuncid is only ever set in a finished plan tree, so this
+ * never appears in deparsed views, rules, or other stored
+ * expressions.
+ */
+ appendStringInfo(buf, " %s %s%s (",
generate_operator_name(expr->opno,
exprType(arg1),
get_base_element_type(exprType(arg2))),
+ OidIsValid(expr->hashfuncid) ? "hashed " : "",
expr->useOr ? "ANY" : "ALL");
get_rule_expr_paren(arg2, context, true, node);
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index c35583cb2ea..9d4db9c3b2a 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -327,6 +327,15 @@ select return_text_input('a') not in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i
f
(1 row)
+-- Check tha explain marks the hashed decision.
+explain (verbose, costs off)
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Result
+ Output: (return_int_input(1) = hashed ANY ('{10,9,2,8,3,7,4,6,5,1}'::integer[]))
+(2 rows)
+
rollback;
-- Test with non-strict equality function.
-- We need to create our own type for this.
diff --git a/src/test/regress/sql/expressions.sql b/src/test/regress/sql/expressions.sql
index 08691877902..fe9c330361b 100644
--- a/src/test/regress/sql/expressions.sql
+++ b/src/test/regress/sql/expressions.sql
@@ -131,6 +131,9 @@ select return_int_input(1) not in (null, null, null, null, null, null, null, nul
select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
select return_int_input(null::int) not in (10, 9, 2, 8, 3, 7, 4, 6, 5, null);
select return_text_input('a') not in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+-- Check tha explain marks the hashed decision.
+explain (verbose, costs off)
+select return_int_input(1) in (10, 9, 2, 8, 3, 7, 4, 6, 5, 1);
rollback;
--
2.54.0