From 44bbaa642fe09f38fb0d49434b236ac5c1621a3d Mon Sep 17 00:00:00 2001 From: David Rowley Date: Sat, 4 Oct 2025 16:26:32 +1300 Subject: [PATCH v4 1/2] Fix incorrect use of targetlist in dummy UNIONs I had thought that using the targetlist of the first UNION child was ok, but it's not as this could cause: ERROR: could not find pathkey item to sort Instead, use the top-level UNION's targetlist and fix setrefs.c so it rewrites the varno==0 Vars into varno==1 vars so that we display the targetlist item from the first UNION child. If we didn't do this, EXPLAIN VERBOSE would give us something like: ERROR: bogus varno: 0 --- src/backend/optimizer/plan/setrefs.c | 24 ++++++++++++++++++++---- src/backend/optimizer/prep/prepunion.c | 1 - src/test/regress/expected/union.out | 18 +++++++++++------- src/test/regress/sql/union.sql | 3 ++- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 6950eff2c5b..10d0cc4c2d3 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -1034,16 +1034,32 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) * expected to occur here, it seems safer to special-case * it here and keep the assertions that ROWID_VARs * shouldn't be seen by fix_scan_expr. + * + * We also must handle the case where set operations have + * been short-circuited resulting in a dummy Result node. + * prepunion.c uses varno==0 for the set op targetlist. + * Rewrite these to use varno==1. Otherwise EXPLAIN will + * have trouble displaying targetlists. */ foreach(l, splan->plan.targetlist) { TargetEntry *tle = (TargetEntry *) lfirst(l); Var *var = (Var *) tle->expr; - if (var && IsA(var, Var) && var->varno == ROWID_VAR) - tle->expr = (Expr *) makeNullConst(var->vartype, - var->vartypmod, - var->varcollid); + 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); + } } splan->plan.targetlist = diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 547dbd53540..da9431108a2 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -826,7 +826,6 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root, /* If all UNION children were dummy rels, make the resulting rel dummy */ if (cheapest_pathlist == NIL) { - result_rel->reltarget = create_pathtarget(root, list_nth(tlist_list, 0)); mark_dummy_rel(result_rel); return result_rel; diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index 7c089e0d598..15931beea3a 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -1258,14 +1258,18 @@ SELECT two FROM tenk1 WHERE 1=2 UNION SELECT four FROM tenk1 WHERE 1=2 UNION -SELECT ten FROM tenk1 WHERE 1=2; - QUERY PLAN --------------------------------- - Result +SELECT ten FROM tenk1 WHERE 1=2 +ORDER BY 1; + QUERY PLAN +-------------------------------------- + Sort Output: unnamed_subquery.two - Replaces: Aggregate - One-Time Filter: false -(4 rows) + Sort Key: unnamed_subquery.two + -> Result + Output: unnamed_subquery.two + Replaces: Aggregate + One-Time Filter: false +(7 rows) -- Test constraint exclusion of UNION ALL subqueries explain (costs off) diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql index 56bd20e741c..e252316f69b 100644 --- a/src/test/regress/sql/union.sql +++ b/src/test/regress/sql/union.sql @@ -484,7 +484,8 @@ SELECT two FROM tenk1 WHERE 1=2 UNION SELECT four FROM tenk1 WHERE 1=2 UNION -SELECT ten FROM tenk1 WHERE 1=2; +SELECT ten FROM tenk1 WHERE 1=2 +ORDER BY 1; -- Test constraint exclusion of UNION ALL subqueries explain (costs off) -- 2.43.0