Commit 39151781 authored by Tom Lane's avatar Tom Lane

Fix testing of parallel-safety of SubPlans.

is_parallel_safe() supposed that the only relevant property of a SubPlan
was the parallel safety of the referenced subplan tree.  This is wrong:
the testexpr or args subtrees might contain parallel-unsafe stuff, as
demonstrated by the test case added here.  However, just recursing into the
subtrees fails in a different way: we'll typically find PARAM_EXEC Params
representing the subplan's output columns in the testexpr.  The previous
coding supposed that any Param must be treated as parallel-restricted, so
that a naive attempt at fixing this disabled parallel pushdown of SubPlans
altogether.  We must instead determine, for any visited Param, whether it
is one that would be computed by a surrounding SubPlan node; if so, it's
safe to push down along with the SubPlan node.

We might later be able to extend this logic to cope with Params used for
correlated subplans and other cases; but that's a task for v11 or beyond.

Tom Lane and Amit Kapila

Discussion: https://postgr.es/m/7064.1492022469@sss.pgh.pa.us
parent 539f6701
...@@ -93,6 +93,7 @@ typedef struct ...@@ -93,6 +93,7 @@ typedef struct
{ {
char max_hazard; /* worst proparallel hazard found so far */ char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */ char max_interesting; /* worst proparallel hazard of interest */
List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
} max_parallel_hazard_context; } max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context); static bool contain_agg_clause_walker(Node *node, void *context);
...@@ -1056,6 +1057,7 @@ max_parallel_hazard(Query *parse) ...@@ -1056,6 +1057,7 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE; context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE; context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
(void) max_parallel_hazard_walker((Node *) parse, &context); (void) max_parallel_hazard_walker((Node *) parse, &context);
return context.max_hazard; return context.max_hazard;
} }
...@@ -1084,6 +1086,7 @@ is_parallel_safe(PlannerInfo *root, Node *node) ...@@ -1084,6 +1086,7 @@ is_parallel_safe(PlannerInfo *root, Node *node)
/* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */ /* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
context.max_hazard = PROPARALLEL_SAFE; context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED; context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
return !max_parallel_hazard_walker(node, &context); return !max_parallel_hazard_walker(node, &context);
} }
...@@ -1171,18 +1174,49 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context) ...@@ -1171,18 +1174,49 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
return true; return true;
} }
/* We can push the subplans only if they are parallel-safe. */ /*
* Only parallel-safe SubPlans can be sent to workers. Within the
* testexpr of the SubPlan, Params representing the output columns of the
* subplan can be treated as parallel-safe, so temporarily add their IDs
* to the safe_param_ids list while examining the testexpr.
*/
else if (IsA(node, SubPlan)) else if (IsA(node, SubPlan))
return !((SubPlan *) node)->parallel_safe; {
SubPlan *subplan = (SubPlan *) node;
List *save_safe_param_ids;
if (!subplan->parallel_safe &&
max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
return true;
save_safe_param_ids = context->safe_param_ids;
context->safe_param_ids = list_concat(list_copy(subplan->paramIds),
context->safe_param_ids);
if (max_parallel_hazard_walker(subplan->testexpr, context))
return true; /* no need to restore 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;
/* don't want to recurse normally, so we're done */
return false;
}
/* /*
* We can't pass Params to workers at the moment either, so they are also * We can't pass Params to workers at the moment either, so they are also
* parallel-restricted. * parallel-restricted, unless they are PARAM_EXEC Params listed in
* safe_param_ids, meaning they could be generated within the worker.
*/ */
else if (IsA(node, Param)) else if (IsA(node, Param))
{ {
if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context)) Param *param = (Param *) node;
return true;
if (param->paramkind != PARAM_EXEC ||
!list_member_int(context->safe_param_ids, param->paramid))
{
if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
return true;
}
return false; /* nothing to recurse to */
} }
/* /*
......
...@@ -699,7 +699,8 @@ typedef struct SubPlan ...@@ -699,7 +699,8 @@ typedef struct SubPlan
bool unknownEqFalse; /* TRUE if it's okay to return FALSE when the bool unknownEqFalse; /* TRUE if it's okay to return FALSE when the
* spec result is UNKNOWN; this allows much * spec result is UNKNOWN; this allows much
* simpler handling of null values */ * simpler handling of null values */
bool parallel_safe; /* OK to use as part of parallel plan? */ bool parallel_safe; /* is the subplan parallel-safe? */
/* Note: parallel_safe does not consider contents of testexpr or args */
/* Information for passing params into and out of the subselect: */ /* Information for passing params into and out of the subselect: */
/* setParam and parParam are lists of integers (param IDs) */ /* setParam and parParam are lists of integers (param IDs) */
List *setParam; /* initplan subqueries have to set these List *setParam; /* initplan subqueries have to set these
......
...@@ -126,6 +126,18 @@ select count(*) from tenk1 where (two, four) not in ...@@ -126,6 +126,18 @@ select count(*) from tenk1 where (two, four) not in
10000 10000
(1 row) (1 row)
-- 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
(select ten from tenk2);
QUERY PLAN
------------------------------------
Seq Scan on tenk1
Filter: (NOT (hashed SubPlan 1))
SubPlan 1
-> Seq Scan on tenk2
(4 rows)
alter table tenk2 reset (parallel_workers); alter table tenk2 reset (parallel_workers);
-- test parallel index scans. -- test parallel index scans.
set enable_seqscan to off; set enable_seqscan to off;
......
...@@ -46,6 +46,10 @@ explain (costs off) ...@@ -46,6 +46,10 @@ explain (costs off)
(select hundred, thousand from tenk2 where thousand > 100); (select hundred, thousand from tenk2 where thousand > 100);
select count(*) from tenk1 where (two, four) not in select count(*) from tenk1 where (two, four) not in
(select hundred, thousand from tenk2 where thousand > 100); (select hundred, thousand from tenk2 where thousand > 100);
-- 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
(select ten from tenk2);
alter table tenk2 reset (parallel_workers); alter table tenk2 reset (parallel_workers);
-- test parallel index scans. -- test parallel index scans.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment