v2-0002-Parallel-query-support-for-basic-correlated-subqu.patch
application/octet-stream
Filename: v2-0002-Parallel-query-support-for-basic-correlated-subqu.patch
Type: application/octet-stream
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0002
Subject: Parallel query support for basic correlated subqueries
| File | + | − |
|---|---|---|
| doc/src/sgml/parallel.sgml | 2 | 1 |
| src/backend/nodes/copyfuncs.c | 2 | 0 |
| src/backend/nodes/equalfuncs.c | 1 | 0 |
| src/backend/nodes/outfuncs.c | 3 | 0 |
| src/backend/nodes/readfuncs.c | 2 | 0 |
| src/backend/optimizer/path/allpaths.c | 46 | 15 |
| src/backend/optimizer/path/equivclass.c | 2 | 2 |
| src/backend/optimizer/path/indxpath.c | 16 | 4 |
| src/backend/optimizer/path/joinpath.c | 16 | 5 |
| src/backend/optimizer/plan/createplan.c | 2 | 0 |
| src/backend/optimizer/plan/planmain.c | 1 | 1 |
| src/backend/optimizer/plan/planner.c | 136 | 19 |
| src/backend/optimizer/plan/subselect.c | 20 | 1 |
| src/backend/optimizer/prep/prepunion.c | 1 | 0 |
| src/backend/optimizer/util/clauses.c | 91 | 22 |
| src/backend/optimizer/util/pathnode.c | 27 | 9 |
| src/backend/optimizer/util/relnode.c | 26 | 4 |
| src/backend/utils/misc/guc.c | 11 | 0 |
| src/include/nodes/pathnodes.h | 4 | 1 |
| src/include/nodes/plannodes.h | 1 | 0 |
| src/include/nodes/primnodes.h | 1 | 0 |
| src/include/optimizer/clauses.h | 3 | 1 |
| src/test/regress/expected/incremental_sort.out | 14 | 14 |
| src/test/regress/expected/select_parallel.out | 137 | 0 |
| src/test/regress/expected/sysviews.out | 2 | 1 |
| src/test/regress/sql/select_parallel.sql | 37 | 0 |
From 41ce0d022fb0dcccd12fa713a07c139452d9c763 Mon Sep 17 00:00:00 2001
From: jcoleman <jtc331@gmail.com>
Date: Fri, 27 Nov 2020 18:44:30 -0500
Subject: [PATCH v2 2/3] Parallel query support for basic correlated subqueries
Not all Params are inherently parallel-unsafe, but we can't know whether
they're parallel-safe up-front: we need contextual information for a
given path shape. Here we delay the final determination of whether or
not a Param is parallel-safe by initially verifying that it is minimally
parallel-safe for things that are inherent (e.g., no parallel-unsafe
functions or relations involved) and later re-checking that a given
usage is contextually safe (e.g., the Param is for correlation that can
happen entirely within a parallel worker (as opposed to needing to pass
values between workers).
---
doc/src/sgml/parallel.sgml | 3 +-
src/backend/nodes/copyfuncs.c | 2 +
src/backend/nodes/equalfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 3 +
src/backend/nodes/readfuncs.c | 2 +
src/backend/optimizer/path/allpaths.c | 61 +++++--
src/backend/optimizer/path/equivclass.c | 4 +-
src/backend/optimizer/path/indxpath.c | 20 ++-
src/backend/optimizer/path/joinpath.c | 21 ++-
src/backend/optimizer/plan/createplan.c | 2 +
src/backend/optimizer/plan/planmain.c | 2 +-
src/backend/optimizer/plan/planner.c | 155 +++++++++++++++---
src/backend/optimizer/plan/subselect.c | 21 ++-
src/backend/optimizer/prep/prepunion.c | 1 +
src/backend/optimizer/util/clauses.c | 113 ++++++++++---
src/backend/optimizer/util/pathnode.c | 36 +++-
src/backend/optimizer/util/relnode.c | 30 +++-
src/backend/utils/misc/guc.c | 11 ++
src/include/nodes/pathnodes.h | 5 +-
src/include/nodes/plannodes.h | 1 +
src/include/nodes/primnodes.h | 1 +
src/include/optimizer/clauses.h | 4 +-
.../regress/expected/incremental_sort.out | 28 ++--
src/test/regress/expected/select_parallel.out | 137 ++++++++++++++++
src/test/regress/expected/sysviews.out | 3 +-
src/test/regress/sql/select_parallel.sql | 37 +++++
26 files changed, 604 insertions(+), 100 deletions(-)
diff --git a/doc/src/sgml/parallel.sgml b/doc/src/sgml/parallel.sgml
index 13479d7e5e..a651a9fe73 100644
--- a/doc/src/sgml/parallel.sgml
+++ b/doc/src/sgml/parallel.sgml
@@ -517,7 +517,8 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
<listitem>
<para>
- Plan nodes that reference a correlated <literal>SubPlan</literal>.
+ Plan nodes thatreference a correlated <literal>SubPlan</literal> where
+ the result is shared between workers.
</para>
</listitem>
</itemizedlist>
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 38251c2b8e..4042cef2ea 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -124,6 +124,7 @@ CopyPlanFields(const Plan *from, Plan *newnode)
COPY_SCALAR_FIELD(parallel_aware);
COPY_SCALAR_FIELD(parallel_safe);
COPY_SCALAR_FIELD(async_capable);
+ COPY_SCALAR_FIELD(parallel_safe_ignoring_params);
COPY_SCALAR_FIELD(plan_node_id);
COPY_NODE_FIELD(targetlist);
COPY_NODE_FIELD(qual);
@@ -1767,6 +1768,7 @@ _copySubPlan(const SubPlan *from)
COPY_SCALAR_FIELD(useHashTable);
COPY_SCALAR_FIELD(unknownEqFalse);
COPY_SCALAR_FIELD(parallel_safe);
+ COPY_SCALAR_FIELD(parallel_safe_ignoring_params);
COPY_NODE_FIELD(setParam);
COPY_NODE_FIELD(parParam);
COPY_NODE_FIELD(args);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..482063c066 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -465,6 +465,7 @@ _equalSubPlan(const SubPlan *a, const SubPlan *b)
COMPARE_SCALAR_FIELD(useHashTable);
COMPARE_SCALAR_FIELD(unknownEqFalse);
COMPARE_SCALAR_FIELD(parallel_safe);
+ COMPARE_SCALAR_FIELD(parallel_safe_ignoring_params);
COMPARE_NODE_FIELD(setParam);
COMPARE_NODE_FIELD(parParam);
COMPARE_NODE_FIELD(args);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 87561cbb6f..42dbb05db5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -334,6 +334,7 @@ _outPlanInfo(StringInfo str, const Plan *node)
WRITE_BOOL_FIELD(parallel_aware);
WRITE_BOOL_FIELD(parallel_safe);
WRITE_BOOL_FIELD(async_capable);
+ WRITE_BOOL_FIELD(parallel_safe_ignoring_params);
WRITE_INT_FIELD(plan_node_id);
WRITE_NODE_FIELD(targetlist);
WRITE_NODE_FIELD(qual);
@@ -1374,6 +1375,7 @@ _outSubPlan(StringInfo str, const SubPlan *node)
WRITE_BOOL_FIELD(useHashTable);
WRITE_BOOL_FIELD(unknownEqFalse);
WRITE_BOOL_FIELD(parallel_safe);
+ WRITE_BOOL_FIELD(parallel_safe_ignoring_params);
WRITE_NODE_FIELD(setParam);
WRITE_NODE_FIELD(parParam);
WRITE_NODE_FIELD(args);
@@ -1772,6 +1774,7 @@ _outPathInfo(StringInfo str, const Path *node)
outBitmapset(str, NULL);
WRITE_BOOL_FIELD(parallel_aware);
WRITE_BOOL_FIELD(parallel_safe);
+ WRITE_BOOL_FIELD(parallel_safe_ignoring_params);
WRITE_INT_FIELD(parallel_workers);
WRITE_FLOAT_FIELD(rows, "%.0f");
WRITE_FLOAT_FIELD(startup_cost, "%.2f");
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 0dd1ad7dfc..b6598726bb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1621,6 +1621,7 @@ ReadCommonPlan(Plan *local_node)
READ_BOOL_FIELD(parallel_aware);
READ_BOOL_FIELD(parallel_safe);
READ_BOOL_FIELD(async_capable);
+ READ_BOOL_FIELD(parallel_safe_ignoring_params);
READ_INT_FIELD(plan_node_id);
READ_NODE_FIELD(targetlist);
READ_NODE_FIELD(qual);
@@ -2615,6 +2616,7 @@ _readSubPlan(void)
READ_BOOL_FIELD(useHashTable);
READ_BOOL_FIELD(unknownEqFalse);
READ_BOOL_FIELD(parallel_safe);
+ READ_BOOL_FIELD(parallel_safe_ignoring_params);
READ_NODE_FIELD(setParam);
READ_NODE_FIELD(parParam);
READ_NODE_FIELD(args);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 1363f1bc6c..5ab6d0e23f 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -556,7 +556,8 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
* (see grouping_planner).
*/
if (rel->reloptkind == RELOPT_BASEREL &&
- bms_membership(root->all_baserels) != BMS_SINGLETON)
+ bms_membership(root->all_baserels) != BMS_SINGLETON
+ && (rel->subplan_params == NIL || rte->rtekind != RTE_SUBQUERY))
generate_useful_gather_paths(root, rel, false);
/* Now find the cheapest of the paths for this rel */
@@ -592,6 +593,9 @@ static void
set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte)
{
+ bool parallel_safe;
+ bool parallel_safe_except_in_params;
+
/*
* The flag has previously been initialized to false, so we can just
* return if it becomes clear that we can't safely set it.
@@ -632,7 +636,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
if (proparallel != PROPARALLEL_SAFE)
return;
- if (!is_parallel_safe(root, (Node *) rte->tablesample->args))
+ if (!is_parallel_safe(root, (Node *) rte->tablesample->args, NULL))
return;
}
@@ -700,7 +704,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
case RTE_FUNCTION:
/* Check for parallel-restricted functions. */
- if (!is_parallel_safe(root, (Node *) rte->functions))
+ if (!is_parallel_safe(root, (Node *) rte->functions, NULL))
return;
break;
@@ -710,7 +714,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
case RTE_VALUES:
/* Check for parallel-restricted functions. */
- if (!is_parallel_safe(root, (Node *) rte->values_lists))
+ if (!is_parallel_safe(root, (Node *) rte->values_lists, NULL))
return;
break;
@@ -747,18 +751,28 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* outer join clauses work correctly. It would likely break equivalence
* classes, too.
*/
- if (!is_parallel_safe(root, (Node *) rel->baserestrictinfo))
- return;
+ parallel_safe = is_parallel_safe(root, (Node *) rel->baserestrictinfo,
+ ¶llel_safe_except_in_params);
/*
* Likewise, if the relation's outputs are not parallel-safe, give up.
* (Usually, they're just Vars, but sometimes they're not.)
*/
- if (!is_parallel_safe(root, (Node *) rel->reltarget->exprs))
- return;
+ if (parallel_safe || parallel_safe_except_in_params)
+ {
+ bool target_parallel_safe;
+ bool target_parallel_safe_ignoring_params = false;
+
+ target_parallel_safe = is_parallel_safe(root,
+ (Node *) rel->reltarget->exprs,
+ &target_parallel_safe_ignoring_params);
+ parallel_safe = parallel_safe && target_parallel_safe;
+ parallel_safe_except_in_params = parallel_safe_except_in_params
+ && target_parallel_safe_ignoring_params;
+ }
- /* We have a winner. */
- rel->consider_parallel = true;
+ rel->consider_parallel = parallel_safe;
+ rel->consider_parallel_rechecking_params = parallel_safe_except_in_params;
}
/*
@@ -2277,9 +2291,21 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ /*
+ * XXX: As far as I can tell, the only time partial paths exist here
+ * is when we're going to execute multiple partial parths in parallel
+ * under a gather node (instead of executing paths serially under
+ * an append node). That means that the subquery scan path here
+ * is self-contained at this point -- so by definition it can't be
+ * reliant on lateral relids, which means we'll never have to consider
+ * rechecking params here.
+ */
+ Assert(!(rel->consider_parallel_rechecking_params && rel->partial_pathlist && !rel->consider_parallel));
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
+
/* If consider_parallel is false, there should be no partial paths. */
Assert(sub_final_rel->consider_parallel ||
sub_final_rel->partial_pathlist == NIL);
@@ -2633,7 +2659,7 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows)
cheapest_partial_path->rows * cheapest_partial_path->parallel_workers;
simple_gather_path = (Path *)
create_gather_path(root, rel, cheapest_partial_path, rel->reltarget,
- NULL, rowsp);
+ rel->lateral_relids, rowsp);
add_path(rel, simple_gather_path);
/*
@@ -2650,7 +2676,7 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows)
rows = subpath->rows * subpath->parallel_workers;
path = create_gather_merge_path(root, rel, subpath, rel->reltarget,
- subpath->pathkeys, NULL, rowsp);
+ subpath->pathkeys, rel->lateral_relids, rowsp);
add_path(rel, &path->path);
}
}
@@ -2752,11 +2778,15 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r
double *rowsp = NULL;
List *useful_pathkeys_list = NIL;
Path *cheapest_partial_path = NULL;
+ Relids required_outer = rel->lateral_relids;
/* If there are no partial paths, there's nothing to do here. */
if (rel->partial_pathlist == NIL)
return;
+ if (!bms_is_subset(required_outer, rel->relids))
+ return;
+
/* Should we override the rel's rowcount estimate? */
if (override_rows)
rowsp = &rows;
@@ -2828,7 +2858,7 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r
tmp,
rel->reltarget,
tmp->pathkeys,
- NULL,
+ required_outer,
rowsp);
add_path(rel, &path->path);
@@ -2862,7 +2892,7 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r
tmp,
rel->reltarget,
tmp->pathkeys,
- NULL,
+ required_outer,
rowsp);
add_path(rel, &path->path);
@@ -3041,7 +3071,8 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
/*
* Except for the topmost scan/join rel, consider gathering
* partial paths. We'll do the same for the topmost scan/join rel
- * once we know the final targetlist (see grouping_planner).
+ * once we know the final targetlist (see
+ * apply_scanjoin_target_to_paths).
*/
if (lev < levels_needed)
generate_useful_gather_paths(root, rel, false);
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 6f1abbe47d..4c176d2d49 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -897,7 +897,7 @@ find_computable_ec_member(PlannerInfo *root,
* check this last because it's a rather expensive test.
*/
if (require_parallel_safe &&
- !is_parallel_safe(root, (Node *) em->em_expr))
+ !is_parallel_safe(root, (Node *) em->em_expr, NULL))
continue;
return em; /* found usable expression */
@@ -1012,7 +1012,7 @@ relation_can_be_sorted_early(PlannerInfo *root, RelOptInfo *rel,
* check this last because it's a rather expensive test.
*/
if (require_parallel_safe &&
- !is_parallel_safe(root, (Node *) em->em_expr))
+ !is_parallel_safe(root, (Node *) em->em_expr, NULL))
continue;
return true;
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 0e4e00eaf0..262c129ff5 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1047,10 +1047,21 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
/*
* If appropriate, consider parallel index scan. We don't allow
* parallel index scan for bitmap index scans.
+ *
+ * XXX: Checking rel->consider_parallel_rechecking_params here resulted
+ * in some odd behavior on:
+ * select (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1) from tenk1 t;
+ * where the total cost on the chosen plan went *up* considering
+ * the extra path.
+ *
+ * Current working theory is that this method is about base relation
+ * scans, and we only want parameterized paths to be parallelized as
+ * companions to existing parallel plans and so don't really care to
+ * consider a separate parallel index scan here.
*/
if (index->amcanparallel &&
- rel->consider_parallel && outer_relids == NULL &&
- scantype != ST_BITMAPSCAN)
+ rel->consider_parallel && outer_relids == NULL &&
+ scantype != ST_BITMAPSCAN)
{
ipath = create_index_path(root, index,
index_clauses,
@@ -1100,9 +1111,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
result = lappend(result, ipath);
/* If appropriate, consider parallel index scan */
+ /* XXX: As above here for rel->consider_parallel_rechecking_params? */
if (index->amcanparallel &&
- rel->consider_parallel && outer_relids == NULL &&
- scantype != ST_BITMAPSCAN)
+ rel->consider_parallel && outer_relids == NULL &&
+ scantype != ST_BITMAPSCAN)
{
ipath = create_index_path(root, index,
index_clauses,
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 6407ede12a..f8daa7b265 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -722,6 +722,7 @@ try_partial_nestloop_path(PlannerInfo *root,
else
outerrelids = outerrel->relids;
+ /* TODO: recheck parallel safety here? */
if (!bms_is_subset(inner_paramrels, outerrelids))
return;
}
@@ -1746,16 +1747,24 @@ match_unsorted_outer(PlannerInfo *root,
* partial path and the joinrel is parallel-safe. However, we can't
* handle JOIN_UNIQUE_OUTER, because the outer path will be partial, and
* therefore we won't be able to properly guarantee uniqueness. Nor can
- * we handle joins needing lateral rels, since partial paths must not be
- * parameterized. Similarly, we can't handle JOIN_FULL and JOIN_RIGHT,
- * because they can produce false null extended rows.
+ * we handle JOIN_FULL and JOIN_RIGHT, because they can produce false null
+ * extended rows.
+ *
+ * Partial paths may only have parameters in limited cases
+ * where the parameterization is fully satisfied without sharing state
+ * between workers, so we only allow lateral rels on inputs to the join
+ * if the resulting join contains no lateral rels, the inner rel's laterals
+ * are fully satisfied by the outer rel, and the outer rel doesn't depend
+ * on the inner rel to produce any laterals.
*/
if (joinrel->consider_parallel &&
save_jointype != JOIN_UNIQUE_OUTER &&
save_jointype != JOIN_FULL &&
save_jointype != JOIN_RIGHT &&
outerrel->partial_pathlist != NIL &&
- bms_is_empty(joinrel->lateral_relids))
+ bms_is_empty(joinrel->lateral_relids) &&
+ bms_is_subset(innerrel->lateral_relids, outerrel->relids) &&
+ (bms_is_empty(outerrel->lateral_relids) || !bms_is_subset(outerrel->lateral_relids, innerrel->relids)))
{
if (nestjoinOK)
consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
@@ -1870,7 +1879,9 @@ consider_parallel_nestloop(PlannerInfo *root,
Path *mpath;
/* Can't join to an inner path that is not parallel-safe */
- if (!innerpath->parallel_safe)
+ /* TODO: recheck parallel safety of params here? */
+ if (!innerpath->parallel_safe &&
+ !(innerpath->parallel_safe_ignoring_params))
continue;
/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index a5f6d678cc..e84834427b 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -5273,6 +5273,7 @@ copy_generic_path_info(Plan *dest, Path *src)
dest->plan_width = src->pathtarget->width;
dest->parallel_aware = src->parallel_aware;
dest->parallel_safe = src->parallel_safe;
+ dest->parallel_safe_ignoring_params = src->parallel_safe_ignoring_params;
}
/*
@@ -5290,6 +5291,7 @@ copy_plan_costsize(Plan *dest, Plan *src)
dest->parallel_aware = false;
/* Assume the inserted node is parallel-safe, if child plan is. */
dest->parallel_safe = src->parallel_safe;
+ dest->parallel_safe_ignoring_params = src->parallel_safe_ignoring_params;
}
/*
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 273ac0acf7..bdbce2b87d 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -119,7 +119,7 @@ query_planner(PlannerInfo *root,
if (root->glob->parallelModeOK &&
force_parallel_mode != FORCE_PARALLEL_OFF)
final_rel->consider_parallel =
- is_parallel_safe(root, parse->jointree->quals);
+ is_parallel_safe(root, parse->jointree->quals, NULL);
/*
* The only path for it is a trivial Result path. We cheat a
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1e42d75465..25ed2b2788 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -150,6 +150,7 @@ static RelOptInfo *create_grouping_paths(PlannerInfo *root,
RelOptInfo *input_rel,
PathTarget *target,
bool target_parallel_safe,
+ bool target_parallel_safe_ignoring_params,
grouping_sets_data *gd);
static bool is_degenerate_grouping(PlannerInfo *root);
static void create_degenerate_grouping_paths(PlannerInfo *root,
@@ -157,6 +158,7 @@ static void create_degenerate_grouping_paths(PlannerInfo *root,
RelOptInfo *grouped_rel);
static RelOptInfo *make_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
PathTarget *target, bool target_parallel_safe,
+ bool target_parallel_safe_ignoring_params,
Node *havingQual);
static void create_ordinary_grouping_paths(PlannerInfo *root,
RelOptInfo *input_rel,
@@ -237,6 +239,7 @@ static void apply_scanjoin_target_to_paths(PlannerInfo *root,
List *scanjoin_targets,
List *scanjoin_targets_contain_srfs,
bool scanjoin_target_parallel_safe,
+ bool scanjoin_target_parallel_safe_ignoring_params,
bool tlist_same_exprs);
static void create_partitionwise_grouping_paths(PlannerInfo *root,
RelOptInfo *input_rel,
@@ -1241,6 +1244,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
List *final_targets;
List *final_targets_contain_srfs;
bool final_target_parallel_safe;
+ bool final_target_parallel_safe_ignoring_params = false;
RelOptInfo *current_rel;
RelOptInfo *final_rel;
FinalPathExtraData extra;
@@ -1303,7 +1307,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
/* And check whether it's parallel safe */
final_target_parallel_safe =
- is_parallel_safe(root, (Node *) final_target->exprs);
+ is_parallel_safe(root, (Node *) final_target->exprs, &final_target_parallel_safe_ignoring_params);
/* The setop result tlist couldn't contain any SRFs */
Assert(!parse->hasTargetSRFs);
@@ -1337,14 +1341,17 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
List *sort_input_targets;
List *sort_input_targets_contain_srfs;
bool sort_input_target_parallel_safe;
+ bool sort_input_target_parallel_safe_ignoring_params = false;
PathTarget *grouping_target;
List *grouping_targets;
List *grouping_targets_contain_srfs;
bool grouping_target_parallel_safe;
+ bool grouping_target_parallel_safe_ignoring_params = false;
PathTarget *scanjoin_target;
List *scanjoin_targets;
List *scanjoin_targets_contain_srfs;
bool scanjoin_target_parallel_safe;
+ bool scanjoin_target_parallel_safe_ignoring_params = false;
bool scanjoin_target_same_exprs;
bool have_grouping;
WindowFuncLists *wflists = NULL;
@@ -1457,7 +1464,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
*/
final_target = create_pathtarget(root, root->processed_tlist);
final_target_parallel_safe =
- is_parallel_safe(root, (Node *) final_target->exprs);
+ is_parallel_safe(root, (Node *) final_target->exprs, &final_target_parallel_safe_ignoring_params);
/*
* If ORDER BY was given, consider whether we should use a post-sort
@@ -1470,12 +1477,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
final_target,
&have_postponed_srfs);
sort_input_target_parallel_safe =
- is_parallel_safe(root, (Node *) sort_input_target->exprs);
+ is_parallel_safe(root, (Node *) sort_input_target->exprs, &sort_input_target_parallel_safe_ignoring_params);
}
else
{
sort_input_target = final_target;
sort_input_target_parallel_safe = final_target_parallel_safe;
+ sort_input_target_parallel_safe_ignoring_params = final_target_parallel_safe_ignoring_params;
}
/*
@@ -1489,12 +1497,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
final_target,
activeWindows);
grouping_target_parallel_safe =
- is_parallel_safe(root, (Node *) grouping_target->exprs);
+ is_parallel_safe(root, (Node *) grouping_target->exprs, &grouping_target_parallel_safe_ignoring_params);
}
else
{
grouping_target = sort_input_target;
grouping_target_parallel_safe = sort_input_target_parallel_safe;
+ grouping_target_parallel_safe_ignoring_params = sort_input_target_parallel_safe_ignoring_params;
}
/*
@@ -1508,12 +1517,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
{
scanjoin_target = make_group_input_target(root, final_target);
scanjoin_target_parallel_safe =
- is_parallel_safe(root, (Node *) scanjoin_target->exprs);
+ is_parallel_safe(root, (Node *) scanjoin_target->exprs, &scanjoin_target_parallel_safe_ignoring_params);
}
else
{
scanjoin_target = grouping_target;
scanjoin_target_parallel_safe = grouping_target_parallel_safe;
+ scanjoin_target_parallel_safe_ignoring_params = grouping_target_parallel_safe_ignoring_params;
}
/*
@@ -1565,6 +1575,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
apply_scanjoin_target_to_paths(root, current_rel, scanjoin_targets,
scanjoin_targets_contain_srfs,
scanjoin_target_parallel_safe,
+ scanjoin_target_parallel_safe_ignoring_params,
scanjoin_target_same_exprs);
/*
@@ -1592,6 +1603,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
current_rel,
grouping_target,
grouping_target_parallel_safe,
+ grouping_target_parallel_safe_ignoring_params,
gset_data);
/* Fix things up if grouping_target contains SRFs */
if (parse->hasTargetSRFs)
@@ -1665,10 +1677,25 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
* not a SELECT, consider_parallel will be false for every relation in the
* query.
*/
- if (current_rel->consider_parallel &&
- is_parallel_safe(root, parse->limitOffset) &&
- is_parallel_safe(root, parse->limitCount))
- final_rel->consider_parallel = true;
+ if (current_rel->consider_parallel || current_rel->consider_parallel_rechecking_params)
+ {
+ bool limit_count_parallel_safe;
+ bool limit_offset_parallel_safe;
+ bool limit_count_parallel_safe_ignoring_params = false;
+ bool limit_offset_parallel_safe_ignoring_params = false;
+
+ limit_count_parallel_safe = is_parallel_safe(root, parse->limitCount, &limit_count_parallel_safe_ignoring_params);
+ limit_offset_parallel_safe = is_parallel_safe(root, parse->limitOffset, &limit_offset_parallel_safe_ignoring_params);
+
+ if (current_rel->consider_parallel &&
+ limit_count_parallel_safe &&
+ limit_offset_parallel_safe)
+ final_rel->consider_parallel = true;
+ if (current_rel->consider_parallel_rechecking_params &&
+ limit_count_parallel_safe_ignoring_params &&
+ limit_offset_parallel_safe_ignoring_params)
+ final_rel->consider_parallel_rechecking_params = true;
+ }
/*
* If the current_rel belongs to a single FDW, so does the final_rel.
@@ -1869,8 +1896,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
*/
- if (final_rel->consider_parallel && root->query_level > 1 &&
- !limit_needed(parse))
+ if ((final_rel->consider_parallel || final_rel->consider_parallel_rechecking_params) &&
+ root->query_level > 1 && !limit_needed(parse))
{
Assert(!parse->rowMarks && parse->commandType == CMD_SELECT);
foreach(lc, current_rel->partial_pathlist)
@@ -3282,6 +3309,7 @@ create_grouping_paths(PlannerInfo *root,
RelOptInfo *input_rel,
PathTarget *target,
bool target_parallel_safe,
+ bool target_parallel_safe_ignoring_params,
grouping_sets_data *gd)
{
Query *parse = root->parse;
@@ -3297,7 +3325,9 @@ create_grouping_paths(PlannerInfo *root,
* aggregation paths.
*/
grouped_rel = make_grouping_rel(root, input_rel, target,
- target_parallel_safe, parse->havingQual);
+ target_parallel_safe,
+ target_parallel_safe_ignoring_params,
+ parse->havingQual);
/*
* Create either paths for a degenerate grouping or paths for ordinary
@@ -3358,6 +3388,7 @@ create_grouping_paths(PlannerInfo *root,
extra.flags = flags;
extra.target_parallel_safe = target_parallel_safe;
+ extra.target_parallel_safe_ignoring_params = target_parallel_safe_ignoring_params;
extra.havingQual = parse->havingQual;
extra.targetList = parse->targetList;
extra.partial_costs_set = false;
@@ -3393,7 +3424,7 @@ create_grouping_paths(PlannerInfo *root,
static RelOptInfo *
make_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
PathTarget *target, bool target_parallel_safe,
- Node *havingQual)
+ bool target_parallel_safe_ignoring_params, Node *havingQual)
{
RelOptInfo *grouped_rel;
@@ -3421,9 +3452,21 @@ make_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
* can't be parallel-safe, either. Otherwise, it's parallel-safe if the
* target list and HAVING quals are parallel-safe.
*/
- if (input_rel->consider_parallel && target_parallel_safe &&
- is_parallel_safe(root, (Node *) havingQual))
- grouped_rel->consider_parallel = true;
+ if ((input_rel->consider_parallel || input_rel->consider_parallel_rechecking_params)
+ && (target_parallel_safe || target_parallel_safe_ignoring_params))
+ {
+ bool having_qual_parallel_safe;
+ bool having_qual_parallel_safe_ignoring_params = false;
+
+ having_qual_parallel_safe = is_parallel_safe(root, (Node *) havingQual,
+ &having_qual_parallel_safe_ignoring_params);
+
+ grouped_rel->consider_parallel = input_rel->consider_parallel &&
+ having_qual_parallel_safe && target_parallel_safe;
+ grouped_rel->consider_parallel_rechecking_params =
+ input_rel->consider_parallel_rechecking_params &&
+ having_qual_parallel_safe_ignoring_params && target_parallel_safe_ignoring_params;
+ }
/*
* If the input rel belongs to a single FDW, so does the grouped rel.
@@ -4050,7 +4093,7 @@ create_window_paths(PlannerInfo *root,
* target list and active windows for non-parallel-safe constructs.
*/
if (input_rel->consider_parallel && output_target_parallel_safe &&
- is_parallel_safe(root, (Node *) activeWindows))
+ is_parallel_safe(root, (Node *) activeWindows, NULL))
window_rel->consider_parallel = true;
/*
@@ -5755,6 +5798,7 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
PathTarget *thistarget = lfirst_node(PathTarget, lc1);
bool contains_srfs = (bool) lfirst_int(lc2);
+ /* TODO: How do we know the new target is parallel safe? */
/* If this level doesn't contain SRFs, do regular projection */
if (contains_srfs)
newpath = (Path *) create_set_projection_path(root,
@@ -6071,8 +6115,8 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
* safe.
*/
if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
- !is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
- !is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
+ !is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index), NULL) ||
+ !is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index), NULL))
{
parallel_workers = 0;
goto done;
@@ -6535,6 +6579,8 @@ create_partial_grouping_paths(PlannerInfo *root,
grouped_rel->relids);
partially_grouped_rel->consider_parallel =
grouped_rel->consider_parallel;
+ partially_grouped_rel->consider_parallel_rechecking_params =
+ grouped_rel->consider_parallel_rechecking_params;
partially_grouped_rel->reloptkind = grouped_rel->reloptkind;
partially_grouped_rel->serverid = grouped_rel->serverid;
partially_grouped_rel->userid = grouped_rel->userid;
@@ -6998,6 +7044,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
List *scanjoin_targets,
List *scanjoin_targets_contain_srfs,
bool scanjoin_target_parallel_safe,
+ bool scanjoin_target_parallel_safe_ignoring_params,
bool tlist_same_exprs)
{
bool rel_is_partitioned = IS_PARTITIONED_REL(rel);
@@ -7007,6 +7054,11 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
/* This recurses, so be paranoid. */
check_stack_depth();
+ /*
+ * TOOD: when/how do we want to generate gather paths if
+ * scanjoin_target_parallel_safe_ignoring_params = true
+ */
+
/*
* If the rel is partitioned, we want to drop its existing paths and
* generate new ones. This function would still be correct if we kept the
@@ -7047,6 +7099,64 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
generate_useful_gather_paths(root, rel, false);
/* Can't use parallel query above this level. */
+
+ /*
+ * There are cases where:
+ * (rel->consider_parallel &&
+ * !scanjoin_target_parallel_safe &&
+ * scanjoin_target_parallel_safe_ignoring_params)
+ * is true at this point. See longer commment below.
+ */
+ if (!(rel->consider_parallel_rechecking_params && scanjoin_target_parallel_safe_ignoring_params))
+ {
+ /*
+ * TODO: if we limit this to this condition, we're pushing off the
+ * checks as to whether or not a given param usage is safe in the
+ * context of a given path (in the context of a given rel?). That
+ * almost certainly means we'd have to add other checks later (maybe
+ * just on lateral/relids and not parallel safety overall), because
+ * at the end of grouping_planner() we copy partial paths to the
+ * final_rel, and while that path may be acceptable in some contexts
+ * it may not be in all contexts.
+ *
+ * OTOH if we're only dependent on PARAM_EXEC params, and we already
+ * know that subpath->param_info == NULL holds (and that seems like
+ * it must since we were going to replace the path target anyway...
+ * though the one caveat is from the original form of this function
+ * we'd only ever actually assert that for paths not partial paths)
+ * then if a param shows up in the target why would it not be parallel
+ * safe.
+ *
+ * Adding to the mystery even with the original form of this function
+ * we still end up with parallel paths where I'd expect this to
+ * disallow them. For example:
+ *
+ * SELECT '' AS six, f1 AS "Correlated Field", f3 AS "Second Field"
+ * FROM SUBSELECT_TBL upper
+ * WHERE f3 IN (
+ * SELECT upper.f1 + f2
+ * FROM SUBSELECT_TBL
+ * WHERE f2 = CAST(f3 AS integer)
+ * );
+ *
+ * ends up with the correlated query underneath parallel plan despite
+ * its target containing a param, and therefore this function marking
+ * the rel as consider_parallel=false and removing the partial paths.
+ *
+ * But the plan as a whole is parallel safe, and so the subplan is also
+ * parallel safe, which means we can incorporate it into a full parallel
+ * plan. In other words, this is a parallel safe, but not parallel aware
+ * subplan (and regular, not parallel, seq scan inside that subplan).
+ * It's not a partial path; it'a a full path that is executed as a subquery.
+ *
+ * Current conclusion: it's fine for subplans, which is the case we're
+ * currently targeting anyway. And it might even be the only case that
+ * matters at all.
+ */
+ /* rel->consider_parallel_rechecking_params = false; */
+ /* rel->partial_pathlist = NIL; */
+ }
+ rel->consider_parallel_rechecking_params = false;
rel->partial_pathlist = NIL;
rel->consider_parallel = false;
}
@@ -7182,6 +7292,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
child_scanjoin_targets,
scanjoin_targets_contain_srfs,
scanjoin_target_parallel_safe,
+ scanjoin_target_parallel_safe_ignoring_params,
tlist_same_exprs);
/* Save non-dummy children for Append paths. */
@@ -7199,6 +7310,11 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
* avoid creating multiple Gather nodes within the same plan. We must do
* this after all paths have been generated and before set_cheapest, since
* one of the generated paths may turn out to be the cheapest one.
+ *
+ * TODO: This is the same problem as earlier in this function: when allowing
+ * "parallel safe ignoring params" paths here we don't actually know we are
+ * safe in any possible context just possibly safe in the context of the
+ * right rel.
*/
if (rel->consider_parallel && !IS_OTHER_REL(rel))
generate_useful_gather_paths(root, rel, false);
@@ -7306,6 +7422,7 @@ create_partitionwise_grouping_paths(PlannerInfo *root,
child_grouped_rel = make_grouping_rel(root, child_input_rel,
child_target,
extra->target_parallel_safe,
+ extra->target_parallel_safe_ignoring_params,
child_extra.havingQual);
/* Create grouping paths for this child relation. */
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index c9f7a09d10..393db3b42d 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -342,6 +342,7 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
splan->useHashTable = false;
splan->unknownEqFalse = unknownEqFalse;
splan->parallel_safe = plan->parallel_safe;
+ splan->parallel_safe_ignoring_params = plan->parallel_safe_ignoring_params;
splan->setParam = NIL;
splan->parParam = NIL;
splan->args = NIL;
@@ -1937,6 +1938,7 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
{
SubLink *sublink = (SubLink *) node;
Node *testexpr;
+ Node *result;
/*
* First, recursively process the lefthand-side expressions, if any.
@@ -1948,12 +1950,29 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
/*
* Now build the SubPlan node and make the expr to return.
*/
- return make_subplan(context->root,
+ result = make_subplan(context->root,
(Query *) sublink->subselect,
sublink->subLinkType,
sublink->subLinkId,
testexpr,
context->isTopQual);
+
+ /*
+ * If planning determined that a subpath was parallel safe as long
+ * as required params are provided by each individual worker then we
+ * can mark the resulting subplan actually parallel safe since we now
+ * know for certain how that path will be used.
+ */
+ if (IsA(result, SubPlan) && !((SubPlan*)result)->parallel_safe
+ && ((SubPlan*)result)->parallel_safe_ignoring_params
+ && enable_parallel_params_recheck)
+ {
+ Plan *subplan = planner_subplan_get_plan(context->root, (SubPlan*)result);
+ ((SubPlan*)result)->parallel_safe = is_parallel_safe(context->root, testexpr, NULL);
+ subplan->parallel_safe = ((SubPlan*)result)->parallel_safe;
+ }
+
+ return result;
}
/*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e9256a2d4d..16cb35c914 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -409,6 +409,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
Assert(subpath->param_info == NULL);
/* avoid apply_projection_to_path, in case of multiple refs */
+ /* TODO: how to we know the target is parallel safe? */
path = (Path *) create_projection_path(root, subpath->parent,
subpath, target);
lfirst(lc) = path;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 3412d31117..e2cf335f4a 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -55,6 +55,15 @@
#include "utils/syscache.h"
#include "utils/typcache.h"
+bool enable_parallel_params_recheck = true;
+
+typedef struct
+{
+ PlannerInfo *root;
+ AggSplit aggsplit;
+ AggClauseCosts *costs;
+} get_agg_clause_costs_context;
+
typedef struct
{
ParamListInfo boundParams;
@@ -88,6 +97,8 @@ typedef struct
{
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
+ char max_hazard_ignoring_params;
+ bool check_params_independently;
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
} max_parallel_hazard_context;
@@ -624,19 +635,27 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
+ context.check_params_independently = false;
(void) max_parallel_hazard_walker((Node *) parse, &context);
return context.max_hazard;
}
/*
* is_parallel_safe
- * Detect whether the given expr contains only parallel-safe functions
+ * Detect whether the given expr contains only parallel safe funcions
+ * XXX: It does more than functions?
+ *
+ * If provided, safe_ignoring_params will be set the result of running the same
+ * parallel safety checks with the exception that params will be allowed. This
+ * value is useful since params are not inherently parallel unsafe, but rather
+ * their usage context (whether or not the worker is able to provide the value)
+ * determines parallel safety.
*
* root->glob->maxParallelHazard must previously have been set to the
- * result of max_parallel_hazard() on the whole query.
+ * result of max_parallel_hazard() on the whole query
*/
bool
-is_parallel_safe(PlannerInfo *root, Node *node)
+is_parallel_safe(PlannerInfo *root, Node *node, bool *safe_ignoring_params)
{
max_parallel_hazard_context context;
PlannerInfo *proot;
@@ -653,8 +672,10 @@ is_parallel_safe(PlannerInfo *root, Node *node)
return true;
/* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
context.max_hazard = PROPARALLEL_SAFE;
+ context.max_hazard_ignoring_params = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+ context.check_params_independently = safe_ignoring_params != NULL;
/*
* The params that refer to the same or parent query level are considered
@@ -672,12 +693,17 @@ is_parallel_safe(PlannerInfo *root, Node *node)
}
}
- return !max_parallel_hazard_walker(node, &context);
+ (void) max_parallel_hazard_walker(node, &context);
+
+ if (safe_ignoring_params)
+ *safe_ignoring_params = context.max_hazard_ignoring_params == PROPARALLEL_SAFE;
+
+ return context.max_hazard == PROPARALLEL_SAFE;
}
/* core logic for all parallel-hazard checks */
static bool
-max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
+max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context, bool from_param)
{
switch (proparallel)
{
@@ -688,12 +714,16 @@ max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
/* increase max_hazard to RESTRICTED */
Assert(context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = proparallel;
+ if (!from_param)
+ context->max_hazard_ignoring_params = proparallel;
/* done if we are not expecting any unsafe functions */
if (context->max_interesting == proparallel)
return true;
break;
case PROPARALLEL_UNSAFE:
context->max_hazard = proparallel;
+ if (!from_param)
+ context->max_hazard_ignoring_params = proparallel;
/* we're always done at the first unsafe construct */
return true;
default:
@@ -708,7 +738,41 @@ static bool
max_parallel_hazard_checker(Oid func_id, void *context)
{
return max_parallel_hazard_test(func_parallel(func_id),
- (max_parallel_hazard_context *) context);
+ (max_parallel_hazard_context *) context, false);
+}
+
+static bool
+max_parallel_hazard_walker_can_short_circuit(max_parallel_hazard_context *context)
+{
+ if (!context->check_params_independently)
+ return true;
+
+ switch (context->max_hazard)
+ {
+ case PROPARALLEL_SAFE:
+ /* nothing to see here, move along */
+ break;
+ case PROPARALLEL_RESTRICTED:
+ if (context->max_interesting == PROPARALLEL_RESTRICTED)
+ return context->max_hazard_ignoring_params != PROPARALLEL_SAFE;
+
+ /*
+ * We haven't even met our max interesting yet, so
+ * we certainly can't short-circuit.
+ */
+ break;
+ case PROPARALLEL_UNSAFE:
+ if (context->max_interesting == PROPARALLEL_RESTRICTED)
+ return context->max_hazard_ignoring_params != PROPARALLEL_SAFE;
+ else if (context->max_interesting == PROPARALLEL_UNSAFE)
+ return context->max_hazard_ignoring_params == PROPARALLEL_UNSAFE;
+
+ break;
+ default:
+ elog(ERROR, "unrecognized proparallel value \"%c\"", context->max_hazard);
+ break;
+ }
+ return false;
}
static bool
@@ -720,7 +784,7 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
/* Check for hazardous functions in node itself */
if (check_functions_in_node(node, max_parallel_hazard_checker,
context))
- return true;
+ return max_parallel_hazard_walker_can_short_circuit(context);
/*
* It should be OK to treat MinMaxExpr as parallel-safe, since btree
@@ -735,14 +799,14 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
*/
if (IsA(node, CoerceToDomain))
{
- if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
- return true;
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context, false))
+ return max_parallel_hazard_walker_can_short_circuit(context);
}
else if (IsA(node, NextValueExpr))
{
- if (max_parallel_hazard_test(PROPARALLEL_UNSAFE, context))
- return true;
+ if (max_parallel_hazard_test(PROPARALLEL_UNSAFE, context, false))
+ return max_parallel_hazard_walker_can_short_circuit(context);
}
/*
@@ -755,8 +819,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
*/
else if (IsA(node, WindowFunc))
{
- if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
- return true;
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context, false))
+ return max_parallel_hazard_walker_can_short_circuit(context);
}
/*
@@ -775,8 +839,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
*/
else if (IsA(node, SubLink))
{
- if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
- return true;
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context, false))
+ return max_parallel_hazard_walker_can_short_circuit(context);
}
/*
@@ -791,18 +855,23 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
List *save_safe_param_ids;
if (!subplan->parallel_safe &&
- max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+ (!enable_parallel_params_recheck || !subplan->parallel_safe_ignoring_params) &&
+ max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context, false) &&
+ max_parallel_hazard_walker_can_short_circuit(context))
return true;
save_safe_param_ids = context->safe_param_ids;
context->safe_param_ids = list_concat_copy(context->safe_param_ids,
subplan->paramIds);
- if (max_parallel_hazard_walker(subplan->testexpr, context))
- return true; /* no need to restore safe_param_ids */
+ if (max_parallel_hazard_walker(subplan->testexpr, context) &&
+ max_parallel_hazard_walker_can_short_circuit(context))
+ /* no need to restore safe_param_ids */
+ return true;
+
list_free(context->safe_param_ids);
context->safe_param_ids = save_safe_param_ids;
/* we must also check args, but no special Param treatment there */
if (max_parallel_hazard_walker((Node *) subplan->args, context))
- return true;
+ return max_parallel_hazard_walker_can_short_circuit(context);
/* don't want to recurse normally, so we're done */
return false;
}
@@ -824,8 +893,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
if (param->paramkind != PARAM_EXEC ||
!list_member_int(context->safe_param_ids, param->paramid))
{
- if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
- return true;
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context, true))
+ return max_parallel_hazard_walker_can_short_circuit(context);
}
return false; /* nothing to recurse to */
}
@@ -843,7 +912,7 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
if (query->rowMarks != NULL)
{
context->max_hazard = PROPARALLEL_UNSAFE;
- return true;
+ return max_parallel_hazard_walker_can_short_circuit(context);
}
/* Recurse into subselects */
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index a53850b370..21be3824c1 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -756,10 +756,10 @@ add_partial_path(RelOptInfo *parent_rel, Path *new_path)
CHECK_FOR_INTERRUPTS();
/* Path to be added must be parallel safe. */
- Assert(new_path->parallel_safe);
+ Assert(new_path->parallel_safe || new_path->parallel_safe_ignoring_params);
/* Relation should be OK for parallelism, too. */
- Assert(parent_rel->consider_parallel);
+ Assert(parent_rel->consider_parallel || parent_rel->consider_parallel_rechecking_params);
/*
* As in add_path, throw out any paths which are dominated by the new
@@ -938,6 +938,7 @@ create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
required_outer);
pathnode->parallel_aware = parallel_workers > 0 ? true : false;
pathnode->parallel_safe = rel->consider_parallel;
+ pathnode->parallel_safe_ignoring_params = rel->consider_parallel_rechecking_params;
pathnode->parallel_workers = parallel_workers;
pathnode->pathkeys = NIL; /* seqscan has unordered result */
@@ -1016,6 +1017,7 @@ create_index_path(PlannerInfo *root,
required_outer);
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel;
+ pathnode->path.parallel_safe_ignoring_params = rel->consider_parallel_rechecking_params;
pathnode->path.parallel_workers = 0;
pathnode->path.pathkeys = pathkeys;
@@ -1865,7 +1867,7 @@ create_gather_merge_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
Cost input_startup_cost = 0;
Cost input_total_cost = 0;
- Assert(subpath->parallel_safe);
+ Assert(subpath->parallel_safe || subpath->parallel_safe_ignoring_params);
Assert(pathkeys);
pathnode->path.pathtype = T_GatherMerge;
@@ -1953,7 +1955,7 @@ create_gather_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
{
GatherPath *pathnode = makeNode(GatherPath);
- Assert(subpath->parallel_safe);
+ Assert(subpath->parallel_safe || subpath->parallel_safe_ignoring_params);
pathnode->path.pathtype = T_Gather;
pathnode->path.parent = rel;
@@ -2000,6 +2002,8 @@ create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.parallel_safe_ignoring_params = rel->consider_parallel_rechecking_params &&
+ subpath->parallel_safe_ignoring_params;
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.pathkeys = pathkeys;
pathnode->subpath = subpath;
@@ -2417,6 +2421,8 @@ create_nestloop_path(PlannerInfo *root,
NestPath *pathnode = makeNode(NestPath);
Relids inner_req_outer = PATH_REQ_OUTER(inner_path);
+ /* TODO: Assert lateral relids subset safety? */
+
/*
* If the inner path is parameterized by the outer, we must drop any
* restrict_clauses that are due to be moved into the inner path. We have
@@ -2457,6 +2463,8 @@ create_nestloop_path(PlannerInfo *root,
pathnode->jpath.path.parallel_aware = false;
pathnode->jpath.path.parallel_safe = joinrel->consider_parallel &&
outer_path->parallel_safe && inner_path->parallel_safe;
+ pathnode->jpath.path.parallel_safe_ignoring_params = joinrel->consider_parallel_rechecking_params &&
+ outer_path->parallel_safe_ignoring_params && inner_path->parallel_safe_ignoring_params;
/* This is a foolish way to estimate parallel_workers, but for now... */
pathnode->jpath.path.parallel_workers = outer_path->parallel_workers;
pathnode->jpath.path.pathkeys = pathkeys;
@@ -2630,6 +2638,8 @@ create_projection_path(PlannerInfo *root,
{
ProjectionPath *pathnode = makeNode(ProjectionPath);
PathTarget *oldtarget;
+ bool target_parallel_safe;
+ bool target_parallel_safe_ignoring_params = false;
/*
* We mustn't put a ProjectionPath directly above another; it's useless
@@ -2653,9 +2663,12 @@ create_projection_path(PlannerInfo *root,
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
+ target_parallel_safe = is_parallel_safe(root, (Node *) target->exprs,
+ &target_parallel_safe_ignoring_params);
pathnode->path.parallel_safe = rel->consider_parallel &&
- subpath->parallel_safe &&
- is_parallel_safe(root, (Node *) target->exprs);
+ subpath->parallel_safe && target_parallel_safe;
+ pathnode->path.parallel_safe_ignoring_params = rel->consider_parallel_rechecking_params &&
+ subpath->parallel_safe_ignoring_params && target_parallel_safe_ignoring_params;
pathnode->path.parallel_workers = subpath->parallel_workers;
/* Projection does not change the sort order */
pathnode->path.pathkeys = subpath->pathkeys;
@@ -2763,7 +2776,7 @@ apply_projection_to_path(PlannerInfo *root,
* parallel-safe in the target expressions, then we can't.
*/
if ((IsA(path, GatherPath) || IsA(path, GatherMergePath)) &&
- is_parallel_safe(root, (Node *) target->exprs))
+ is_parallel_safe(root, (Node *) target->exprs, NULL))
{
/*
* We always use create_projection_path here, even if the subpath is
@@ -2797,7 +2810,7 @@ apply_projection_to_path(PlannerInfo *root,
}
}
else if (path->parallel_safe &&
- !is_parallel_safe(root, (Node *) target->exprs))
+ !is_parallel_safe(root, (Node *) target->exprs, NULL))
{
/*
* We're inserting a parallel-restricted target list into a path
@@ -2805,6 +2818,7 @@ apply_projection_to_path(PlannerInfo *root,
* safe.
*/
path->parallel_safe = false;
+ path->parallel_safe_ignoring_params = false; /* TODO */
}
return path;
@@ -2837,7 +2851,7 @@ create_set_projection_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe &&
- is_parallel_safe(root, (Node *) target->exprs);
+ is_parallel_safe(root, (Node *) target->exprs, NULL);
pathnode->path.parallel_workers = subpath->parallel_workers;
/* Projection does not change the sort order XXX? */
pathnode->path.pathkeys = subpath->pathkeys;
@@ -3114,6 +3128,8 @@ create_agg_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.parallel_safe_ignoring_params = rel->consider_parallel_rechecking_params &&
+ subpath->parallel_safe_ignoring_params;
pathnode->path.parallel_workers = subpath->parallel_workers;
if (aggstrategy == AGG_SORTED)
pathnode->path.pathkeys = subpath->pathkeys; /* preserves order */
@@ -3735,6 +3751,8 @@ create_limit_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.parallel_safe_ignoring_params = rel->consider_parallel_rechecking_params &&
+ subpath->parallel_safe_ignoring_params;
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.rows = subpath->rows;
pathnode->path.startup_cost = subpath->startup_cost;
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 47769cea45..9d908d0fea 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -213,6 +213,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
rel->consider_startup = (root->tuple_fraction > 0);
rel->consider_param_startup = false; /* might get changed later */
rel->consider_parallel = false; /* might get changed later */
+ rel->consider_parallel_rechecking_params = false; /* might get changed later */
rel->reltarget = create_empty_pathtarget();
rel->pathlist = NIL;
rel->ppilist = NIL;
@@ -617,6 +618,7 @@ build_join_rel(PlannerInfo *root,
joinrel->consider_startup = (root->tuple_fraction > 0);
joinrel->consider_param_startup = false;
joinrel->consider_parallel = false;
+ joinrel->consider_parallel_rechecking_params = false;
joinrel->reltarget = create_empty_pathtarget();
joinrel->pathlist = NIL;
joinrel->ppilist = NIL;
@@ -743,10 +745,27 @@ build_join_rel(PlannerInfo *root,
* take; therefore, we should make the same decision here however we get
* here.
*/
- if (inner_rel->consider_parallel && outer_rel->consider_parallel &&
- is_parallel_safe(root, (Node *) restrictlist) &&
- is_parallel_safe(root, (Node *) joinrel->reltarget->exprs))
- joinrel->consider_parallel = true;
+ if ((inner_rel->consider_parallel || inner_rel->consider_parallel_rechecking_params)
+ && (outer_rel->consider_parallel || outer_rel->consider_parallel_rechecking_params))
+ {
+ bool restrictlist_parallel_safe;
+ bool restrictlist_parallel_safe_ignoring_params = false;
+ bool target_parallel_safe;
+ bool target_parallel_safe_ignoring_params = false;
+
+ restrictlist_parallel_safe = is_parallel_safe(root, (Node *) restrictlist, &restrictlist_parallel_safe_ignoring_params);
+ target_parallel_safe = is_parallel_safe(root, (Node *) joinrel->reltarget->exprs, &target_parallel_safe_ignoring_params);
+
+ if (inner_rel->consider_parallel && outer_rel->consider_parallel
+ && restrictlist_parallel_safe && target_parallel_safe)
+ joinrel->consider_parallel = true;
+
+ if (inner_rel->consider_parallel_rechecking_params
+ && outer_rel->consider_parallel_rechecking_params
+ && restrictlist_parallel_safe_ignoring_params
+ && target_parallel_safe_ignoring_params)
+ joinrel->consider_parallel_rechecking_params = true;
+ }
/* Add the joinrel to the PlannerInfo. */
add_join_rel(root, joinrel);
@@ -805,6 +824,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
joinrel->consider_startup = (root->tuple_fraction > 0);
joinrel->consider_param_startup = false;
joinrel->consider_parallel = false;
+ joinrel->consider_parallel_rechecking_params = false;
joinrel->reltarget = create_empty_pathtarget();
joinrel->pathlist = NIL;
joinrel->ppilist = NIL;
@@ -892,6 +912,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
/* Child joinrel is parallel safe if parent is parallel safe. */
joinrel->consider_parallel = parent_joinrel->consider_parallel;
+ joinrel->consider_parallel_rechecking_params = parent_joinrel->consider_parallel_rechecking_params;
/* Set estimates of the child-joinrel's size. */
set_joinrel_size_estimates(root, joinrel, outer_rel, inner_rel,
@@ -1236,6 +1257,7 @@ fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind, Relids relids)
upperrel->consider_startup = (root->tuple_fraction > 0);
upperrel->consider_param_startup = false;
upperrel->consider_parallel = false; /* might get changed later */
+ upperrel->consider_parallel_rechecking_params = false; /* might get changed later */
upperrel->reltarget = create_empty_pathtarget();
upperrel->pathlist = NIL;
upperrel->cheapest_startup_path = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 467b0fd6fe..034510b611 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -57,6 +57,7 @@
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
+#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/geqo.h"
#include "optimizer/optimizer.h"
@@ -968,6 +969,16 @@ static const unit_conversion time_unit_conversion_table[] =
static struct config_bool ConfigureNamesBool[] =
{
+ {
+ {"enable_parallel_params_recheck", PGC_USERSET, QUERY_TUNING_METHOD,
+ gettext_noop("Enables the planner's rechecking of parallel safety in the presence of PARAM_EXEC params (for correlated subqueries)."),
+ NULL,
+ GUC_EXPLAIN
+ },
+ &enable_parallel_params_recheck,
+ true,
+ NULL, NULL, NULL
+ },
{
{"enable_seqscan", PGC_USERSET, QUERY_TUNING_METHOD,
gettext_noop("Enables the planner's use of sequential-scan plans."),
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 1abe233db2..f2c3d229b0 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -683,6 +683,7 @@ typedef struct RelOptInfo
bool consider_startup; /* keep cheap-startup-cost paths? */
bool consider_param_startup; /* ditto, for parameterized paths? */
bool consider_parallel; /* consider parallel paths? */
+ bool consider_parallel_rechecking_params; /* consider parallel paths? */
/* default result targetlist for Paths scanning this relation */
struct PathTarget *reltarget; /* list of Vars/Exprs, cost, width */
@@ -1182,6 +1183,7 @@ typedef struct Path
bool parallel_aware; /* engage parallel-aware logic? */
bool parallel_safe; /* OK to use as part of parallel plan? */
+ bool parallel_safe_ignoring_params; /* OK to use as part of parallel plan if worker context provides params? */
int parallel_workers; /* desired # of workers; 0 = not parallel */
/* estimated size/costs for path (see costsize.c for more info) */
@@ -2471,7 +2473,7 @@ typedef struct MinMaxAggInfo
* for conflicting purposes.
*
* In addition, PARAM_EXEC slots are assigned for Params representing outputs
- * from subplans (values that are setParam items for those subplans). These
+ * from subplans (values that are setParam items for those subplans). [TODO: is this true, or only for init plans?] These
* IDs need not be tracked via PlannerParamItems, since we do not need any
* duplicate-elimination nor later processing of the represented expressions.
* Instead, we just record the assignment of the slot number by appending to
@@ -2590,6 +2592,7 @@ typedef struct
/* Data which may differ across partitions. */
bool target_parallel_safe;
+ bool target_parallel_safe_ignoring_params;
Node *havingQual;
List *targetList;
PartitionwiseAggregateType patype;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index ec9a8b0c81..67805f2cfa 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -128,6 +128,7 @@ typedef struct Plan
*/
bool parallel_aware; /* engage parallel-aware logic? */
bool parallel_safe; /* OK to use as part of parallel plan? */
+ bool parallel_safe_ignoring_params; /* OK to use as part of parallel plan if worker context provides params? */
/*
* information needed for asynchronous execution
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index c04282f91f..df1e2496c7 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -764,6 +764,7 @@ typedef struct SubPlan
* spec result is UNKNOWN; this allows much
* simpler handling of null values */
bool parallel_safe; /* is the subplan parallel-safe? */
+ bool parallel_safe_ignoring_params; /* is the subplan parallel-safe when params are provided by the worker context? */
/* Note: parallel_safe does not consider contents of testexpr or args */
/* Information for passing params into and out of the subselect: */
/* setParam and parParam are lists of integers (param IDs) */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 0673887a85..df01be2c61 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -16,6 +16,8 @@
#include "nodes/pathnodes.h"
+extern PGDLLIMPORT bool enable_parallel_params_recheck;
+
typedef struct
{
int numWindowFuncs; /* total number of WindowFuncs found */
@@ -33,7 +35,7 @@ extern double expression_returns_set_rows(PlannerInfo *root, Node *clause);
extern bool contain_subplans(Node *clause);
extern char max_parallel_hazard(Query *parse);
-extern bool is_parallel_safe(PlannerInfo *root, Node *node);
+extern bool is_parallel_safe(PlannerInfo *root, Node *node, bool *safe_ignoring_params);
extern bool contain_nonstrict_functions(Node *clause);
extern bool contain_exec_param(Node *clause, List *param_ids);
extern bool contain_leaked_vars(Node *clause);
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
index 545e301e48..8f9ca05e60 100644
--- a/src/test/regress/expected/incremental_sort.out
+++ b/src/test/regress/expected/incremental_sort.out
@@ -1614,16 +1614,16 @@ from tenk1 t, generate_series(1, 1000);
QUERY PLAN
---------------------------------------------------------------------------------
Unique
- -> Sort
- Sort Key: t.unique1, ((SubPlan 1))
- -> Gather
- Workers Planned: 2
+ -> Gather Merge
+ Workers Planned: 2
+ -> Sort
+ Sort Key: t.unique1, ((SubPlan 1))
-> Nested Loop
-> Parallel Index Only Scan using tenk1_unique1 on tenk1 t
-> Function Scan on generate_series
- SubPlan 1
- -> Index Only Scan using tenk1_unique1 on tenk1
- Index Cond: (unique1 = t.unique1)
+ SubPlan 1
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = t.unique1)
(11 rows)
explain (costs off) select
@@ -1633,16 +1633,16 @@ from tenk1 t, generate_series(1, 1000)
order by 1, 2;
QUERY PLAN
---------------------------------------------------------------------------
- Sort
- Sort Key: t.unique1, ((SubPlan 1))
- -> Gather
- Workers Planned: 2
+ Gather Merge
+ Workers Planned: 2
+ -> Sort
+ Sort Key: t.unique1, ((SubPlan 1))
-> Nested Loop
-> Parallel Index Only Scan using tenk1_unique1 on tenk1 t
-> Function Scan on generate_series
- SubPlan 1
- -> Index Only Scan using tenk1_unique1 on tenk1
- Index Cond: (unique1 = t.unique1)
+ SubPlan 1
+ -> Index Only Scan using tenk1_unique1 on tenk1
+ Index Cond: (unique1 = t.unique1)
(10 rows)
-- Parallel sort but with expression not available until the upper rel.
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 2303f70d6e..253f117d7a 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -311,6 +311,131 @@ select count(*) from tenk1 where (two, four) not in
10000
(1 row)
+-- test parallel plans for queries containing correlated subplans
+-- where the subplan only needs params available from the current
+-- worker's scan.
+explain (costs off, verbose) select
+ (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
+ from tenk1 t, generate_series(1, 10);
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Gather
+ Output: ((SubPlan 1))
+ Workers Planned: 4
+ -> Nested Loop
+ Output: (SubPlan 1)
+ -> Parallel Index Only Scan using tenk1_unique1 on public.tenk1 t
+ Output: t.unique1
+ -> Function Scan on pg_catalog.generate_series
+ Output: generate_series.generate_series
+ Function Call: generate_series(1, 10)
+ SubPlan 1
+ -> Index Only Scan using tenk1_unique1 on public.tenk1
+ Output: t.unique1
+ Index Cond: (tenk1.unique1 = t.unique1)
+(14 rows)
+
+explain (costs off, verbose) select
+ (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
+ from tenk1 t;
+ QUERY PLAN
+----------------------------------------------------------------------
+ Gather
+ Output: ((SubPlan 1))
+ Workers Planned: 4
+ -> Parallel Index Only Scan using tenk1_unique1 on public.tenk1 t
+ Output: (SubPlan 1)
+ SubPlan 1
+ -> Index Only Scan using tenk1_unique1 on public.tenk1
+ Output: t.unique1
+ Index Cond: (tenk1.unique1 = t.unique1)
+(9 rows)
+
+explain (analyze, costs off, summary off, verbose, timing off) select
+ (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
+ from tenk1 t
+ limit 1;
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------
+ Limit (actual rows=1 loops=1)
+ Output: ((SubPlan 1))
+ -> Gather (actual rows=1 loops=1)
+ Output: ((SubPlan 1))
+ Workers Planned: 4
+ Workers Launched: 4
+ -> Parallel Index Only Scan using tenk1_unique1 on public.tenk1 t (actual rows=1 loops=5)
+ Output: (SubPlan 1)
+ Heap Fetches: 0
+ Worker 0: actual rows=1 loops=1
+ Worker 1: actual rows=1 loops=1
+ Worker 2: actual rows=1 loops=1
+ Worker 3: actual rows=1 loops=1
+ SubPlan 1
+ -> Index Only Scan using tenk1_unique1 on public.tenk1 (actual rows=1 loops=5)
+ Output: t.unique1
+ Index Cond: (tenk1.unique1 = t.unique1)
+ Heap Fetches: 0
+ Worker 0: actual rows=1 loops=1
+ Worker 1: actual rows=1 loops=1
+ Worker 2: actual rows=1 loops=1
+ Worker 3: actual rows=1 loops=1
+(22 rows)
+
+explain (costs off, verbose) select t.unique1
+ from tenk1 t
+ where t.unique1 = (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1);
+ QUERY PLAN
+----------------------------------------------------------------------
+ Gather
+ Output: t.unique1
+ Workers Planned: 4
+ -> Parallel Index Only Scan using tenk1_unique1 on public.tenk1 t
+ Output: t.unique1
+ Filter: (t.unique1 = (SubPlan 1))
+ SubPlan 1
+ -> Index Only Scan using tenk1_unique1 on public.tenk1
+ Output: t.unique1
+ Index Cond: (tenk1.unique1 = t.unique1)
+(10 rows)
+
+explain (costs off, verbose) select *
+ from tenk1 t
+ order by (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1);
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Gather Merge
+ Output: t.unique1, t.unique2, t.two, t.four, t.ten, t.twenty, t.hundred, t.thousand, t.twothousand, t.fivethous, t.tenthous, t.odd, t.even, t.stringu1, t.stringu2, t.string4, ((SubPlan 1))
+ Workers Planned: 4
+ -> Sort
+ Output: t.unique1, t.unique2, t.two, t.four, t.ten, t.twenty, t.hundred, t.thousand, t.twothousand, t.fivethous, t.tenthous, t.odd, t.even, t.stringu1, t.stringu2, t.string4, ((SubPlan 1))
+ Sort Key: ((SubPlan 1))
+ -> Parallel Seq Scan on public.tenk1 t
+ Output: t.unique1, t.unique2, t.two, t.four, t.ten, t.twenty, t.hundred, t.thousand, t.twothousand, t.fivethous, t.tenthous, t.odd, t.even, t.stringu1, t.stringu2, t.string4, (SubPlan 1)
+ SubPlan 1
+ -> Index Only Scan using tenk1_unique1 on public.tenk1
+ Output: t.unique1
+ Index Cond: (tenk1.unique1 = t.unique1)
+(12 rows)
+
+-- test subplan in join/lateral join
+explain (costs off, verbose, timing off) select t.unique1, l.*
+ from tenk1 t
+ join lateral (
+ select (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1 offset 0)
+ ) l on true;
+ QUERY PLAN
+----------------------------------------------------------------------
+ Gather
+ Output: t.unique1, ((SubPlan 1))
+ Workers Planned: 4
+ -> Parallel Index Only Scan using tenk1_unique1 on public.tenk1 t
+ Output: t.unique1, (SubPlan 1)
+ SubPlan 1
+ -> Index Only Scan using tenk1_unique1 on public.tenk1
+ Output: t.unique1
+ Index Cond: (tenk1.unique1 = t.unique1)
+(9 rows)
+
-- this is not parallel-safe due to use of random() within SubLink's testexpr:
explain (costs off)
select * from tenk1 where (unique1 + random())::integer not in
@@ -1192,6 +1317,18 @@ EXECUTE pstmt('1', make_some_array(1,2));
DEALLOCATE pstmt;
-- test interaction between subquery and partial_paths
+-- this plan changes to using a non-parallel index only
+-- scan on tenk1_unique1 (the parallel version of the subquery scan
+-- is cheaper, but only by ~30, and cost comparison treats them as equal
+-- since the costs are so large) because set_rel_consider_parallel
+-- called from make_one_rel sees the subplan as parallel safe now
+-- (in context it now knows the params are actually parallel safe).
+-- Because of that the non-parallel index path is now parallel_safe=true,
+-- therefore it wins the COSTS_EQUAL comparison in add_path.
+-- Perhaps any is_parallel_safe calls made for the purpose of determining
+-- consider_parallel should disable that behavior? It's not clear which is
+-- correct.
+set enable_parallel_params_recheck = off;
CREATE VIEW tenk1_vw_sec WITH (security_barrier) AS SELECT * FROM tenk1;
EXPLAIN (COSTS OFF)
SELECT 1 FROM tenk1_vw_sec
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 6e54f3e15e..e4b48fc61d 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -109,13 +109,14 @@ select name, setting from pg_settings where name like 'enable%';
enable_nestloop | on
enable_parallel_append | on
enable_parallel_hash | on
+ enable_parallel_params_recheck | on
enable_partition_pruning | on
enable_partitionwise_aggregate | off
enable_partitionwise_join | off
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(20 rows)
+(21 rows)
-- Test that the pg_timezone_names and pg_timezone_abbrevs views are
-- more-or-less working. We can't test their contents in any great detail
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index 019e17e751..f217d0ec9b 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -111,6 +111,31 @@ explain (costs off)
(select hundred, thousand from tenk2 where thousand > 100);
select count(*) from tenk1 where (two, four) not in
(select hundred, thousand from tenk2 where thousand > 100);
+-- test parallel plans for queries containing correlated subplans
+-- where the subplan only needs params available from the current
+-- worker's scan.
+explain (costs off, verbose) select
+ (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
+ from tenk1 t, generate_series(1, 10);
+explain (costs off, verbose) select
+ (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
+ from tenk1 t;
+explain (analyze, costs off, summary off, verbose, timing off) select
+ (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1)
+ from tenk1 t
+ limit 1;
+explain (costs off, verbose) select t.unique1
+ from tenk1 t
+ where t.unique1 = (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1);
+explain (costs off, verbose) select *
+ from tenk1 t
+ order by (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1);
+-- test subplan in join/lateral join
+explain (costs off, verbose, timing off) select t.unique1, l.*
+ from tenk1 t
+ join lateral (
+ select (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1 offset 0)
+ ) l on true;
-- this is not parallel-safe due to use of random() within SubLink's testexpr:
explain (costs off)
select * from tenk1 where (unique1 + random())::integer not in
@@ -456,6 +481,18 @@ EXECUTE pstmt('1', make_some_array(1,2));
DEALLOCATE pstmt;
-- test interaction between subquery and partial_paths
+-- this plan changes to using a non-parallel index only
+-- scan on tenk1_unique1 (the parallel version of the subquery scan
+-- is cheaper, but only by ~30, and cost comparison treats them as equal
+-- since the costs are so large) because set_rel_consider_parallel
+-- called from make_one_rel sees the subplan as parallel safe now
+-- (in context it now knows the params are actually parallel safe).
+-- Because of that the non-parallel index path is now parallel_safe=true,
+-- therefore it wins the COSTS_EQUAL comparison in add_path.
+-- Perhaps any is_parallel_safe calls made for the purpose of determining
+-- consider_parallel should disable that behavior? It's not clear which is
+-- correct.
+set enable_parallel_params_recheck = off;
CREATE VIEW tenk1_vw_sec WITH (security_barrier) AS SELECT * FROM tenk1;
EXPLAIN (COSTS OFF)
SELECT 1 FROM tenk1_vw_sec
--
2.20.1