Commit da1c9163 authored by Tom Lane's avatar Tom Lane

Speed up planner's scanning for parallel-query hazards.

We need to scan the whole parse tree for parallel-unsafe functions.
If there are none, we'll later need to determine whether particular
subtrees contain any parallel-restricted functions.  The previous coding
retained no knowledge from the first scan, even though this is very
wasteful in the common case where the query contains only parallel-safe
functions.  We can bypass all of the later scans by remembering that fact.
This provides a small but measurable speed improvement when the case
applies, and shouldn't cost anything when it doesn't.

Patch by me, reviewed by Robert Haas

Discussion: <3740.1471538387@sss.pgh.pa.us>
parent 6f79ae7f
...@@ -2029,6 +2029,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node) ...@@ -2029,6 +2029,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
WRITE_BOOL_FIELD(dependsOnRole); WRITE_BOOL_FIELD(dependsOnRole);
WRITE_BOOL_FIELD(parallelModeOK); WRITE_BOOL_FIELD(parallelModeOK);
WRITE_BOOL_FIELD(parallelModeNeeded); WRITE_BOOL_FIELD(parallelModeNeeded);
WRITE_CHAR_FIELD(maxParallelHazard);
} }
static void static void
......
...@@ -78,7 +78,6 @@ static void set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, ...@@ -78,7 +78,6 @@ static void set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel,
static void create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel); static void create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel);
static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte); RangeTblEntry *rte);
static bool function_rte_parallel_ok(RangeTblEntry *rte);
static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte); RangeTblEntry *rte);
static void set_tablesample_rel_size(PlannerInfo *root, RelOptInfo *rel, static void set_tablesample_rel_size(PlannerInfo *root, RelOptInfo *rel,
...@@ -542,8 +541,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, ...@@ -542,8 +541,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
if (proparallel != PROPARALLEL_SAFE) if (proparallel != PROPARALLEL_SAFE)
return; return;
if (has_parallel_hazard((Node *) rte->tablesample->args, if (!is_parallel_safe(root, (Node *) rte->tablesample->args))
false))
return; return;
} }
...@@ -596,7 +594,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, ...@@ -596,7 +594,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
case RTE_FUNCTION: case RTE_FUNCTION:
/* Check for parallel-restricted functions. */ /* Check for parallel-restricted functions. */
if (!function_rte_parallel_ok(rte)) if (!is_parallel_safe(root, (Node *) rte->functions))
return; return;
break; break;
...@@ -629,40 +627,20 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, ...@@ -629,40 +627,20 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* outer join clauses work correctly. It would likely break equivalence * outer join clauses work correctly. It would likely break equivalence
* classes, too. * classes, too.
*/ */
if (has_parallel_hazard((Node *) rel->baserestrictinfo, false)) if (!is_parallel_safe(root, (Node *) rel->baserestrictinfo))
return; return;
/* /*
* Likewise, if the relation's outputs are not parallel-safe, give up. * Likewise, if the relation's outputs are not parallel-safe, give up.
* (Usually, they're just Vars, but sometimes they're not.) * (Usually, they're just Vars, but sometimes they're not.)
*/ */
if (has_parallel_hazard((Node *) rel->reltarget->exprs, false)) if (!is_parallel_safe(root, (Node *) rel->reltarget->exprs))
return; return;
/* We have a winner. */ /* We have a winner. */
rel->consider_parallel = true; rel->consider_parallel = true;
} }
/*
* Check whether a function RTE is scanning something parallel-restricted.
*/
static bool
function_rte_parallel_ok(RangeTblEntry *rte)
{
ListCell *lc;
foreach(lc, rte->functions)
{
RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
Assert(IsA(rtfunc, RangeTblFunction));
if (has_parallel_hazard(rtfunc->funcexpr, false))
return false;
}
return true;
}
/* /*
* set_plain_rel_pathlist * set_plain_rel_pathlist
* Build access paths for a plain relation (no subquery, no inheritance) * Build access paths for a plain relation (no subquery, no inheritance)
......
...@@ -71,14 +71,13 @@ query_planner(PlannerInfo *root, List *tlist, ...@@ -71,14 +71,13 @@ query_planner(PlannerInfo *root, List *tlist,
/* /*
* If query allows parallelism in general, check whether the quals are * If query allows parallelism in general, check whether the quals are
* parallel-restricted. There's currently no real benefit to setting * parallel-restricted. (We need not check final_rel->reltarget
* this flag correctly because we can't yet reference subplans from * because it's empty at this point. Anything parallel-restricted in
* parallel workers. But that might change someday, so set this * the query tlist will be dealt with later.)
* correctly anyway.
*/ */
if (root->glob->parallelModeOK) if (root->glob->parallelModeOK)
final_rel->consider_parallel = final_rel->consider_parallel =
!has_parallel_hazard(parse->jointree->quals, false); is_parallel_safe(root, parse->jointree->quals);
/* The only path for it is a trivial Result path */ /* The only path for it is a trivial Result path */
add_path(final_rel, (Path *) add_path(final_rel, (Path *)
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "access/sysattr.h" #include "access/sysattr.h"
#include "access/xact.h" #include "access/xact.h"
#include "catalog/pg_constraint_fn.h" #include "catalog/pg_constraint_fn.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "executor/executor.h" #include "executor/executor.h"
#include "executor/nodeAgg.h" #include "executor/nodeAgg.h"
...@@ -241,12 +242,26 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) ...@@ -241,12 +242,26 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
* time and execution time, so don't generate a parallel plan if we're in * time and execution time, so don't generate a parallel plan if we're in
* serializable mode. * serializable mode.
*/ */
glob->parallelModeOK = (cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 && if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster && dynamic_shared_memory_type != DSM_IMPL_NONE && IsUnderPostmaster &&
parse->commandType == CMD_SELECT && !parse->hasModifyingCTE && dynamic_shared_memory_type != DSM_IMPL_NONE &&
parse->utilityStmt == NULL && max_parallel_workers_per_gather > 0 && parse->commandType == CMD_SELECT &&
!IsParallelWorker() && !IsolationIsSerializable() && parse->utilityStmt == NULL &&
!has_parallel_hazard((Node *) parse, true); !parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker() &&
!IsolationIsSerializable())
{
/* all the cheap tests pass, so scan the query tree */
glob->maxParallelHazard = max_parallel_hazard(parse);
glob->parallelModeOK = (glob->maxParallelHazard != PROPARALLEL_UNSAFE);
}
else
{
/* skip the query tree scan, just assume it's unsafe */
glob->maxParallelHazard = PROPARALLEL_UNSAFE;
glob->parallelModeOK = false;
}
/* /*
* glob->parallelModeNeeded should tell us whether it's necessary to * glob->parallelModeNeeded should tell us whether it's necessary to
...@@ -1802,7 +1817,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, ...@@ -1802,7 +1817,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
* computed by partial paths. * computed by partial paths.
*/ */
if (current_rel->partial_pathlist && if (current_rel->partial_pathlist &&
!has_parallel_hazard((Node *) scanjoin_target->exprs, false)) is_parallel_safe(root, (Node *) scanjoin_target->exprs))
{ {
/* Apply the scan/join target to each partial path */ /* Apply the scan/join target to each partial path */
foreach(lc, current_rel->partial_pathlist) foreach(lc, current_rel->partial_pathlist)
...@@ -1948,8 +1963,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, ...@@ -1948,8 +1963,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
* query. * query.
*/ */
if (current_rel->consider_parallel && if (current_rel->consider_parallel &&
!has_parallel_hazard(parse->limitOffset, false) && is_parallel_safe(root, parse->limitOffset) &&
!has_parallel_hazard(parse->limitCount, false)) is_parallel_safe(root, parse->limitCount))
final_rel->consider_parallel = true; final_rel->consider_parallel = true;
/* /*
...@@ -3326,8 +3341,8 @@ create_grouping_paths(PlannerInfo *root, ...@@ -3326,8 +3341,8 @@ create_grouping_paths(PlannerInfo *root,
* target list and HAVING quals are parallel-safe. * target list and HAVING quals are parallel-safe.
*/ */
if (input_rel->consider_parallel && if (input_rel->consider_parallel &&
!has_parallel_hazard((Node *) target->exprs, false) && is_parallel_safe(root, (Node *) target->exprs) &&
!has_parallel_hazard((Node *) parse->havingQual, false)) is_parallel_safe(root, (Node *) parse->havingQual))
grouped_rel->consider_parallel = true; grouped_rel->consider_parallel = true;
/* /*
...@@ -3881,8 +3896,8 @@ create_window_paths(PlannerInfo *root, ...@@ -3881,8 +3896,8 @@ create_window_paths(PlannerInfo *root,
* target list and active windows for non-parallel-safe constructs. * target list and active windows for non-parallel-safe constructs.
*/ */
if (input_rel->consider_parallel && if (input_rel->consider_parallel &&
!has_parallel_hazard((Node *) output_target->exprs, false) && is_parallel_safe(root, (Node *) output_target->exprs) &&
!has_parallel_hazard((Node *) activeWindows, false)) is_parallel_safe(root, (Node *) activeWindows))
window_rel->consider_parallel = true; window_rel->consider_parallel = true;
/* /*
...@@ -4272,7 +4287,7 @@ create_ordered_paths(PlannerInfo *root, ...@@ -4272,7 +4287,7 @@ create_ordered_paths(PlannerInfo *root,
* target list is parallel-safe. * target list is parallel-safe.
*/ */
if (input_rel->consider_parallel && if (input_rel->consider_parallel &&
!has_parallel_hazard((Node *) target->exprs, false)) is_parallel_safe(root, (Node *) target->exprs))
ordered_rel->consider_parallel = true; ordered_rel->consider_parallel = true;
/* /*
......
...@@ -91,8 +91,9 @@ typedef struct ...@@ -91,8 +91,9 @@ typedef struct
typedef struct typedef struct
{ {
bool allow_restricted; char max_hazard; /* worst proparallel hazard found so far */
} has_parallel_hazard_arg; char max_interesting; /* worst proparallel hazard of interest */
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context); static bool contain_agg_clause_walker(Node *node, void *context);
static bool get_agg_clause_costs_walker(Node *node, static bool get_agg_clause_costs_walker(Node *node,
...@@ -103,8 +104,8 @@ static bool contain_subplans_walker(Node *node, void *context); ...@@ -103,8 +104,8 @@ static bool contain_subplans_walker(Node *node, void *context);
static bool contain_mutable_functions_walker(Node *node, void *context); static bool contain_mutable_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_walker(Node *node, void *context); static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context); static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
static bool has_parallel_hazard_walker(Node *node, static bool max_parallel_hazard_walker(Node *node,
has_parallel_hazard_arg *context); max_parallel_hazard_context *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context); static bool contain_nonstrict_functions_walker(Node *node, void *context);
static bool contain_context_dependent_node(Node *clause); static bool contain_context_dependent_node(Node *clause);
static bool contain_context_dependent_node_walker(Node *node, int *flags); static bool contain_context_dependent_node_walker(Node *node, int *flags);
...@@ -1100,46 +1101,98 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context) ...@@ -1100,46 +1101,98 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
context); context);
} }
/***************************************************************************** /*****************************************************************************
* Check queries for parallel unsafe and/or restricted constructs * Check queries for parallel unsafe and/or restricted constructs
*****************************************************************************/ *****************************************************************************/
/* /*
* Check whether a node tree contains parallel hazards. This is used both on * max_parallel_hazard
* the entire query tree, to see whether the query can be parallelized at all * Find the worst parallel-hazard level in the given query
* (with allow_restricted = true), and also to evaluate whether a particular *
* expression is safe to run within a parallel worker (with allow_restricted = * Returns the worst function hazard property (the earliest in this list:
* false). We could separate these concerns into two different functions, but * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
* there's enough overlap that it doesn't seem worthwhile. * be found in the given parsetree. We use this to find out whether the query
* can be parallelized at all. The caller will also save the result in
* PlannerGlobal so as to short-circuit checks of portions of the querytree
* later, in the common case where everything is SAFE.
*/
char
max_parallel_hazard(Query *parse)
{
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
(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
*
* root->glob->maxParallelHazard must previously have been set to the
* result of max_parallel_hazard() on the whole query.
*/ */
bool bool
has_parallel_hazard(Node *node, bool allow_restricted) is_parallel_safe(PlannerInfo *root, Node *node)
{ {
has_parallel_hazard_arg context; max_parallel_hazard_context context;
context.allow_restricted = allow_restricted; /* If max_parallel_hazard found nothing unsafe, we don't need to look */
return has_parallel_hazard_walker(node, &context); if (root->glob->maxParallelHazard == PROPARALLEL_SAFE)
return true;
/* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
return !max_parallel_hazard_walker(node, &context);
} }
/* core logic for all parallel-hazard checks */
static bool static bool
has_parallel_hazard_checker(Oid func_id, void *context) max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
{ {
char proparallel = func_parallel(func_id); switch (proparallel)
{
case PROPARALLEL_SAFE:
/* nothing to see here, move along */
break;
case PROPARALLEL_RESTRICTED:
/* increase max_hazard to RESTRICTED */
Assert(context->max_hazard != PROPARALLEL_UNSAFE);
context->max_hazard = 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;
/* we're always done at the first unsafe construct */
return true;
default:
elog(ERROR, "unrecognized proparallel value \"%c\"", proparallel);
break;
}
return false;
}
if (((has_parallel_hazard_arg *) context)->allow_restricted) /* check_functions_in_node callback */
return (proparallel == PROPARALLEL_UNSAFE); static bool
else max_parallel_hazard_checker(Oid func_id, void *context)
return (proparallel != PROPARALLEL_SAFE); {
return max_parallel_hazard_test(func_parallel(func_id),
(max_parallel_hazard_context *) context);
} }
static bool static bool
has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context) max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
{ {
if (node == NULL) if (node == NULL)
return false; return false;
/* Check for hazardous functions in node itself */ /* Check for hazardous functions in node itself */
if (check_functions_in_node(node, has_parallel_hazard_checker, if (check_functions_in_node(node, max_parallel_hazard_checker,
context)) context))
return true; return true;
...@@ -1156,7 +1209,7 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context) ...@@ -1156,7 +1209,7 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
*/ */
if (IsA(node, CoerceToDomain)) if (IsA(node, CoerceToDomain))
{ {
if (!context->allow_restricted) if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
return true; return true;
} }
...@@ -1167,7 +1220,7 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context) ...@@ -1167,7 +1220,7 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
{ {
RestrictInfo *rinfo = (RestrictInfo *) node; RestrictInfo *rinfo = (RestrictInfo *) node;
return has_parallel_hazard_walker((Node *) rinfo->clause, context); return max_parallel_hazard_walker((Node *) rinfo->clause, context);
} }
/* /*
...@@ -1176,13 +1229,13 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context) ...@@ -1176,13 +1229,13 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
* not worry about examining their contents; if they are unsafe, we would * not worry about examining their contents; if they are unsafe, we would
* have found that out while examining the whole tree before reduction of * have found that out while examining the whole tree before reduction of
* sublinks to subplans. (Really we should not see SubLink during a * sublinks to subplans. (Really we should not see SubLink during a
* not-allow_restricted scan, but if we do, return true.) * max_interesting == restricted scan, but if we do, return true.)
*/ */
else if (IsA(node, SubLink) || else if (IsA(node, SubLink) ||
IsA(node, SubPlan) || IsA(node, SubPlan) ||
IsA(node, AlternativeSubPlan)) IsA(node, AlternativeSubPlan))
{ {
if (!context->allow_restricted) if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
return true; return true;
} }
...@@ -1192,7 +1245,7 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context) ...@@ -1192,7 +1245,7 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
*/ */
else if (IsA(node, Param)) else if (IsA(node, Param))
{ {
if (!context->allow_restricted) if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
return true; return true;
} }
...@@ -1207,20 +1260,24 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context) ...@@ -1207,20 +1260,24 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
/* SELECT FOR UPDATE/SHARE must be treated as unsafe */ /* SELECT FOR UPDATE/SHARE must be treated as unsafe */
if (query->rowMarks != NULL) if (query->rowMarks != NULL)
{
context->max_hazard = PROPARALLEL_UNSAFE;
return true; return true;
}
/* Recurse into subselects */ /* Recurse into subselects */
return query_tree_walker(query, return query_tree_walker(query,
has_parallel_hazard_walker, max_parallel_hazard_walker,
context, 0); context, 0);
} }
/* Recurse to check arguments */ /* Recurse to check arguments */
return expression_tree_walker(node, return expression_tree_walker(node,
has_parallel_hazard_walker, max_parallel_hazard_walker,
context); context);
} }
/***************************************************************************** /*****************************************************************************
* Check clauses for nonstrict functions * Check clauses for nonstrict functions
*****************************************************************************/ *****************************************************************************/
......
...@@ -2178,7 +2178,7 @@ create_projection_path(PlannerInfo *root, ...@@ -2178,7 +2178,7 @@ create_projection_path(PlannerInfo *root,
pathnode->path.parallel_aware = false; pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel && pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe && subpath->parallel_safe &&
!has_parallel_hazard((Node *) target->exprs, false); is_parallel_safe(root, (Node *) target->exprs);
pathnode->path.parallel_workers = subpath->parallel_workers; pathnode->path.parallel_workers = subpath->parallel_workers;
/* Projection does not change the sort order */ /* Projection does not change the sort order */
pathnode->path.pathkeys = subpath->pathkeys; pathnode->path.pathkeys = subpath->pathkeys;
...@@ -2285,7 +2285,7 @@ apply_projection_to_path(PlannerInfo *root, ...@@ -2285,7 +2285,7 @@ apply_projection_to_path(PlannerInfo *root,
* target expressions, then we can't. * target expressions, then we can't.
*/ */
if (IsA(path, GatherPath) && if (IsA(path, GatherPath) &&
!has_parallel_hazard((Node *) target->exprs, false)) is_parallel_safe(root, (Node *) target->exprs))
{ {
GatherPath *gpath = (GatherPath *) path; GatherPath *gpath = (GatherPath *) path;
...@@ -2306,7 +2306,7 @@ apply_projection_to_path(PlannerInfo *root, ...@@ -2306,7 +2306,7 @@ apply_projection_to_path(PlannerInfo *root,
target); target);
} }
else if (path->parallel_safe && else if (path->parallel_safe &&
has_parallel_hazard((Node *) target->exprs, false)) !is_parallel_safe(root, (Node *) target->exprs))
{ {
/* /*
* We're inserting a parallel-restricted target list into a path * We're inserting a parallel-restricted target list into a path
......
...@@ -513,8 +513,8 @@ build_join_rel(PlannerInfo *root, ...@@ -513,8 +513,8 @@ build_join_rel(PlannerInfo *root,
* here. * here.
*/ */
if (inner_rel->consider_parallel && outer_rel->consider_parallel && if (inner_rel->consider_parallel && outer_rel->consider_parallel &&
!has_parallel_hazard((Node *) restrictlist, false) && is_parallel_safe(root, (Node *) restrictlist) &&
!has_parallel_hazard((Node *) joinrel->reltarget->exprs, false)) is_parallel_safe(root, (Node *) joinrel->reltarget->exprs))
joinrel->consider_parallel = true; joinrel->consider_parallel = true;
/* /*
......
...@@ -126,6 +126,8 @@ typedef struct PlannerGlobal ...@@ -126,6 +126,8 @@ typedef struct PlannerGlobal
bool parallelModeOK; /* parallel mode potentially OK? */ bool parallelModeOK; /* parallel mode potentially OK? */
bool parallelModeNeeded; /* parallel mode actually required? */ bool parallelModeNeeded; /* parallel mode actually required? */
char maxParallelHazard; /* worst PROPARALLEL hazard level */
} PlannerGlobal; } PlannerGlobal;
/* macro for fetching the Plan associated with a SubPlan node */ /* macro for fetching the Plan associated with a SubPlan node */
......
...@@ -61,7 +61,8 @@ extern bool contain_subplans(Node *clause); ...@@ -61,7 +61,8 @@ extern bool contain_subplans(Node *clause);
extern bool contain_mutable_functions(Node *clause); extern bool contain_mutable_functions(Node *clause);
extern bool contain_volatile_functions(Node *clause); extern bool contain_volatile_functions(Node *clause);
extern bool contain_volatile_functions_not_nextval(Node *clause); extern bool contain_volatile_functions_not_nextval(Node *clause);
extern bool has_parallel_hazard(Node *node, bool allow_restricted); extern char max_parallel_hazard(Query *parse);
extern bool is_parallel_safe(PlannerInfo *root, Node *node);
extern bool contain_nonstrict_functions(Node *clause); extern bool contain_nonstrict_functions(Node *clause);
extern bool contain_leaked_vars(Node *clause); extern bool contain_leaked_vars(Node *clause);
......
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