Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit c252ccd

Browse files
committed
Fix UNION/INTERSECT/EXCEPT over no columns.
Since 9.4, we've allowed the syntax "select union select" and variants of that. However, the planner wasn't expecting a no-column set operation and ended up treating the set operation as if it were UNION ALL. Turns out it's trivial to fix in v10 and later; we just need to be careful about not generating a Sort node with no sort keys. However, since a weird corner case like this is never going to be exercised by developers, we'd better have thorough regression tests if we want to consider it supported. Per report from Victor Yegorov. Discussion: https://postgr.es/m/CAGnEbojGJrRSOgJwNGM7JSJZpVAf8xXcVPbVrGdhbVEHZ-BUMw@mail.gmail.com
1 parent f3decdc commit c252ccd

File tree

4 files changed

+168
-17
lines changed

4 files changed

+168
-17
lines changed

src/backend/optimizer/plan/createplan.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6292,7 +6292,6 @@ make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
62926292
* convert SortGroupClause list into arrays of attr indexes and equality
62936293
* operators, as wanted by executor
62946294
*/
6295-
Assert(numCols > 0);
62966295
dupColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols);
62976296
dupOperators = (Oid *) palloc(sizeof(Oid) * numCols);
62986297

src/backend/optimizer/prep/prepunion.c

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -696,10 +696,6 @@ generate_nonunion_path(SetOperationStmt *op, PlannerInfo *root,
696696
/* Identify the grouping semantics */
697697
groupList = generate_setop_grouplist(op, tlist);
698698

699-
/* punt if nothing to group on (can this happen?) */
700-
if (groupList == NIL)
701-
return path;
702-
703699
/*
704700
* Estimate number of distinct groups that we'll need hashtable entries
705701
* for; this is the size of the left-hand input for EXCEPT, or the smaller
@@ -726,7 +722,7 @@ generate_nonunion_path(SetOperationStmt *op, PlannerInfo *root,
726722
dNumGroups, dNumOutputRows,
727723
(op->op == SETOP_INTERSECT) ? "INTERSECT" : "EXCEPT");
728724

729-
if (!use_hash)
725+
if (groupList && !use_hash)
730726
path = (Path *) create_sort_path(root,
731727
result_rel,
732728
path,
@@ -849,10 +845,6 @@ make_union_unique(SetOperationStmt *op, Path *path, List *tlist,
849845
/* Identify the grouping semantics */
850846
groupList = generate_setop_grouplist(op, tlist);
851847

852-
/* punt if nothing to group on (can this happen?) */
853-
if (groupList == NIL)
854-
return path;
855-
856848
/*
857849
* XXX for the moment, take the number of distinct groups as equal to the
858850
* total input size, ie, the worst case. This is too conservative, but we
@@ -883,13 +875,15 @@ make_union_unique(SetOperationStmt *op, Path *path, List *tlist,
883875
else
884876
{
885877
/* Sort and Unique */
886-
path = (Path *) create_sort_path(root,
887-
result_rel,
888-
path,
889-
make_pathkeys_for_sortclauses(root,
890-
groupList,
891-
tlist),
892-
-1.0);
878+
if (groupList)
879+
path = (Path *)
880+
create_sort_path(root,
881+
result_rel,
882+
path,
883+
make_pathkeys_for_sortclauses(root,
884+
groupList,
885+
tlist),
886+
-1.0);
893887
/* We have to manually jam the right tlist into the path; ick */
894888
path->pathtarget = create_pathtarget(root, tlist);
895889
path = (Path *) create_upper_unique_path(root,

src/test/regress/expected/union.out

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,121 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
552552
4567890123456789 | -4567890123456789
553553
(5 rows)
554554

555+
--
556+
-- Check behavior with empty select list (allowed since 9.4)
557+
--
558+
select union select;
559+
--
560+
(1 row)
561+
562+
select intersect select;
563+
--
564+
(1 row)
565+
566+
select except select;
567+
--
568+
(0 rows)
569+
570+
-- check hashed implementation
571+
set enable_hashagg = true;
572+
set enable_sort = false;
573+
explain (costs off)
574+
select from generate_series(1,5) union select from generate_series(1,3);
575+
QUERY PLAN
576+
----------------------------------------------------------------
577+
HashAggregate
578+
-> Append
579+
-> Function Scan on generate_series
580+
-> Function Scan on generate_series generate_series_1
581+
(4 rows)
582+
583+
explain (costs off)
584+
select from generate_series(1,5) intersect select from generate_series(1,3);
585+
QUERY PLAN
586+
----------------------------------------------------------------------
587+
HashSetOp Intersect
588+
-> Append
589+
-> Subquery Scan on "*SELECT* 1"
590+
-> Function Scan on generate_series
591+
-> Subquery Scan on "*SELECT* 2"
592+
-> Function Scan on generate_series generate_series_1
593+
(6 rows)
594+
595+
select from generate_series(1,5) union select from generate_series(1,3);
596+
--
597+
(1 row)
598+
599+
select from generate_series(1,5) union all select from generate_series(1,3);
600+
--
601+
(8 rows)
602+
603+
select from generate_series(1,5) intersect select from generate_series(1,3);
604+
--
605+
(1 row)
606+
607+
select from generate_series(1,5) intersect all select from generate_series(1,3);
608+
--
609+
(3 rows)
610+
611+
select from generate_series(1,5) except select from generate_series(1,3);
612+
--
613+
(0 rows)
614+
615+
select from generate_series(1,5) except all select from generate_series(1,3);
616+
--
617+
(2 rows)
618+
619+
-- check sorted implementation
620+
set enable_hashagg = false;
621+
set enable_sort = true;
622+
explain (costs off)
623+
select from generate_series(1,5) union select from generate_series(1,3);
624+
QUERY PLAN
625+
----------------------------------------------------------------
626+
Unique
627+
-> Append
628+
-> Function Scan on generate_series
629+
-> Function Scan on generate_series generate_series_1
630+
(4 rows)
631+
632+
explain (costs off)
633+
select from generate_series(1,5) intersect select from generate_series(1,3);
634+
QUERY PLAN
635+
----------------------------------------------------------------------
636+
SetOp Intersect
637+
-> Append
638+
-> Subquery Scan on "*SELECT* 1"
639+
-> Function Scan on generate_series
640+
-> Subquery Scan on "*SELECT* 2"
641+
-> Function Scan on generate_series generate_series_1
642+
(6 rows)
643+
644+
select from generate_series(1,5) union select from generate_series(1,3);
645+
--
646+
(1 row)
647+
648+
select from generate_series(1,5) union all select from generate_series(1,3);
649+
--
650+
(8 rows)
651+
652+
select from generate_series(1,5) intersect select from generate_series(1,3);
653+
--
654+
(1 row)
655+
656+
select from generate_series(1,5) intersect all select from generate_series(1,3);
657+
--
658+
(3 rows)
659+
660+
select from generate_series(1,5) except select from generate_series(1,3);
661+
--
662+
(0 rows)
663+
664+
select from generate_series(1,5) except all select from generate_series(1,3);
665+
--
666+
(2 rows)
667+
668+
reset enable_hashagg;
669+
reset enable_sort;
555670
--
556671
-- Check handling of a case with unknown constants. We don't guarantee
557672
-- an undecorated constant will work in all cases, but historically this

src/test/regress/sql/union.sql

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,49 @@ SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)))
190190

