From 44bbaa642fe09f38fb0d49434b236ac5c1621a3d Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowley@gmail.com>
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

