diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index b136f12ff3b..02ac58be211 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -862,6 +862,17 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root, apath = (Path *) create_append_path(root, result_rel, cheapest, NIL, NULL, 0, false, -1); + /* + * Although we told create_append_path to assign NIL pathkeys to the + * AppendPath, it may have overridden that (if there's just one surviving + * child path, it will use that path's pathkeys). However, createplan.c + * will fail because the append relation's tlist contains "varno 0" Vars + * (cf. generate_append_tlist), which won't match what is in the pathkeys. + * We need to fix that someday, but for now, just force the AppendPath's + * pathkeys back to NIL. + */ + apath->pathkeys = NIL; + /* * Initialize the result row estimate to the total input size. This is * correct for UNION ALL; for the UNION case it is overwritten below with @@ -908,6 +919,9 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root, create_append_path(root, result_rel, partial, NIL, NULL, parallel_workers, enable_parallel_append, -1); + /* See comment above about pathkeys vs. "varno 0" Vars */ + papath->pathkeys = NIL; + gpath = (Path *) create_gather_path(root, result_rel, papath, result_rel->reltarget, NULL, NULL); @@ -1224,6 +1238,8 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root, apath = (Path *) create_append_path(root, result_rel, append, NIL, NULL, 0, false, -1); + /* See comment in generate_union_paths about "varno 0" Vars */ + apath->pathkeys = NIL; add_path(result_rel, apath); diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index f07b6141e75..2495351d907 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -1366,7 +1366,7 @@ ORDER BY 1; Output: tenk1.two (5 rows) --- Try a mixed setop case. Ensure the right-hand UNION child gets removed. +-- Try mixed setop cases. Ensure the right-hand UNION child gets removed. EXPLAIN (COSTS OFF, VERBOSE) SELECT two FROM tenk1 t1 EXCEPT @@ -1388,6 +1388,84 @@ SELECT ten FROM tenk1 dummy WHERE 1=2; Output: t2.four (11 rows) +EXPLAIN (COSTS OFF, VERBOSE) +SELECT two FROM tenk1 t1 WHERE two = 1 +INTERSECT ALL +SELECT four FROM tenk1 t2 WHERE four = 1 +UNION +SELECT ten FROM tenk1 dummy WHERE 1=2; + QUERY PLAN +----------------------------------------- + HashAggregate + Output: t1.two + Group Key: t1.two + -> HashSetOp Intersect All + Output: t1.two + -> Seq Scan on public.tenk1 t1 + Output: t1.two + Filter: (t1.two = 1) + -> Seq Scan on public.tenk1 t2 + Output: t2.four + Filter: (t2.four = 1) +(11 rows) + +-- As above, but with sorting: ensure we do not get confused by child pathkeys +SET enable_hashagg = off; +EXPLAIN (COSTS OFF, VERBOSE) +SELECT two FROM tenk1 t1 WHERE two = 1 +INTERSECT ALL +SELECT four FROM tenk1 t2 WHERE four = 1 +UNION +SELECT ten FROM tenk1 dummy WHERE 1=2; + QUERY PLAN +----------------------------------------------------- + Unique + Output: t1.two + -> Sort + Output: t1.two + Sort Key: t1.two + -> SetOp Intersect All + Output: t1.two + -> Sort + Output: t1.two + Sort Key: t1.two + -> Seq Scan on public.tenk1 t1 + Output: t1.two + Filter: (t1.two = 1) + -> Sort + Output: t2.four + Sort Key: t2.four + -> Seq Scan on public.tenk1 t2 + Output: t2.four + Filter: (t2.four = 1) +(19 rows) + +-- also check case where the upper op is EXCEPT +EXPLAIN (COSTS OFF, VERBOSE) +(SELECT two FROM tenk1 t1 WHERE two = 1 + INTERSECT + SELECT four FROM tenk1 t2 WHERE four = 1) +EXCEPT ALL +SELECT ten FROM tenk1 dummy WHERE 1=2; + QUERY PLAN +----------------------------------------- + SetOp Intersect + Output: t1.two + -> Sort + Output: t1.two + Sort Key: t1.two + -> Seq Scan on public.tenk1 t1 + Output: t1.two + Filter: (t1.two = 1) + -> Sort + Output: t2.four + Sort Key: t2.four + -> Seq Scan on public.tenk1 t2 + Output: t2.four + Filter: (t2.four = 1) +(14 rows) + +RESET enable_hashagg; -- Ensure EXPLAIN can show a dummy set operation whose output is coerced -- to another type by the parent set operation. EXPLAIN (COSTS OFF, VERBOSE) diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql index a787dd25d23..8fce4528a3f 100644 --- a/src/test/regress/sql/union.sql +++ b/src/test/regress/sql/union.sql @@ -523,7 +523,7 @@ EXCEPT ALL SELECT four FROM tenk1 WHERE 1=2 ORDER BY 1; --- Try a mixed setop case. Ensure the right-hand UNION child gets removed. +-- Try mixed setop cases. Ensure the right-hand UNION child gets removed. EXPLAIN (COSTS OFF, VERBOSE) SELECT two FROM tenk1 t1 EXCEPT @@ -531,6 +531,33 @@ SELECT four FROM tenk1 t2 UNION SELECT ten FROM tenk1 dummy WHERE 1=2; +EXPLAIN (COSTS OFF, VERBOSE) +SELECT two FROM tenk1 t1 WHERE two = 1 +INTERSECT ALL +SELECT four FROM tenk1 t2 WHERE four = 1 +UNION +SELECT ten FROM tenk1 dummy WHERE 1=2; + +-- As above, but with sorting: ensure we do not get confused by child pathkeys +SET enable_hashagg = off; + +EXPLAIN (COSTS OFF, VERBOSE) +SELECT two FROM tenk1 t1 WHERE two = 1 +INTERSECT ALL +SELECT four FROM tenk1 t2 WHERE four = 1 +UNION +SELECT ten FROM tenk1 dummy WHERE 1=2; + +-- also check case where the upper op is EXCEPT +EXPLAIN (COSTS OFF, VERBOSE) +(SELECT two FROM tenk1 t1 WHERE two = 1 + INTERSECT + SELECT four FROM tenk1 t2 WHERE four = 1) +EXCEPT ALL +SELECT ten FROM tenk1 dummy WHERE 1=2; + +RESET enable_hashagg; + -- Ensure EXPLAIN can show a dummy set operation whose output is coerced -- to another type by the parent set operation. EXPLAIN (COSTS OFF, VERBOSE)