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

Commit 86b7cca

Browse files
committed
Check parallel safety in generate_useful_gather_paths
Commit ebb7ae8 ensured we ignore pathkeys with volatile expressions when considering adding a sort below a Gather Merge. Turns out we need to care about parallel safety of the pathkeys too, otherwise we might try sorting e.g. on results of a correlated subquery (as demonstrated by a report from Luis Roberto). Initial investigation by Tom Lane, patch by James Coleman. Backpatch to 13, where the code was instroduced (as part of Incremental Sort). Reported-by: Luis Roberto Author: James Coleman Reviewed-by: Tomas Vondra Backpatch-through: 13 Discussion: https://postgr.es/m/622580997.37108180.1604080457319.JavaMail.zimbra%40siscobra.com.br Discussion: https://postgr.es/m/CAAaqYe8cK3g5CfLC4w7bs=hC0mSksZC=H5M8LSchj5e5OxpTAg@mail.gmail.com
1 parent f4a3c0b commit 86b7cca

File tree

5 files changed

+73
-5
lines changed

5 files changed

+73
-5
lines changed

src/backend/optimizer/path/allpaths.c

+10-3
Original file line numberDiff line numberDiff line change
@@ -2802,14 +2802,18 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows)
28022802
* This allows us to do incremental sort on top of an index scan under a gather
28032803
* merge node, i.e. parallelized.
28042804
*
2805+
* If the require_parallel_safe is true, we also require the expressions to
2806+
* be parallel safe (which allows pushing the sort below Gather Merge).
2807+
*
28052808
* XXX At the moment this can only ever return a list with a single element,
28062809
* because it looks at query_pathkeys only. So we might return the pathkeys
28072810
* directly, but it seems plausible we'll want to consider other orderings
28082811
* in the future. For example, we might want to consider pathkeys useful for
28092812
* merge joins.
28102813
*/
28112814
static List *
2812-
get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
2815+
get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel,
2816+
bool require_parallel_safe)
28132817
{
28142818
List *useful_pathkeys_list = NIL;
28152819

@@ -2839,8 +2843,11 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
28392843
* meet criteria of EC membership in the current relation, we
28402844
* enable not just an incremental sort on the entirety of
28412845
* query_pathkeys but also incremental sort below a JOIN.
2846+
*
2847+
* If requested, ensure the expression is parallel safe too.
28422848
*/
2843-
if (!find_em_expr_usable_for_sorting_rel(pathkey_ec, rel))
2849+
if (!find_em_expr_usable_for_sorting_rel(root, pathkey_ec, rel,
2850+
require_parallel_safe))
28442851
break;
28452852

28462853
npathkeys++;
@@ -2894,7 +2901,7 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r
28942901
generate_gather_paths(root, rel, override_rows);
28952902

28962903
/* consider incremental sort for interesting orderings */
2897-
useful_pathkeys_list = get_useful_pathkeys_for_relation(root, rel);
2904+
useful_pathkeys_list = get_useful_pathkeys_for_relation(root, rel, true);
28982905

28992906
/* used for explicit (full) sort paths */
29002907
cheapest_partial_path = linitial(rel->partial_pathlist);

src/backend/optimizer/path/equivclass.c

+8-1
Original file line numberDiff line numberDiff line change
@@ -803,7 +803,8 @@ find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)
803803
* applied in prepare_sort_from_pathkeys.
804804
*/
805805
Expr *
806-
find_em_expr_usable_for_sorting_rel(EquivalenceClass *ec, RelOptInfo *rel)
806+
find_em_expr_usable_for_sorting_rel(PlannerInfo *root, EquivalenceClass *ec,
807+
RelOptInfo *rel, bool require_parallel_safe)
807808
{
808809
ListCell *lc_em;
809810

@@ -833,6 +834,12 @@ find_em_expr_usable_for_sorting_rel(EquivalenceClass *ec, RelOptInfo *rel)
833834
if (!bms_is_subset(em->em_relids, rel->relids))
834835
continue;
835836

837+
/*
838+
* If requested, reject expressions that are not parallel-safe.
839+
*/
840+
if (require_parallel_safe && !is_parallel_safe(root, (Node *) em_expr))
841+
continue;
842+
836843
/*
837844
* As long as the expression isn't volatile then
838845
* prepare_sort_from_pathkeys is able to generate a new target entry,

src/include/optimizer/paths.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@ extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
135135
Relids rel,
136136
bool create_it);
137137
extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);
138-
extern Expr *find_em_expr_usable_for_sorting_rel(EquivalenceClass *ec, RelOptInfo *rel);
138+
extern Expr *find_em_expr_usable_for_sorting_rel(PlannerInfo *root,
139+
EquivalenceClass *ec,
140+
RelOptInfo *rel,
141+
bool require_parallel_safe);
139142
extern void generate_base_implied_equalities(PlannerInfo *root);
140143
extern List *generate_join_implied_equalities(PlannerInfo *root,
141144
Relids join_relids,

src/test/regress/expected/incremental_sort.out

+40
Original file line numberDiff line numberDiff line change
@@ -1551,6 +1551,46 @@ order by 1, 2;
15511551
-> Function Scan on generate_series
15521552
(7 rows)
15531553

1554+
-- Parallel sort but with expression (correlated subquery) that
1555+
-- is prohibited in parallel plans.
1556+
explain (costs off) select distinct
1557+
unique1,
1558+
(select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
1559+
from tenk1 t, generate_series(1, 1000);
1560+
QUERY PLAN
1561+
---------------------------------------------------------------------------------
1562+
Unique
1563+
-> Sort
1564+
Sort Key: t.unique1, ((SubPlan 1))
1565+
-> Gather
1566+
Workers Planned: 2
1567+
-> Nested Loop
1568+
-> Parallel Index Only Scan using tenk1_unique1 on tenk1 t
1569+
-> Function Scan on generate_series
1570+
SubPlan 1
1571+
-> Index Only Scan using tenk1_unique1 on tenk1
1572+
Index Cond: (unique1 = t.unique1)
1573+
(11 rows)
1574+
1575+
explain (costs off) select
1576+
unique1,
1577+
(select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
1578+
from tenk1 t, generate_series(1, 1000)
1579+
order by 1, 2;
1580+
QUERY PLAN
1581+
---------------------------------------------------------------------------
1582+
Sort
1583+
Sort Key: t.unique1, ((SubPlan 1))
1584+
-> Gather
1585+
Workers Planned: 2
1586+
-> Nested Loop
1587+
-> Parallel Index Only Scan using tenk1_unique1 on tenk1 t
1588+
-> Function Scan on generate_series
1589+
SubPlan 1
1590+
-> Index Only Scan using tenk1_unique1 on tenk1
1591+
Index Cond: (unique1 = t.unique1)
1592+
(10 rows)
1593+
15541594
-- Parallel sort but with expression not available until the upper rel.
15551595
explain (costs off) select distinct sub.unique1, stringu1 || random()::text
15561596
from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;

src/test/regress/sql/incremental_sort.sql

+11
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,17 @@ from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;
250250
explain (costs off) select sub.unique1, md5(stringu1)
251251
from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub
252252
order by 1, 2;
253+
-- Parallel sort but with expression (correlated subquery) that
254+
-- is prohibited in parallel plans.
255+
explain (costs off) select distinct
256+
unique1,
257+
(select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
258+
from tenk1 t, generate_series(1, 1000);
259+
explain (costs off) select
260+
unique1,
261+
(select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
262+
from tenk1 t, generate_series(1, 1000)
263+
order by 1, 2;
253264
-- Parallel sort but with expression not available until the upper rel.
254265
explain (costs off) select distinct sub.unique1, stringu1 || random()::text
255266
from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;

0 commit comments

Comments
 (0)