Fix generate_union_paths for non-sortable types. REL_17_BETA1
authorRobert Haas <rhaas@postgresql.org>
Tue, 21 May 2024 16:54:09 +0000 (12:54 -0400)
committerRobert Haas <rhaas@postgresql.org>
Tue, 21 May 2024 16:54:09 +0000 (12:54 -0400)
The previous logic would fail to set groupList when
grouping_is_sortable() returned false, which could cause queries
to return wrong answers when some columns were not sortable.

David Rowley, reviewed by Heikki Linnakangas and Γlvaro Herrera.
Reported by Hubert "depesz" Lubaczewski.

Discussion: http://postgr.es/m/Zktzf926vslR35Fv@depesz.com
Discussion: http://postgr.es/m/CAApHDvra=c8_zZT0J-TftByWN2Y+OJfnjNJFg4Dfdi2s+QzmqA@mail.gmail.com

src/backend/optimizer/prep/prepunion.c
src/test/regress/expected/union.out
src/test/regress/sql/union.sql

index 30068c27a1329207becdcc2dad004f0ceb9b9f93..1c69c6e97e80a1975345ed37aa7ce40667f1c413 100644 (file)
@@ -498,7 +498,7 @@ generate_recursion_path(SetOperationStmt *setOp, PlannerInfo *root,
  * interesting_pathkeys: if not NIL, also include paths that suit these
  * pathkeys, sorting any unsorted paths as required.
  * *pNumGroups: if not NULL, we estimate the number of distinct groups
- *     in the result, and store it there
+ * in the result, and store it there.
  */
 static void
 build_setop_child_paths(PlannerInfo *root, RelOptInfo *rel,
@@ -714,7 +714,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
    List       *groupList = NIL;
    Path       *apath;
    Path       *gpath = NULL;
-   bool        try_sorted;
+   bool        try_sorted = false;
    List       *union_pathkeys = NIL;
 
    /*
@@ -740,18 +740,21 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
                                  tlist_list, refnames_tlist);
    *pTargetList = tlist;
 
-   /* For for UNIONs (not UNION ALL), try sorting, if sorting is possible */
-   try_sorted = !op->all && grouping_is_sortable(op->groupClauses);
-
-   if (try_sorted)
+   /* For UNIONs (not UNION ALL), try sorting, if sorting is possible */
+   if (!op->all)
    {
        /* Identify the grouping semantics */
        groupList = generate_setop_grouplist(op, tlist);
 
-       /* Determine the pathkeys for sorting by the whole target list */
-       union_pathkeys = make_pathkeys_for_sortclauses(root, groupList, tlist);
+       if (grouping_is_sortable(op->groupClauses))
+       {
+           try_sorted = true;
+           /* Determine the pathkeys for sorting by the whole target list */
+           union_pathkeys = make_pathkeys_for_sortclauses(root, groupList,
+                                                          tlist);
 
-       root->query_pathkeys = union_pathkeys;
+           root->query_pathkeys = union_pathkeys;
+       }
    }
 
    /*
index 26b718e9033f75bab9c476a93f57371a6d4f4488..0fd0e1c38b3d7c1836ef3e63db65c025ba8b8bf7 100644 (file)
@@ -815,6 +815,19 @@ select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (value
  (1,3)
 (1 row)
 
+-- non-sortable type
+-- Ensure we get a HashAggregate plan.  Keep enable_hashagg=off to ensure
+-- there's no chance of a sort.
+explain (costs off) select '123'::xid union select '123'::xid;
+        QUERY PLAN         
+---------------------------
+ HashAggregate
+   Group Key: ('123'::xid)
+   ->  Append
+         ->  Result
+         ->  Result
+(5 rows)
+
 reset enable_hashagg;
 --
 -- Mixed types
index 8afc580c6320139bc56d818c616179edea3d08bc..f8826514e42a6bda17700193512ce0cb690d1072 100644 (file)
@@ -244,6 +244,12 @@ explain (costs off)
 select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x);
 select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x);
 
+-- non-sortable type
+
+-- Ensure we get a HashAggregate plan.  Keep enable_hashagg=off to ensure
+-- there's no chance of a sort.
+explain (costs off) select '123'::xid union select '123'::xid;
+
 reset enable_hashagg;
 
 --