191191
(((((select * from int8_tbl)))));
192192

193+
--
194+
-- Check behavior with empty select list (allowed since 9.4)
195+
--
196+
197+
select union select;
198+
select intersect select;
199+
select except select;
200+
201+
-- check hashed implementation
202+
set enable_hashagg = true;
203+
set enable_sort = false;
204+
205+
explain (costs off)
206+
select from generate_series(1,5) union select from generate_series(1,3);
207+
explain (costs off)
208+
select from generate_series(1,5) intersect select from generate_series(1,3);
209+
210+
select from generate_series(1,5) union select from generate_series(1,3);
211+
select from generate_series(1,5) union all select from generate_series(1,3);
212+
select from generate_series(1,5) intersect select from generate_series(1,3);
213+
select from generate_series(1,5) intersect all select from generate_series(1,3);
214+
select from generate_series(1,5) except select from generate_series(1,3);
215+
select from generate_series(1,5) except all select from generate_series(1,3);
216+
217+
-- check sorted implementation
218+
set enable_hashagg = false;
219+
set enable_sort = true;
220+
221+
explain (costs off)
222+
select from generate_series(1,5) union select from generate_series(1,3);
223+
explain (costs off)
224+
select from generate_series(1,5) intersect select from generate_series(1,3);
225+
226+
select from generate_series(1,5) union select from generate_series(1,3);
227+
select from generate_series(1,5) union all select from generate_series(1,3);
228+
select from generate_series(1,5) intersect select from generate_series(1,3);
229+
select from generate_series(1,5) intersect all select from generate_series(1,3);
230+
select from generate_series(1,5) except select from generate_series(1,3);
231+
select from generate_series(1,5) except all select from generate_series(1,3);
232+
233+
reset enable_hashagg;
234+
reset enable_sort;
235+
193236
--
194237
-- Check handling of a case with unknown constants. We don't guarantee
195238
-- an undecorated constant will work in all cases, but historically this

0 commit comments

Comments
 (0)