diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index fff2f4c..e122a52 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -612,7 +612,17 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, * the SubqueryScanPath as not parallel-safe. (Note that * set_subquery_pathlist() might push some of these quals down * into the subquery itself, but that doesn't change anything.) + * + * We can't push sub-select containing LIMIT/OFFSET to workers as + * that can change the results depending on what is beneath LIMIT + * node. So, consider that case as parallel-unsafe. */ + { + Query *subquery = castNode(Query, rte->subquery); + + if (limit_needed(subquery)) + return; + } break; case RTE_JOIN: diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 1946f9e..a8a025c 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -121,7 +121,6 @@ static void preprocess_rowmarks(PlannerInfo *root); static double preprocess_limit(PlannerInfo *root, double tuple_fraction, int64 *offset_est, int64 *count_est); -static bool limit_needed(Query *parse); static void remove_useless_groupby_columns(PlannerInfo *root); static List *preprocess_groupclause(PlannerInfo *root, List *force); static List *extract_rollup_sets(List *groupingSets); @@ -2697,7 +2696,7 @@ preprocess_limit(PlannerInfo *root, double tuple_fraction, * a key distinction: here we need hard constants in OFFSET/LIMIT, whereas * in preprocess_limit it's good enough to consider estimated values. */ -static bool +bool limit_needed(Query *parse) { Node *node; diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h index 2a4cf71..acb709a 100644 --- a/src/include/optimizer/planner.h +++ b/src/include/optimizer/planner.h @@ -46,6 +46,8 @@ extern bool is_dummy_plan(Plan *plan); extern RowMarkType select_rowmark_type(RangeTblEntry *rte, LockClauseStrength strength); +extern bool limit_needed(Query *parse); + extern void mark_partial_aggref(Aggref *agg, AggSplit aggsplit); extern Path *get_cheapest_fractional_path(RelOptInfo *rel, diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out index 562eb2c..14420f4 100644 --- a/src/test/regress/expected/select_parallel.out +++ b/src/test/regress/expected/select_parallel.out @@ -573,6 +573,25 @@ explain (costs off) Index Cond: (unique1 = 1) (5 rows) +-- LIMIT/OFFSET within sub-selects can't be pushed to workers. +explain (costs off) +select * from tenk1 where two in + (select two from tenk1 where stringu1 like '%AAAA' limit 3); + QUERY PLAN +--------------------------------------------------------------- + Hash Semi Join + Hash Cond: (tenk1.two = tenk1_1.two) + -> Gather + Workers Planned: 4 + -> Parallel Seq Scan on tenk1 + -> Hash + -> Limit + -> Gather + Workers Planned: 4 + -> Parallel Seq Scan on tenk1 tenk1_1 + Filter: (stringu1 ~~ '%AAAA'::text) +(11 rows) + -- to increase the parallel query test coverage EXPLAIN (analyze, timing off, summary off, costs off) SELECT * FROM tenk1; QUERY PLAN diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql index 1d29e96..c0bfc2d 100644 --- a/src/test/regress/sql/select_parallel.sql +++ b/src/test/regress/sql/select_parallel.sql @@ -204,6 +204,11 @@ set force_parallel_mode=1; explain (costs off) select stringu1::int2 from tenk1 where unique1 = 1; +-- LIMIT/OFFSET within sub-selects can't be pushed to workers. +explain (costs off) +select * from tenk1 where two in + (select two from tenk1 where stringu1 like '%AAAA' limit 3); + -- to increase the parallel query test coverage EXPLAIN (analyze, timing off, summary off, costs off) SELECT * FROM tenk1;