use of org.voltdb.planner.parseinfo.BranchNode in project voltdb by VoltDB.
the class PlanAssembler method setupForNewPlans.
/**
* Clear any old state and get ready to plan a new plan. The next call to
* getNextPlan() will return the first candidate plan for these parameters.
*
*/
private void setupForNewPlans(AbstractParsedStmt parsedStmt) {
m_bestAndOnlyPlanWasGenerated = false;
m_partitioning.analyzeTablePartitioning(parsedStmt.allScans());
if (parsedStmt instanceof ParsedUnionStmt) {
m_parsedUnion = (ParsedUnionStmt) parsedStmt;
return;
}
if (parsedStmt instanceof ParsedSelectStmt) {
if (tableListIncludesExportOnly(parsedStmt.m_tableList)) {
throw new PlanningErrorException("Illegal to read a stream.");
}
m_parsedSelect = (ParsedSelectStmt) parsedStmt;
// Simplify the outer join if possible
if (m_parsedSelect.m_joinTree instanceof BranchNode) {
if (!m_parsedSelect.hasJoinOrder()) {
simplifyOuterJoin((BranchNode) m_parsedSelect.m_joinTree);
}
// Convert RIGHT joins to the LEFT ones
((BranchNode) m_parsedSelect.m_joinTree).toLeftJoin();
}
m_subAssembler = new SelectSubPlanAssembler(m_catalogDb, m_parsedSelect, m_partitioning);
// Process the GROUP BY information, decide whether it is group by the partition column
if (isPartitionColumnInGroupbyList(m_parsedSelect.groupByColumns())) {
m_parsedSelect.setHasPartitionColumnInGroupby();
}
if (isPartitionColumnInWindowedAggregatePartitionByList()) {
m_parsedSelect.setHasPartitionColumnInWindowedAggregate();
}
return;
}
// check that no modification happens to views
if (tableListIncludesReadOnlyView(parsedStmt.m_tableList)) {
throw new PlanningErrorException("Illegal to modify a materialized view.");
}
m_partitioning.setIsDML();
// figure out which table we're updating/deleting
if (parsedStmt instanceof ParsedSwapStmt) {
assert (parsedStmt.m_tableList.size() == 2);
if (tableListIncludesExportOnly(parsedStmt.m_tableList)) {
throw new PlanningErrorException("Illegal to swap a stream.");
}
m_parsedSwap = (ParsedSwapStmt) parsedStmt;
return;
}
Table targetTable = parsedStmt.m_tableList.get(0);
if (targetTable.getIsreplicated()) {
if (m_partitioning.wasSpecifiedAsSingle() && !m_partitioning.isReplicatedDmlToRunOnAllPartitions()) {
String msg = "Trying to write to replicated table '" + targetTable.getTypeName() + "' in a single-partition procedure.";
throw new PlanningErrorException(msg);
}
} else if (m_partitioning.wasSpecifiedAsSingle() == false) {
m_partitioning.setPartitioningColumnForDML(targetTable.getPartitioncolumn());
}
if (parsedStmt instanceof ParsedInsertStmt) {
m_parsedInsert = (ParsedInsertStmt) parsedStmt;
// The currently handled inserts are too simple to even require a subplan assembler. So, done.
return;
}
if (parsedStmt instanceof ParsedUpdateStmt) {
if (tableListIncludesExportOnly(parsedStmt.m_tableList)) {
throw new PlanningErrorException("Illegal to update a stream.");
}
m_parsedUpdate = (ParsedUpdateStmt) parsedStmt;
} else if (parsedStmt instanceof ParsedDeleteStmt) {
if (tableListIncludesExportOnly(parsedStmt.m_tableList)) {
throw new PlanningErrorException("Illegal to delete from a stream.");
}
m_parsedDelete = (ParsedDeleteStmt) parsedStmt;
} else {
throw new RuntimeException("Unknown subclass of AbstractParsedStmt.");
}
if (!m_partitioning.wasSpecifiedAsSingle()) {
//TODO: When updates and deletes can contain joins, this step may have to be
// deferred so that the valueEquivalence set can be analyzed per join order.
// This appears to be an unfortunate side effect of how the HSQL interface
// misleadingly organizes the placement of join/where filters on the statement tree.
// This throws off the accounting of equivalence join filters until they can be
// normalized in analyzeJoinFilters, but that normalization process happens on a
// per-join-order basis, and so, so must this analysis.
HashMap<AbstractExpression, Set<AbstractExpression>> valueEquivalence = parsedStmt.analyzeValueEquivalence();
Collection<StmtTableScan> scans = parsedStmt.allScans();
m_partitioning.analyzeForMultiPartitionAccess(scans, valueEquivalence);
}
m_subAssembler = new WriterSubPlanAssembler(m_catalogDb, parsedStmt, m_partitioning);
}
use of org.voltdb.planner.parseinfo.BranchNode in project voltdb by VoltDB.
the class SelectSubPlanAssembler method generateJoinOrdersForTree.
private static List<JoinNode> generateJoinOrdersForTree(JoinNode subTree) {
if (subTree instanceof BranchNode) {
BranchNode branchSubTree = (BranchNode) subTree;
JoinType joinType = branchSubTree.getJoinType();
if (joinType == JoinType.INNER) {
return generateInnerJoinOrdersForTree(subTree);
} else if (joinType == JoinType.LEFT) {
return generateOuterJoinOrdersForTree(subTree);
} else if (joinType == JoinType.FULL) {
return generateFullJoinOrdersForTree(subTree);
} else {
// Shouldn't get there
assert (false);
return null;
}
} else {
// Single tables and subqueries
return generateInnerJoinOrdersForTree(subTree);
}
}
use of org.voltdb.planner.parseinfo.BranchNode in project voltdb by VoltDB.
the class SelectSubPlanAssembler method getSelectSubPlanForJoinNode.
/**
* Given a specific join node and access path set for inner and outer tables, construct the plan
* that gives the right tuples.
*
* @param joinNode The join node to build the plan for.
* @param isInnerTable True if the join node is the inner node in the join
* @return A completed plan-sub-graph that should match the correct tuples from the
* correct tables.
*/
private AbstractPlanNode getSelectSubPlanForJoinNode(JoinNode joinNode) {
assert (joinNode != null);
if (joinNode instanceof BranchNode) {
BranchNode branchJoinNode = (BranchNode) joinNode;
// Outer node
AbstractPlanNode outerScanPlan = getSelectSubPlanForJoinNode(branchJoinNode.getLeftNode());
if (outerScanPlan == null) {
return null;
}
// Inner Node.
AbstractPlanNode innerScanPlan = getSelectSubPlanForJoinNode((branchJoinNode).getRightNode());
if (innerScanPlan == null) {
return null;
}
// Join Node
IndexSortablePlanNode answer = getSelectSubPlanForJoin(branchJoinNode, outerScanPlan, innerScanPlan);
// branch node is an inner join.
if ((answer != null) && (branchJoinNode.getJoinType() == JoinType.INNER) && outerScanPlan instanceof IndexSortablePlanNode) {
IndexUseForOrderBy indexUseForJoin = answer.indexUse();
IndexUseForOrderBy indexUseFromScan = ((IndexSortablePlanNode) outerScanPlan).indexUse();
indexUseForJoin.setWindowFunctionUsesIndex(indexUseFromScan.getWindowFunctionUsesIndex());
indexUseForJoin.setWindowFunctionIsCompatibleWithOrderBy(indexUseFromScan.isWindowFunctionCompatibleWithOrderBy());
indexUseForJoin.setFinalExpressionOrderFromIndexScan(indexUseFromScan.getFinalExpressionOrderFromIndexScan());
indexUseForJoin.setSortOrderFromIndexScan(indexUseFromScan.getSortOrderFromIndexScan());
}
if (answer == null) {
return null;
}
return answer.planNode();
}
// End of recursion
AbstractPlanNode scanNode = getAccessPlanForTable(joinNode);
// Connect the sub-query tree if any
if (joinNode instanceof SubqueryLeafNode) {
StmtSubqueryScan tableScan = ((SubqueryLeafNode) joinNode).getSubqueryScan();
CompiledPlan subQueryPlan = tableScan.getBestCostPlan();
assert (subQueryPlan != null);
assert (subQueryPlan.rootPlanGraph != null);
// The sub-query best cost plan needs to be un-linked from the previous parent plan
// it's the same child plan that gets re-attached to many parents one at a time
subQueryPlan.rootPlanGraph.disconnectParents();
scanNode.addAndLinkChild(subQueryPlan.rootPlanGraph);
}
return scanNode;
}
use of org.voltdb.planner.parseinfo.BranchNode in project voltdb by VoltDB.
the class SelectSubPlanAssembler method getSelectSubPlanForJoin.
/**
* Given a join node and plan-sub-graph for outer and inner sub-nodes,
* construct the plan-sub-graph for that node.
*
* @param joinNode A parent join node.
* @param outerPlan The outer node plan-sub-graph.
* @param innerPlan The inner node plan-sub-graph.
* @return A completed plan-sub-graph
* or null if a valid plan can not be produced for given access paths.
*/
private IndexSortablePlanNode getSelectSubPlanForJoin(BranchNode joinNode, AbstractPlanNode outerPlan, AbstractPlanNode innerPlan) {
// Filter (post-join) expressions
ArrayList<AbstractExpression> whereClauses = new ArrayList<>();
whereClauses.addAll(joinNode.m_whereInnerList);
whereClauses.addAll(joinNode.m_whereInnerOuterList);
if (joinNode.getJoinType() == JoinType.FULL) {
// For all other join types, the whereOuterList expressions were pushed down to the outer node
whereClauses.addAll(joinNode.m_whereOuterList);
}
assert (joinNode.getRightNode() != null);
JoinNode innerJoinNode = joinNode.getRightNode();
AccessPath innerAccessPath = innerJoinNode.m_currentAccessPath;
// We may need to add a send/receive pair to the inner plan for the special case.
// This trick only works once per plan, BUT once the partitioned data has been
// received on the coordinator, it can be treated as replicated data in later
// joins, which MAY help with later outer joins with replicated data.
boolean needInnerSendReceive = m_partitioning.requiresTwoFragments() && !innerPlan.hasReplicatedResult() && outerPlan.hasReplicatedResult() && joinNode.getJoinType() != JoinType.INNER;
// When the inner plan is an IndexScan, there MAY be a choice of whether to join using a
// NestLoopJoin (NLJ) or a NestLoopIndexJoin (NLIJ). The NLJ will have an advantage over the
// NLIJ in the cases where it applies, since it does a single access or iteration over the index
// and caches the result, where the NLIJ does an index access or iteration for each outer row.
// The NestLoopJoin applies when the inner IndexScan is driven only by parameter and constant
// expressions determined at the start of the query. That requires that none of the IndexScan's
// various expressions that drive the index access may reference columns from the outer row
// -- they can only reference columns of the index's base table (the indexed expressions)
// as well as constants and parameters. The IndexScan's "otherExprs" expressions that only
// drive post-filtering are not an issue since the NestLoopJoin does feature per-outer-tuple
// post-filtering on each pass over the cached index scan result.
// The special case of an OUTER JOIN of replicated outer row data with a partitioned inner
// table requires that the partitioned data be sent to the coordinator prior to the join.
// This limits the join option to NLJ. The index scan must make a single index access on
// each partition and cache the result at the coordinator for post-filtering.
// This requires that the index access be based on parameters or constants only
// -- the replicated outer row data will only be available later at the coordinator,
// so it can not drive the per-partition index scan.
// If the NLJ option is precluded for the usual reason (outer-row-based indexing) AND
// the NLIJ is precluded by the special case (OUTER JOIN of replicated outer rows and
// partitioned inner rows) this method returns null, effectively rejecting this indexed
// access path for the inner node. Other access paths or join orders may prove more successful.
boolean canHaveNLJ = true;
boolean canHaveNLIJ = true;
if (innerPlan instanceof IndexScanPlanNode) {
if (hasInnerOuterIndexExpression(joinNode.getRightNode().getTableAlias(), innerAccessPath.indexExprs, innerAccessPath.initialExpr, innerAccessPath.endExprs)) {
canHaveNLJ = false;
}
} else {
canHaveNLIJ = false;
}
if (needInnerSendReceive) {
canHaveNLIJ = false;
}
// partition columns
if (joinNode.getJoinType() == JoinType.FULL && m_partitioning.requiresTwoFragments() && !outerPlan.hasReplicatedResult() && innerPlan.hasReplicatedResult()) {
canHaveNLIJ = false;
canHaveNLJ = false;
}
AbstractJoinPlanNode ajNode = null;
if (canHaveNLJ) {
NestLoopPlanNode nljNode = new NestLoopPlanNode();
// get all the clauses that join the applicable two tables
// Copy innerAccessPath.joinExprs to leave it unchanged,
// avoiding accumulation of redundant expressions when
// joinClauses gets built up for various alternative plans.
ArrayList<AbstractExpression> joinClauses = new ArrayList<>(innerAccessPath.joinExprs);
if ((innerPlan instanceof IndexScanPlanNode) || (innerPlan instanceof NestLoopIndexPlanNode && innerPlan.getChild(0) instanceof MaterializedScanPlanNode)) {
// InnerPlan is an IndexScan OR an NLIJ of a MaterializedScan
// (IN LIST) and an IndexScan. In this case, the inner and
// inner-outer non-index join expressions (if any) are in the
// indexScan's otherExpr. The former should stay as IndexScanPlan
// predicates but the latter need to be pulled up into NLJ
// predicates because the IndexScan is executed once, not once
// per outer tuple.
ArrayList<AbstractExpression> otherExprs = new ArrayList<>();
// PLEASE do not update the "innerAccessPath.otherExprs", it may be reused
// for other path evaluation on the other outer side join.
List<AbstractExpression> innerExpr = filterSingleTVEExpressions(innerAccessPath.otherExprs, otherExprs);
joinClauses.addAll(otherExprs);
IndexScanPlanNode scanNode = null;
if (innerPlan instanceof IndexScanPlanNode) {
scanNode = (IndexScanPlanNode) innerPlan;
} else {
assert (innerPlan instanceof NestLoopIndexPlanNode);
scanNode = ((NestLoopIndexPlanNode) innerPlan).getInlineIndexScan();
}
scanNode.setPredicate(innerExpr);
} else if (innerJoinNode instanceof BranchNode && joinNode.getJoinType() != JoinType.INNER) {
// If the innerJoinNode is a LEAF node OR if the join type is an INNER join,
// the conditions that apply to the inner side
// have been applied as predicates to the inner scan node already.
// otherExpr of innerAccessPath comes from its parentNode's joinInnerList.
// For Outer join (LEFT or FULL), it could mean a join predicate on the table of
// the inner node ONLY, that can not be pushed down.
joinClauses.addAll(innerAccessPath.otherExprs);
}
nljNode.setJoinPredicate(ExpressionUtil.combinePredicates(joinClauses));
// combine the tails plan graph with the new head node
nljNode.addAndLinkChild(outerPlan);
// right child node.
if (needInnerSendReceive) {
// This trick only works once per plan.
if (outerPlan.hasAnyNodeOfClass(AbstractReceivePlanNode.class) || innerPlan.hasAnyNodeOfClass(AbstractReceivePlanNode.class)) {
return null;
}
innerPlan = addSendReceivePair(innerPlan);
}
nljNode.addAndLinkChild(innerPlan);
ajNode = nljNode;
} else if (canHaveNLIJ) {
NestLoopIndexPlanNode nlijNode = new NestLoopIndexPlanNode();
IndexScanPlanNode innerNode = (IndexScanPlanNode) innerPlan;
// Set IndexScan predicate. The INNER join expressions for a FULL join come from
// the innerAccessPath.joinExprs and need to be combined with the other join expressions
innerNode.setPredicate(innerAccessPath.joinExprs, innerAccessPath.otherExprs);
nlijNode.addInlinePlanNode(innerPlan);
// combine the tails plan graph with the new head node
nlijNode.addAndLinkChild(outerPlan);
ajNode = nlijNode;
} else {
m_recentErrorMsg = "Unsupported special case of complex OUTER JOIN between replicated outer table and partitioned inner table.";
return null;
}
ajNode.setJoinType(joinNode.getJoinType());
ajNode.setPreJoinPredicate(ExpressionUtil.combinePredicates(joinNode.m_joinOuterList));
ajNode.setWherePredicate(ExpressionUtil.combinePredicates(whereClauses));
ajNode.resolveSortDirection();
return ajNode;
}
use of org.voltdb.planner.parseinfo.BranchNode in project voltdb by VoltDB.
the class PlanAssembler method simplifyOuterJoinRecursively.
private static void simplifyOuterJoinRecursively(BranchNode joinNode, List<AbstractExpression> exprs) {
assert (joinNode != null);
JoinNode leftNode = joinNode.getLeftNode();
JoinNode rightNode = joinNode.getRightNode();
if (joinNode.getJoinType() == JoinType.LEFT) {
// see if the expression is NULL-rejecting for any of them
if (isNullRejecting(rightNode.generateTableJoinOrder(), exprs)) {
joinNode.setJoinType(JoinType.INNER);
}
} else if (joinNode.getJoinType() == JoinType.RIGHT) {
// see if the expression is NULL-rejecting for any of them
if (isNullRejecting(leftNode.generateTableJoinOrder(), exprs)) {
joinNode.setJoinType(JoinType.INNER);
}
} else if (joinNode.getJoinType() == JoinType.FULL) {
// see if the expression is NULL-rejecting for any of them
if (isNullRejecting(leftNode.generateTableJoinOrder(), exprs)) {
joinNode.setJoinType(JoinType.LEFT);
}
// see if the expression is NULL-rejecting for any of them
if (isNullRejecting(rightNode.generateTableJoinOrder(), exprs)) {
if (JoinType.FULL == joinNode.getJoinType()) {
joinNode.setJoinType(JoinType.RIGHT);
} else {
// LEFT join was just removed
joinNode.setJoinType(JoinType.INNER);
}
}
}
// because they simplify both inner and outer nodes.
if (leftNode.getWhereExpression() != null) {
exprs.add(leftNode.getWhereExpression());
}
if (rightNode.getWhereExpression() != null) {
exprs.add(rightNode.getWhereExpression());
}
// The JOIN expressions (ON) are only applicable
// to the INNER node of an outer join.
List<AbstractExpression> exprsForInnerNode = new ArrayList<>(exprs);
if (leftNode.getJoinExpression() != null) {
exprsForInnerNode.add(leftNode.getJoinExpression());
}
if (rightNode.getJoinExpression() != null) {
exprsForInnerNode.add(rightNode.getJoinExpression());
}
List<AbstractExpression> leftNodeExprs;
List<AbstractExpression> rightNodeExprs;
switch(joinNode.getJoinType()) {
case INNER:
leftNodeExprs = exprsForInnerNode;
rightNodeExprs = exprsForInnerNode;
break;
case LEFT:
leftNodeExprs = exprs;
rightNodeExprs = exprsForInnerNode;
break;
case RIGHT:
leftNodeExprs = exprsForInnerNode;
rightNodeExprs = exprs;
break;
case FULL:
leftNodeExprs = exprs;
rightNodeExprs = exprs;
break;
default:
// shouldn't get there
leftNodeExprs = null;
rightNodeExprs = null;
assert (false);
}
if (leftNode instanceof BranchNode) {
simplifyOuterJoinRecursively((BranchNode) leftNode, leftNodeExprs);
}
if (rightNode instanceof BranchNode) {
simplifyOuterJoinRecursively((BranchNode) rightNode, rightNodeExprs);
}
}
Aggregations