From dab5511d645953b5ff4335375a68ce16897a01d6 Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Sun, 4 Oct 2026 17:29:15 -0400 Subject: [PATCH v1] Fix EXPLAIN of a dummy set operation under a coercion 928df067d1e changed varno 0 Vars in a dummy Result's targetlist to varno 1, but only when the Var was the whole expression. A parent set operation can put a type coercion above the Var, and then EXPLAIN failed with "bogus varno: 0". Fix the Vars inside expressions too. Discussion: https://postgr.es/m/19742-dc403ca277cad1d3@postgresql.org --- src/backend/optimizer/plan/setrefs.c | 53 ++++++++++++++++++++-------- src/test/regress/expected/union.out | 20 +++++++++++ src/test/regress/sql/union.sql | 9 +++++ 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 8a641402a96..c1a0f1d10a6 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -155,6 +155,7 @@ static Plan *set_mergeappend_references(PlannerInfo *root, int rtoffset); static void set_hash_references(PlannerInfo *root, Plan *plan, int rtoffset); static Relids offset_relid_set(Relids relids, int rtoffset); +static Node *fix_dummy_setop_vars_mutator(Node *node, void *context); static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec); static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context); @@ -1060,27 +1061,23 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) * Here we rewrite these to use varno==1, which is the * varno of the first set-op child. Without this, EXPLAIN * will have trouble displaying targetlists of dummy set - * operations. + * operations. The Vars might be inside an expression, + * such as a type coercion added by a parent set + * operation. */ foreach(l, splan->plan.targetlist) { TargetEntry *tle = (TargetEntry *) lfirst(l); Var *var = (Var *) tle->expr; - if (var && IsA(var, Var)) - { - if (var->varno == ROWID_VAR) - tle->expr = (Expr *) makeNullConst(var->vartype, - var->vartypmod, - var->varcollid); - else if (var->varno == 0) - tle->expr = (Expr *) makeVar(1, - var->varattno, - var->vartype, - var->vartypmod, - var->varcollid, - var->varlevelsup); - } + if (var && IsA(var, Var) && var->varno == ROWID_VAR) + tle->expr = (Expr *) makeNullConst(var->vartype, + var->vartypmod, + var->varcollid); + else + tle->expr = (Expr *) + fix_dummy_setop_vars_mutator((Node *) tle->expr, + NULL); } splan->plan.targetlist = @@ -2246,6 +2243,32 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan, return (Node *) bestplan; } +/* + * fix_dummy_setop_vars_mutator + * Change the varno 0 Vars made by prepunion.c to varno 1. + */ +static Node * +fix_dummy_setop_vars_mutator(Node *node, void *context) +{ + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + + if (var->varno == 0) + return (Node *) makeVar(1, + var->varattno, + var->vartype, + var->vartypmod, + var->varcollid, + var->varlevelsup); + return node; + } + return expression_tree_mutator(node, fix_dummy_setop_vars_mutator, + context); +} + /* * fix_scan_expr * Do set_plan_references processing on a scan-level expression diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index 84abcd6b14f..785fde1c8e3 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -1388,6 +1388,26 @@ SELECT ten FROM tenk1 dummy WHERE 1=2; Output: t2.four (11 rows) +-- Ensure EXPLAIN can show a dummy set operation whose output is coerced to +-- another type by the parent set operation +EXPLAIN (COSTS OFF, VERBOSE) +SELECT two::numeric FROM tenk1 +EXCEPT +(SELECT four FROM tenk1 WHERE 1=2 + EXCEPT ALL + SELECT ten FROM tenk1); + QUERY PLAN +--------------------------------------------------------------------- + HashSetOp Except + Output: ((tenk1.two)::numeric) + -> Seq Scan on public.tenk1 + Output: (tenk1.two)::numeric + -> Result + Output: two + Replaces: Aggregate on unnamed_subquery, unnamed_subquery_1 + One-Time Filter: false +(8 rows) + -- Test constraint exclusion of UNION ALL subqueries explain (costs off) SELECT * FROM diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql index c8de276c2b5..c1f349cabc0 100644 --- a/src/test/regress/sql/union.sql +++ b/src/test/regress/sql/union.sql @@ -531,6 +531,15 @@ SELECT four FROM tenk1 t2 UNION SELECT ten FROM tenk1 dummy WHERE 1=2; +-- Ensure EXPLAIN can show a dummy set operation whose output is coerced to +-- another type by the parent set operation +EXPLAIN (COSTS OFF, VERBOSE) +SELECT two::numeric FROM tenk1 +EXCEPT +(SELECT four FROM tenk1 WHERE 1=2 + EXCEPT ALL + SELECT ten FROM tenk1); + -- Test constraint exclusion of UNION ALL subqueries explain (costs off) SELECT * FROM -- 2.37.1 (Apple Git-137.1)