From 241dee85f1094ce797861cd1d082691fd7ec41ab Mon Sep 17 00:00:00 2001 From: "Andrei V. Lepikhov" 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