blob: 59a59d8a38330b2b94fb9da0d76538abf493c233 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*-------------------------------------------------------------------------
*
* cdbllize.c
* Parallelize a PostgreSQL sequential plan tree.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
#include "optimizer/pathnode.h"
#include "nodes/primnodes.h"
#include "nodes/parsenodes.h"
#include "nodes/plannodes.h"
#include "nodes/pg_list.h"
#include "nodes/print.h"
#include "optimizer/planmain.h" /* for is_projection_capable_plan() */
#include "optimizer/var.h" /* for contain_vars_of_level_or_above() */
#include "parser/parsetree.h" /* for rt_fetch() */
#include "utils/lsyscache.h" /* for getatttypetypmod() */
#include "parser/parse_oper.h" /* for compatible_oper_opid() */
#include "nodes/makefuncs.h" /* for makeVar() */
#include "nodes/value.h" /* for makeString() */
#include "utils/relcache.h" /* RelationGetPartitioningKey() */
#include "utils/debugbreak.h"
#include "cdb/cdbvars.h"
#include "cdb/cdbplan.h"
#include "cdb/cdbpullup.h"
#include "cdb/cdbllize.h"
#include "cdb/cdbcat.h"
#include "cdb/cdbmutate.h"
#include "optimizer/tlist.h"
#define ARRAYCOPY(to, from, sz) \
do { \
Size _size = (sz) * sizeof((to)[0]); \
(to) = palloc(_size); \
memcpy((to), (from), _size); \
} while (0)
/*
* A PlanProfile holds state for recursive prescan_walker().
*/
typedef struct PlanProfile
{
plan_tree_base_prefix base; /* Required prefix for plan_tree_walker/mutator */
PlannerInfo *root; /* cdbparallelize argument, root of top plan. */
/*
* The result flags refer to relation in the result of an insert, update,
* or delete
*/
bool resultSegments;
bool dispatchParallel;
Flow *currentPlanFlow; /* what is the flow of the current plan node */
} PlanProfile;
/*
* Forward Declarations
*/
static void prescan(Plan *plan, PlanProfile * context);
static bool prescan_walker(Node *node, PlanProfile * context);
static bool
adjustPlanFlow(Plan *plan,
bool stable,
bool rescannable,
Movement req_move,
List *hashExpr);
static void motion_sanity_check(PlannerInfo *root, Plan *plan);
static bool loci_compatible(List *hashExpr1, List *hashExpr2);
static Plan *materialize_subplan(PlannerInfo *root, Plan *subplan);
/* ------------------------------------------------------------------------- *
* Function cdbparallelize() is the main entry point.
*
* Parallelize a single raw PostgreSQL sequential plan. The input plan
* is modified in that Flow nodes are attached to Plan nodes in the input
* as appropriate and the top-level plan node's dispatch field is set
* to specify how the plan should be handled by the executor.
*
* If the dispatch field of the top-level plan is set to a non-default
* value on entry, the plan is returned unmodified. This guarantees that
* we won't parallelize the same input plan more than once, provides a way
* for the planner to create a plan that this function will recognize as
* already final.
*
* The result is [a copy of] the input plan with alterations to make the
* plan suitable for execution by Greenplum Database.
*
*
* TODO: Much investigation remains in the areas of
*
* - initialization plans,
* - plan parameters,
* - subqueries,
* - ordering and hash attributes,
* - etc.
*
* The current implementation is little more than a rough sketch.
*
*
* Outline of processing:
*
* - Return the input plan unmodified, if it's dispatch field is set.
*
* - Scan (scanForManagedTables) the input plan looking for references to
* managed tables. If none are found, mark the plan for sequential
* dispatch and return it.
*
* - Augment the input plan (prescan) by attaching Flow nodes to each of
* its Plan nodes. This is where all the decisions about parallelizing
* the query occur.
*
* - Implement the decisions recorded in the Flow nodes (apply_motion)
* to produce a modified copy of the plan. Mark the copy for parallel
* dispatch and return it.
* ------------------------------------------------------------------------- *
*/
Plan *
cdbparallelize(PlannerInfo *root,
Plan *plan,
Query *query,
int cursorOptions __attribute__((unused)) ,
ParamListInfo boundParams __attribute__((unused))
)
{
int nParamExec;
PlanProfile profile;
PlanProfile *context = &profile;
/* Make sure we're called correctly (and need to be called). */
switch ( Gp_role )
{
case GP_ROLE_DISPATCH:
break;
case GP_ROLE_UTILITY:
return plan;
case GP_ROLE_EXECUTE:
case GP_ROLE_UNDEFINED:
Insist(0);
}
Assert(is_plan_node((Node *) plan));
Assert(query != NULL && IsA(query, Query));
/* Print plan if debugging. */
if (Debug_print_prelim_plan)
elog_node_display(DEBUG1, "preliminary plan", plan, Debug_pretty_print);
/* Don't parallelize plans rendered impotent be constraint exclusion.
* See MPP-2168. We could fold this into prescan() but this quick
* check is effective in the only case we know of.
*/
if ( IsA(plan, Result) && plan->lefttree == NULL
&& plan->targetlist == NULL
&& plan->qual == NULL
&& plan->initPlan == NULL )
{
Assert(plan->dispatch != DISPATCH_PARALLEL);
return plan;
}
/* Initialize the PlanProfile result ... */
planner_init_plan_tree_base(&context->base, root);
context->root = root;
context->resultSegments = false;
switch (query->commandType)
{
case CMD_SELECT:
if (query->intoClause)
{
/* SELECT INTO always created partitioned tables. */
context->resultSegments = true;
}
break;
case CMD_INSERT:
case CMD_UPDATE:
case CMD_DELETE:
{
/* Since this is a data modification command, we need to
* include information about the result relation in the
* summary of relations touched. */
Oid reloid;
Relation relation;
GpPolicy *policy = NULL;
/* Get Oid of target relation. */
Insist(query->resultRelation > 0 &&
query->resultRelation <= list_length(root->glob->finalrtable));
reloid = getrelid(query->resultRelation, root->glob->finalrtable);
/* Get a copy of the rel's GpPolicy from the relcache. */
relation = relation_open(reloid, NoLock);
policy = RelationGetPartitioningKey(relation);
relation_close(relation, NoLock);
/* Tell caller if target rel is distributed. */
if (policy &&
policy->ptype == POLICYTYPE_PARTITIONED)
context->resultSegments = true;
if (policy)
pfree(policy);
/* RETURNING is not yet implemented for partitioned tables. */
if (query->returningList &&
context->resultSegments)
{
const char *cmd = (query->commandType == CMD_INSERT) ? "INSERT"
: (query->commandType == CMD_UPDATE) ? "UPDATE"
: "DELETE";
ereport(ERROR, (errcode(ERRCODE_CDB_FEATURE_NOT_SUPPORTED),
errmsg("The RETURNING clause of the %s "
"statement is not yet supported in "
"this version of " PACKAGE_NAME ".",
cmd)
));
}
}
break;
default:
Insist(0);
}
/* We need to keep track of whether any part of the plan needs to be
* dispatched in parallel. */
context->dispatchParallel = context->resultSegments;
/* Save the global count of PARAM_EXEC from the top plan node. */
nParamExec = plan->nParamExec;
context->currentPlanFlow = NULL;
/*
* Prescan the plan and attach Flow nodes to its Plan nodes. These nodes
* describe how to parallelize the plan. We also accumulate summary
* information in the profile stucture.
*/
prescan(plan, context);
if ( context->dispatchParallel )
{
/* From this point on, since part of plan is parallel, we have to
* regard the whole plan as parallel. */
plan->dispatch = DISPATCH_PARALLEL;
/*
* Implement the parallelizing directions in the Flow nodes attached
* to the root plan node of each root slice of the plan.
*/
Assert(root->parse == query);
plan = apply_motion(root, plan, query);
}
else
{
//default to sequential
plan->dispatch = DISPATCH_SEQUENTIAL;
}
/* Restore the global count of PARAM_EXEC from the top plan node. */
plan->nParamExec = nParamExec;
if (gp_enable_motion_deadlock_sanity)
motion_sanity_check(root, plan);
return plan;
}
/* ----------------------------------------------------------------------- *
* Functions prescan() and prescan_walker() use the plan_tree_walker()
* framework to visit the nodes of the plan tree in order to check for
* unsupported usages and collect information for use in finalizing the
* plan. The walker exits with an error only due an unexpected problems.
*
* The plan and query arguments should not be null.
* ----------------------------------------------------------------------- *
*/
void
prescan(Plan *plan, PlanProfile * context)
{
if ( prescan_walker((Node *) plan, context) )
{
Insist(0);
}
}
/**
* Context information to be able to parallelize a subplan
*/
typedef struct ParallelizeCorrelatedPlanWalkerContext
{
plan_tree_base_prefix base; /* Required prefix for plan_tree_walker/mutator */
SubPlan *sp; /* subplan node */
Movement movement; /* What is the final movement necessary? Is it gather or broadcast */
List *rtable; /* rtable from the global context */
bool subPlanDistributed; /* is original subplan distributed */
} ParallelizeCorrelatedPlanWalkerContext;
/**
* Context information to map vars occuring in the Result node's targetlist
* and quals to what is produced by the underlying SeqScan node.
*/
typedef struct MapVarsMutatorContext
{
plan_tree_base_prefix base; /* Required prefix for plan_tree_walker/mutator */
List *outerTL; /* Target list from underlying scan node */
} MapVarsMutatorContext;
/**
* Forward declarations of static functions
*/
static bool ContainsParamWalker(Node *expr, void *ctx);
static void ParallelizeSubplan(SubPlan *spExpr, PlanProfile *context);
static Plan* ParallelizeCorrelatedSubPlan(PlannerInfo *root, SubPlan *spExpr, Plan *plan, Movement m, bool subPlanDistributed);
static Node* MapVarsMutator(Node *expr, MapVarsMutatorContext *ctx);
static Node* ParallelizeCorrelatedSubPlanMutator(Node *node, ParallelizeCorrelatedPlanWalkerContext *ctx);
static Node *ParallelizeCorrelatedSubPlanUpdateFlowMutator(Node *node);
/**
* Does an expression contain a parameter?
*/
static bool ContainsParamWalker(Node *expr, void *ctx)
{
Assert(ctx == NULL);
if (expr == NULL)
{
return false;
}
else if (IsA(expr, Param) || IsA(expr, SubPlan))
{
return true;
}
return expression_tree_walker(expr, ContainsParamWalker, ctx);
}
/**
* Replaces all vars in an expression with OUTER vars referring to the
* targetlist contained in the context (ctx->outerTL).
*/
static Node* MapVarsMutator(Node *expr, MapVarsMutatorContext *ctx)
{
Assert(ctx);
if (expr == NULL)
return NULL;
if (IsA(expr, Var))
{
Assert(ctx->outerTL);
TargetEntry *tle = tlist_member(expr, ctx->outerTL);
Assert(tle && "unable to find var in outer TL");
Var *vOrig = (Var *) expr;
Var *vOuter = (Var *) copyObject(vOrig);
vOuter->varno = OUTER;
vOuter->varattno = tle->resno;
vOuter->varnoold = vOrig->varno;
return (Node *) vOuter;
}
return expression_tree_mutator(expr, MapVarsMutator, ctx);
}
/**
* Add a materialize node to prevent rescan of subplan.
*/
Plan *materialize_subplan(PlannerInfo *root, Plan *subplan)
{
Plan *mat = materialize_finished_plan(root, subplan);
((Material *)mat)->cdb_strict = true;
if (mat->flow)
{
mat->flow->req_move = MOVEMENT_NONE;
mat->flow->flow_before_req_move = NULL;
}
return mat;
}
/* *
* Not listing every node type here, if further bug found due to not updating flow
* after cdbparallelize, just simply add that node type here.
* */
static Node *ParallelizeCorrelatedSubPlanUpdateFlowMutator(Node *node)
{
Assert(is_plan_node(node));
switch (nodeTag(node))
{
case T_Agg:
case T_Window:
case T_Sort:
case T_Material:
case T_Limit:
case T_Result:
{
if(((Plan *)node)->lefttree && ((Plan *)node)->lefttree->flow)
((Plan *)node)->flow = pull_up_Flow((Plan *)node, ((Plan *)node)->lefttree, true);
break;
}
case T_Append:
{
List *append_list = (List *) ((Append *)node)->appendplans;
if(append_list == NULL)
break;
Plan *first_append = (Plan *) (lfirst(list_head(append_list)));
Assert(first_append && first_append->flow);
bool with_sort = list_length(append_list) == 1 ? true : false;
((Plan *)node)->flow = pull_up_Flow((Plan *)node, first_append, with_sort);
break;
}
default:
break;
}
return node;
}
/**
* This is the workhorse method that transforms a plan containing correlated references
* to one that is executable as part of a parallel plan.
* 1. This method recurses down the subplan till it finds a leaf scan node
* (i.e. a seqscan, ao or co scan).
* 2. This scan node is replaced by a tree that looks like:
* Result (with quals)
* \
* \_Material
* \
* \_Broadcast (or Gather)
* \
* \_SeqScan (no quals)
* This transformed plan can be executed in a parallel setting since the correlation
* is now part of the result node which executes in the same slice as the outer plan node.
*/
static Node* ParallelizeCorrelatedSubPlanMutator(Node *node, ParallelizeCorrelatedPlanWalkerContext *ctx)
{
if (node == NULL)
return NULL;
if (IsA(node, FunctionScan))
{
RangeTblEntry *rte = rt_fetch(((Scan *)node)->scanrelid,
ctx->rtable);
Assert(rte->rtekind == RTE_FUNCTION);
if (rte->funcexpr &&
ContainsParamWalker(rte->funcexpr, NULL /*ctx */) && ctx->subPlanDistributed)
{
ereport(ERROR, (errcode(ERRCODE_CDB_FEATURE_NOT_YET),
errmsg("Cannot parallelize that query yet."),
errdetail("In a subquery FROM clause, a "
"function invocation cannot contain "
"a correlated reference."),
errOmitLocation(true)
));
}
}
if (IsA(node, SeqScan)
|| IsA(node, AppendOnlyScan)
|| IsA(node, ParquetScan)
|| IsA(node, ShareInputScan))
{
Plan *scanPlan = (Plan *) node;
/**
* If original subplan has no motion, we double check whether the scan
* node is on catalog table or not. If catalog, no need to apply
* parallelization.
* This is for case like:
* SELECT array(select case when p.oid in (select
* unnest(array[typoutput, typsend]) from pg_type) then 'upg_catalog.'
* else 'pg_catalog.' end) FROM pg_proc p;
**/
if(scanPlan->flow && (scanPlan->flow->locustype == CdbLocusType_Entry))
return (Node *)node;
/**
* Steps:
* 1 - get targetlist from seqscan
* 2 - pull up 1 and set it as TL of result node
* 3 - get quals from seqscan
* 4 - extract out all vars from (3) and add it to TL
* 5 - transform (3) using TL and set it as qual of result node (use OUTER for varno)
*/
/**
* Step 1: Save targetlist, quals and paramset from the scan node
*/
List *saveTL = copyObject(scanPlan->targetlist);
Bitmapset *saveAllParam = bms_copy(scanPlan->allParam);
Bitmapset *saveExtParam = bms_copy(scanPlan->extParam);
/**
* Step 2: Next, iterate over quals and find out if they need to be part of the scan or the result node.
*/
ListCell *lc = NULL;
List *resQual = NIL;
List *scanQual = NIL;
foreach (lc, scanPlan->qual)
{
Node *qual = (Node *) lfirst(lc);
if (ContainsParamWalker(qual, NULL /*ctx*/))
{
resQual = lappend(resQual, qual);
}
else
{
scanQual = lappend(scanQual, qual);
}
}
scanPlan->qual = scanQual;
scanPlan->allParam = NULL;
scanPlan->extParam = NULL;
/**
* Step 3: Find all the vars that are needed from the scan node
* for upstream processing.
*/
List *scanVars = pull_var_clause((Node *) scanPlan->targetlist, false);
scanVars = list_concat(scanVars, pull_var_clause((Node *) resQual, false));
/*
* Step 4: Construct the new targetlist for the scan node
*/
{
ListCell *lc = NULL;
int resno = 1;
List *scanTL = NIL;
foreach(lc, scanVars)
{
Var *var = (Var *) copyObject(lfirst(lc));
scanTL = lappend(scanTL, makeTargetEntry((Expr *) var,
resno,
NULL,
false));
resno++;
}
scanPlan->targetlist = scanTL;
}
/**
* There should be no subplans in the scan node anymore.
*/
Assert(!contain_subplans((Node *) scanPlan->targetlist));
Assert(!contain_subplans((Node *) scanPlan->qual));
/**
* Step 5: Construct result node's targetlist and quals
*/
MapVarsMutatorContext ctx1;
ctx1.base.node = ctx->base.node;
ctx1.outerTL = scanPlan->targetlist;
resQual = (List *) MapVarsMutator((Node *) resQual, &ctx1);
List *resTL = (List *) MapVarsMutator((Node *) saveTL, &ctx1);
/**
* Step 6: Ensure that apply_motion adds a broadcast or a gather motion
* depending on the desired movement.
*/
if (ctx->movement == MOVEMENT_BROADCAST)
{
broadcastPlan(scanPlan, false /* stable */, false /* rescannable */);
}
else
{
focusPlan(scanPlan, false /* stable */, false /* rescannable */);
}
/**
* Step 7: Add a material node
*/
Plan *mat = materialize_subplan((PlannerInfo *) ctx->base.node, scanPlan);
/**
* Step 8: Fix up the result node on top of the material node
*/
Result *res = make_result(resTL, NULL, mat);
res->plan.qual = resQual;
((Plan *) res)->allParam = saveAllParam;
((Plan *) res)->extParam = saveExtParam;
Assert(((Plan *) res)->lefttree && ((Plan *) res)->lefttree->flow);
((Plan *) res)->flow = pull_up_Flow((Plan *) res, ((Plan *)res)->lefttree, true);
/**
* It is possible that there is an additional level of correlation in the result node.
* we will need to recurse in these structures again.
*/
if (contain_subplans( (Node *) res->plan.targetlist))
{
res->plan.targetlist = (List *) ParallelizeCorrelatedSubPlanMutator( (Node *) res->plan.targetlist, ctx);
}
if (contain_subplans( (Node *) res->plan.qual))
{
res->plan.qual = (List *) ParallelizeCorrelatedSubPlanMutator( (Node *) res->plan.qual, ctx);
}
return (Node *) res;
}
if (IsA(node, SubPlan))
{
SubPlan *sp = (SubPlan *) node;
if (sp->is_initplan)
{
return node;
}
/**
* Pull up the parallelization of subplan to here, because we want to
* set the is_parallelized flag. We don't want to apply double
* parallelization for nested subplan.
* This is for case like:
* select A.i, A.j, (select sum(C.j) from C where C.j = A.j and C.i =
* (select A.i from A where A.i = C.i)) from A;
**/
SubPlan* new_sp = (SubPlan *)plan_tree_mutator(node, ParallelizeCorrelatedSubPlanMutator, ctx);
Assert(new_sp);
new_sp->is_parallelized = true;
return (Node *)new_sp;
}
/**
* Remove any existing motion nodes in the plan. For an informal proof as to why
* this is correct, please refer to the correlated subquery design doc.
*/
if (IsA(node, Motion))
{
Plan *plan = (Plan *) node;
node = (Node *) plan->lefttree;
Assert(node);
return ParallelizeCorrelatedSubPlanMutator(node, ctx);
}
Node * result = plan_tree_mutator(node, ParallelizeCorrelatedSubPlanMutator, ctx);
/* *
* Update the flow of the current plan node.
* */
if(is_plan_node(node))
return ParallelizeCorrelatedSubPlanUpdateFlowMutator(result);
return result;
}
/**
* Parallelizes a correlated subplan. See ParallelizeCorrelatedSubPlanMutator for details.
*/
Plan* ParallelizeCorrelatedSubPlan(PlannerInfo *root, SubPlan *spExpr, Plan *plan, Movement m, bool subPlanDistributed)
{
ParallelizeCorrelatedPlanWalkerContext ctx;
ctx.base.node = (Node *) root;
ctx.movement = m;
ctx.subPlanDistributed = subPlanDistributed;
ctx.sp = spExpr;
ctx.rtable = root->glob->finalrtable;
return (Plan *) ParallelizeCorrelatedSubPlanMutator((Node *) plan, &ctx);
}
/**
* This function takes a specific SubPlan and transforms its underlying plan
* it so that it may be executed as part of a distributed plan.
* - If the subplan corresponds to an initplan, then requests the results
* to be focused.
* - If the subplan is uncorrelated, multi-row subquery, then it either focuses or broadcasts
* the subplan based on the flow of the containing plan node.
* - If the subplan is correlated, then it transforms the correlated into a form that is
* executable
*/
void ParallelizeSubplan(SubPlan *spExpr, PlanProfile *context)
{
Assert(!spExpr->is_parallelized);
/**
* If a subplan is multi-row, it cannot be an initplan right now.
*/
AssertImply(spExpr->is_multirow, !spExpr->is_initplan);
/**
* If it is an initplan, there can be no parameters from outside
*/
AssertImply(spExpr->is_initplan, spExpr->parParam == NIL);
Plan *origPlan = planner_subplan_get_plan(context->root, spExpr);
bool containingPlanDistributed = (context->currentPlanFlow && context->currentPlanFlow->flotype == FLOW_PARTITIONED);
bool subPlanDistributed = (origPlan->flow && origPlan->flow->flotype == FLOW_PARTITIONED);
bool hasParParam = (list_length(spExpr->parParam) > 0);
/**
* If containing plan is distributed then we must know the flow of the subplan.
*/
AssertImply(containingPlanDistributed, (origPlan->flow && origPlan->flow->flotype != FLOW_UNDEFINED));
Plan *newPlan = copyObject(origPlan);
if (spExpr->is_initplan)
{
/*
* Corresponds to an initplan whose results need to end up at the QD.
* Request focus flow.
* Note ARRAY() subquery needs its order preserved, per spec. Otherwise,
* initplan doesn't care the query result order even if it is specified.
*/
focusPlan(newPlan, spExpr->subLinkType == ARRAY_SUBLINK, false);
}
else if (spExpr->is_multirow
&& spExpr->parParam == NIL)
{
/**
* Corresponds to a multi-row uncorrelated subplan. The results need
* to be either broadcasted or focused depending on the flow of the containing
* plan node.
*/
if (containingPlanDistributed)
{
broadcastPlan(newPlan, false /* stable */, false /* rescannable */);
}
else
{
focusPlan(newPlan, false /* stable */, false /* rescannable */);
}
newPlan = materialize_subplan(context->root, newPlan);
}
/* *
* [JIRA: MPP-24563] Adding hasParParam check here, for the kind of cases in
* JIRA, which has both focused parent plan and subplan.
* */
else if(containingPlanDistributed || subPlanDistributed || hasParParam)
{
Movement reqMove = containingPlanDistributed ? MOVEMENT_BROADCAST : MOVEMENT_FOCUS;
/**
* Subplan corresponds to a correlated subquery and its parallelization
* requires certain transformations.
*/
newPlan = ParallelizeCorrelatedSubPlan(context->root, spExpr, newPlan, reqMove, subPlanDistributed);
}
Assert(newPlan);
/**
* Replace subplan in global subplan list.
*/
list_nth_replace(context->root->glob->subplans, spExpr->plan_id - 1, newPlan);
spExpr->is_parallelized = true;
}
/*
* Function prescan_walker is the workhorse of prescan.
*
* The driving function, prescan(), should be called only once on a
* plan produced by the Greenplum Database optimizer for dispatch on the QD. There
* are two main task performed:
*
* 1. Since the optimizer isn't in a position to view plan globally,
* it may generate plans that we can't execute. This function is
* responsible for detecting potential problem usages:
*
* Example: Non-initplan subplans are legal in sequential contexts
* but not in parallel contexs.
*
* 2. The function specifies (by marking Flow nodes) what (if any)
* motions need to be applied to initplans prior to dispatch.
* As in the case of the main plan, the actual Motion node is
* attached later.
*/
bool
prescan_walker(Node *node, PlanProfile * context)
{
if (node == NULL)
return false;
Flow *savedPlanFlow = context->currentPlanFlow;
if (is_plan_node(node))
{
Plan *plan = (Plan *)node;
if (plan->dispatch == DISPATCH_PARALLEL)
context->dispatchParallel = true;
if (plan->flow &&
(plan->flow->flotype == FLOW_PARTITIONED
|| plan->flow->flotype == FLOW_REPLICATED))
context->dispatchParallel = true;
context->currentPlanFlow = plan->flow;
if (context->currentPlanFlow
&& context->currentPlanFlow->flow_before_req_move)
{
context->currentPlanFlow = context->currentPlanFlow->flow_before_req_move;
}
}
switch (nodeTag(node))
{
case T_SubPlan:
/*
* A SubPlan node is not a Plan node, but an Expr node that
* contains a Plan and its range table. It corresponds to a
* SubLink in the query tree (a SQL subquery expression) and
* may occur in an expression or an initplan list.
*/
{
/*
* SubPlans establish a local state for the contained plan,
* notably the range table.
*/
SubPlan *subplan = (SubPlan *) node;
if (!subplan->is_parallelized)
{
ParallelizeSubplan(subplan, context);
}
break;
}
case T_Motion:
{
context->dispatchParallel = true;
break;
}
default:
break;
}
bool result = plan_tree_walker(node, prescan_walker, context);
/**
* Replace saved flow
*/
context->currentPlanFlow = savedPlanFlow;
return result;
}
/*
* Construct a new Flow in the current memory context.
*/
Flow *
makeFlow(FlowType flotype)
{
Flow *flow = makeNode(Flow);
flow->flotype = flotype;
flow->req_move = MOVEMENT_NONE;
flow->locustype = CdbLocusType_Null;
return flow;
}
/*
* Create a flow for the given Plan node based on the flow in its
* lefttree (outer) plan. Partitioning information is preserved.
* Sort specifications are preserved only if withSort is true.
*
* NB: At one time this function was called during cdbparallelize(), after
* transformation of the plan by set_plan_references(). Later, calls were
* added upstream of set_plan_references(); only these remain at present.
*
* Don't call on a SubqueryScan plan. If you are tempted, you probably
* want to use mark_passthru_locus (after make_subqueryscan) instead.
*/
Flow *
pull_up_Flow(Plan *plan, Plan *subplan, bool withSort)
{
Flow *model_flow = NULL;
Flow *new_flow = NULL;
Insist(subplan);
model_flow = subplan->flow;
Insist(model_flow);
/* SubqueryScan always has manifest Flow, so we shouldn't see one here. */
Assert( !IsA(plan, SubqueryScan) );
if (IsA(plan, MergeJoin) || IsA(plan, NestLoop) || IsA(plan, HashJoin))
Assert(subplan == plan->lefttree || subplan == plan->righttree);
else if ( IsA(plan, Append) )
Assert(list_member(((Append*)plan)->appendplans, subplan));
else
Assert(subplan == plan->lefttree);
new_flow = makeFlow(model_flow->flotype);
if (model_flow->flotype == FLOW_SINGLETON)
new_flow->segindex = model_flow->segindex;
/* Pull up hash key exprs, if they're all present in the plan's result. */
else if (model_flow->flotype == FLOW_PARTITIONED &&
model_flow->hashExpr)
{
if (!is_projection_capable_plan(plan) ||
cdbpullup_isExprCoveredByTargetlist((Expr *)model_flow->hashExpr,
plan->targetlist))
new_flow->hashExpr = copyObject(model_flow->hashExpr);
}
/* Pull up sort key info if requested. */
if (withSort)
{
if (IsA(plan, Sort))
{
Sort *sort = (Sort *)plan;
new_flow->numSortCols = sort->numCols;
ARRAYCOPY(new_flow->sortColIdx, sort->sortColIdx, sort->numCols);
ARRAYCOPY(new_flow->sortOperators, sort->sortOperators, sort->numCols);
}
else if (model_flow->numSortCols == 0)
new_flow->numSortCols = 0;
else if (is_projection_capable_plan(plan))
{
/*
* Try to derive a sortColIdx to align with projected targetlist.
* It might be shorter than the subplan's sortColIdx, maybe even
* empty, if not all of the subplan's sort cols are projected.
*/
new_flow->numSortCols = cdbpullup_colIdx(plan,
subplan,
model_flow->numSortCols,
model_flow->sortColIdx,
&new_flow->sortColIdx);
if (new_flow->numSortCols > 0)
ARRAYCOPY(new_flow->sortOperators,
model_flow->sortOperators,
new_flow->numSortCols);
}
else
{
/* Non-projecting so sort attributes are the same. */
new_flow->numSortCols = model_flow->numSortCols;
ARRAYCOPY(new_flow->sortColIdx,
model_flow->sortColIdx,
new_flow->numSortCols);
ARRAYCOPY(new_flow->sortOperators,
model_flow->sortOperators,
new_flow->numSortCols);
}
} /* withSort */
new_flow->locustype = model_flow->locustype;
return new_flow;
}
/*
* Is the node a "subclass" of Plan?
*/
bool
is_plan_node(Node *node)
{
if (node == NULL)
return false;
if(nodeTag(node) >= T_Plan_Start && nodeTag(node) < T_Plan_End)
return true;
return false;
}
/* Functions focusPlan, broadcastPlan, and repartitionPlan annotate the top
* of the given plan tree with a motion request for later implementation by
* function apply_motion in cdbmutate.c.
*
* plan the plan to annotate
*
* stable if true, the result plan must present the result in the
* order specified in the flow (which, presumably, matches
* the input order.
*
* rescannable if true, the resulting plan must be rescannable, i.e.,
* it must be possible to use it like a scan. (Note that
* currently, no Motion nodes are rescannable.
*
* Remaining arguments, if any, determined by the requirements of the specific
* function.
*
* The result is true if the requested annotation could be made. Else, false.
*/
/*
* Function: focusPlan
*/
bool
focusPlan(Plan *plan, bool stable, bool rescannable)
{
Assert(plan->flow && plan->flow->flotype != FLOW_UNDEFINED);
/* Already focused? Do nothing. */
if (plan->flow->flotype == FLOW_SINGLETON)
return true;
/* TODO How specify deep-six? */
if (plan->flow->flotype == FLOW_REPLICATED)
return false;
return adjustPlanFlow(plan, stable, rescannable, MOVEMENT_FOCUS, NIL);
}
/*
* Function: broadcastPlan
*/
bool
broadcastPlan(Plan *plan, bool stable, bool rescannable)
{
Assert(plan->flow && plan->flow->flotype != FLOW_UNDEFINED);
return adjustPlanFlow(plan, stable, rescannable, MOVEMENT_BROADCAST, NIL);
}
/**
* This method is used to determine if motion nodes may be avoided for certain insert-select
* statements. To do this it determines if the loci are compatible (ignoring relabeling).
*/
static bool loci_compatible(List *hashExpr1, List *hashExpr2)
{
ListCell *cell1 = NULL;
ListCell *cell2 = NULL;
if (list_length(hashExpr1) != list_length(hashExpr2))
{
return false;
}
forboth (cell1, hashExpr1, cell2, hashExpr2) {
Expr *var1;
Expr *var2;
var1 = (Expr *) lfirst(cell1);
var2 = (Expr *) lfirst(cell2);
/* right side variable may be encapsulated by a relabel node. motion, however, does not care about relabel nodes. */
if (IsA(var2, RelabelType))
var2 = ((RelabelType *)var2)->arg;
if (!equal(var1, var2))
{
return false;
}
}
return true;
}
/*
* Function: repartitionPlan
*/
bool
repartitionPlan(Plan *plan, bool stable, bool rescannable, List *hashExpr)
{
Assert(plan->flow);
Assert(plan->flow->flotype == FLOW_PARTITIONED ||
plan->flow->flotype == FLOW_SINGLETON);
/* Already partitioned on the given hashExpr? Do nothing. */
if (hashExpr)
{
if (equal(hashExpr, plan->flow->hashExpr))
return true;
/* MPP-4922: The earlier check is very conservative and may result in unnecessary motion nodes.
* Adding another check to avoid motion nodes, if possible.
*/
if (loci_compatible(hashExpr, plan->flow->hashExpr))
return true;
}
return adjustPlanFlow(plan, stable, rescannable, MOVEMENT_REPARTITION, hashExpr);
}
/*
* Helper Function: adjustPlanFlow
*
* Recursively pushes a motion request down the plan tree which allows us
* to remove constraints in some cases. For example, pushing a motion below
* a Sort removes the stability constraint.
*
* Returns true if successful. Returns false and doesn't change anything
* if the request cannot be honored.
*/
bool
adjustPlanFlow(Plan *plan,
bool stable,
bool rescannable,
Movement req_move,
List *hashExpr)
{
Flow *flow = plan->flow;
bool disorder = false;
bool recur = false;
bool reorder = false;
Assert(flow && flow->flotype != FLOW_UNDEFINED);
Assert(flow->req_move == MOVEMENT_NONE);
Assert(flow->flow_before_req_move == NULL);
switch (req_move)
{
case MOVEMENT_FOCUS:
/* Don't request Merge Receive unless ordering is significant. */
if (!stable)
disorder = true;
break;
case MOVEMENT_BROADCAST:
case MOVEMENT_REPARTITION:
if (flow->flotype == FLOW_PARTITIONED)
disorder = true;
break;
default:
break;
}
/*
* Decide whether to push the request further down the plan tree and
* possibly remove restrictions.
*/
switch (plan->type)
{
case T_Sort:
if (!stable)
{
/*
* Q: If order doesn't matter, why is there a Sort here?
* A: Could be INSERT...SELECT...ORDER BY; each QE in the
* inserting gang will sort its own partition locally. Weird,
* but could be used for clustering. But note it in the debug
* log; some caller might specify stable = false by mistake.
*/
ereport(DEBUG4, (errmsg("adjustPlanFlow stable=false applied "
"to Sort operator; req_move=%d",
req_move) ));
}
if (req_move == MOVEMENT_FOCUS)
{
/*
* We'd rather do a merge of a parallel sort, than do a
* sequential sort. A fixed motion can do that as long as we
* don't need to rescan.
*/
break;
}
else
{
/* No need to preserve order or restartability below a sort. */
recur = true;
reorder = true;
rescannable = false;
}
break;
case T_Material:
/* Always push motion below Material. */
/* No need to preserve restartability below a materialization. */
recur = true;
rescannable = false;
break;
case T_Append: /* Maybe handle specially some day. */
default:
break;
}
/*
* Push motion down to a descendant of this node (= upstream).
*/
if (recur)
{
Assert(plan->lefttree &&
plan->lefttree->flow &&
plan->lefttree->flow->flotype != FLOW_UNDEFINED);
/* Descend to apply the requested Motion somewhere below this node. */
if (!adjustPlanFlow(plan->lefttree,
stable && !reorder,
rescannable,
req_move,
hashExpr))
return false;
/* After updating subplan, bubble new distribution back up the tree. */
Flow *kidflow = plan->lefttree->flow;
flow->flotype = kidflow->flotype;
flow->segindex = kidflow->segindex;
flow->hashExpr = copyObject(kidflow->hashExpr);
plan->dispatch = plan->lefttree->dispatch;
/* Zap sort cols if motion has destroyed the ordering. */
if (disorder && !reorder)
{
flow->numSortCols = 0;
flow->sortColIdx = NULL;
flow->sortOperators = NULL;
}
return true; /* success */
}
/*
* The buck stops here. We have arrived at the node whose output is
* to be moved. Return false if can't do the motion as requested.
*/
/* Would motion mess up a required ordering? */
if (stable && disorder)
return false;
/* Adding a Motion node would make the result non-rescannable. */
if (rescannable)
return false;
/*
* Make our changes to a shallow copy of the original Flow node.
* Later, after applying the requested motion, apply_motion_mutator()
* will restore the original Flow ptr and discard the modified copy.
*
* Note that the original Flow node describes the present Plan node's
* own result distribution and ordering: i.e. the input to the future
* Motion. The temporary modified Flow describes the result of the
* requested Motion: the input to the next operator downstream.
*/
{
Assert(flow->flow_before_req_move == NULL);
plan->flow = (Flow *)palloc(sizeof(*plan->flow));
*plan->flow = *flow;
plan->flow->flow_before_req_move = flow;
flow = plan->flow;
}
/*
* Set flag to tell apply_motion() to insert a Motion node
* immediately above this node (= downstream). Overwrite the
* present Plan node's Flow node to describe the distribution
* and ordering that the future Motion node will produce.
*/
flow->req_move = req_move;
switch (req_move)
{
case MOVEMENT_FOCUS:
/* Converge to a single QE (or QD; that choice is made later). */
flow->flotype = FLOW_SINGLETON;
flow->hashExpr = NIL;
flow->segindex = 0;
break;
case MOVEMENT_BROADCAST:
flow->flotype = FLOW_REPLICATED;
flow->hashExpr = NIL;
flow->segindex = 0;
break;
case MOVEMENT_REPARTITION:
flow->flotype = FLOW_PARTITIONED;
flow->hashExpr = copyObject(hashExpr);
flow->segindex = 0;
break;
default:
Assert(0);
}
/* Discard ordering info if the motion will disturb the order. */
if (disorder)
{
flow->numSortCols = 0;
flow->sortColIdx = NULL;
flow->sortOperators = NULL;
}
/* Since we added Motion, dispatch must be parallel. */
plan->dispatch = DISPATCH_PARALLEL;
return true; /* success */
} /* adjustPlanFlow */
#define SANITY_MOTION 0x1
#define SANITY_DEADLOCK 0x2
typedef struct sanity_result_t
{
plan_tree_base_prefix base; /* Required prefix for plan_tree_walker/mutator */
int flags;
} sanity_result_t;
static bool
motion_sanity_walker(Node *node, sanity_result_t *result)
{
sanity_result_t left_result;
sanity_result_t right_result;
bool deadlock_safe = false;
char *branch_label;
left_result.base = result->base;
left_result.flags = 0;
right_result.base = result->base;
right_result.flags = 0;
if (node == NULL)
return false;
if (!is_plan_node(node))
return false;
/* special handling for branch points */
switch (nodeTag(node))
{
case T_HashJoin: /* Hash join can't deadlock -- it fully
* materializes its inner before switching to
* its outer. */
branch_label = "HJ";
if (((HashJoin *)node)->join.prefetch_inner)
deadlock_safe = true;
break;
case T_NestLoop: /* Nested loop joins are safe only if the
* prefetch flag is set */
branch_label = "NL";
if (((NestLoop *)node)->join.prefetch_inner)
deadlock_safe = true;
break;
case T_MergeJoin:
branch_label = "MJ";
if (((MergeJoin *)node)->join.prefetch_inner)
deadlock_safe = true;
break;
default:
branch_label = NULL;
break;
}
/* now scan the subplans */
switch (nodeTag(node))
{
case T_Result:
case T_Window:
case T_TableFunctionScan:
case T_ShareInputScan:
case T_Append:
case T_SeqScan:
case T_ExternalScan:
case T_AppendOnlyScan:
case T_ParquetScan:
case T_IndexScan:
case T_BitmapIndexScan:
case T_BitmapHeapScan:
case T_BitmapTableScan:
case T_TidScan:
case T_SubqueryScan:
case T_FunctionScan:
case T_ValuesScan:
case T_Agg:
case T_Unique:
case T_Hash:
case T_SetOp:
case T_Limit:
case T_Sort:
case T_Material:
if (plan_tree_walker(node, motion_sanity_walker, result))
return true;
break;
case T_Motion:
if (plan_tree_walker(node, motion_sanity_walker, result))
return true;
result->flags |= SANITY_MOTION;
elog(DEBUG5, " found motion");
break;
case T_HashJoin:
case T_NestLoop:
case T_MergeJoin:
{
Plan *plan = (Plan *)node;
elog(DEBUG5, " %s going left", branch_label);
if (motion_sanity_walker((Node *)plan->lefttree, &left_result))
return true;
elog(DEBUG5, " %s going right", branch_label);
if (motion_sanity_walker((Node *)plan->righttree, &right_result))
return true;
elog(DEBUG5, " %s branch point left 0x%x right 0x%x",
branch_label, left_result.flags, right_result.flags);
/* deadlocks get sent up immediately */
if ((left_result.flags & SANITY_DEADLOCK) ||
(right_result.flags & SANITY_DEADLOCK))
{
result->flags |= SANITY_DEADLOCK;
break;
}
/* if this node is "deadlock safe" then even if we have
* motion on both sides we will not deadlock (because the
* access pattern is deadlock safe: all rows are retrieved
* from one side before the first row from the other). */
if (!deadlock_safe && ((left_result.flags & SANITY_MOTION) &&
(right_result.flags & SANITY_MOTION)))
{
elog(LOG, "FOUND MOTION DEADLOCK in %s", branch_label);
result->flags |= SANITY_DEADLOCK;
break;
}
result->flags |= left_result.flags | right_result.flags;
elog(DEBUG5, " %s branch point left 0x%x right 0x%x res 0x%x%s",
branch_label, left_result.flags, right_result.flags, result->flags, deadlock_safe ? " deadlock safe: prefetching" : "");
}
break;
default:
break;
}
return false;
}
static void
motion_sanity_check(PlannerInfo *root, Plan *plan)
{
sanity_result_t sanity_result;
planner_init_plan_tree_base(&sanity_result.base, root);
sanity_result.flags = 0;
elog(DEBUG5, "Motion Deadlock Sanity Check");
if (motion_sanity_walker((Node *)plan, &sanity_result))
{
Insist(0);
}
if (sanity_result.flags & SANITY_DEADLOCK)
elog(ERROR, "Post-planning sanity check detected motion deadlock.");
}