use of org.voltdb.plannodes.IndexScanPlanNode in project voltdb by VoltDB.
the class TestIndexSelection method checkIndexPredicateContains.
private void checkIndexPredicateContains(AbstractPlanNode pn, String... columns) {
assertEquals(1, pn.getChildCount());
pn = pn.getChild(0);
assertEquals(PlanNodeType.INDEXSCAN, pn.getPlanNodeType());
IndexScanPlanNode ipn = (IndexScanPlanNode) pn;
AbstractExpression pred = ipn.getPredicate();
assertNotNull(pred);
List<TupleValueExpression> tves = pred.findAllTupleValueSubexpressions();
for (TupleValueExpression tve : tves) {
boolean match = false;
for (String column : columns) {
if (tve.getColumnName().equals(column)) {
match = true;
break;
}
}
assertTrue(match);
}
}
use of org.voltdb.plannodes.IndexScanPlanNode in project voltdb by VoltDB.
the class TestPlansCount method testCountStar002.
public void testCountStar002() {
List<AbstractPlanNode> pn = compileToFragments("SELECT POINTS, count(*) from T1 Group by POINTS");
AbstractPlanNode p = pn.get(0).getChild(0);
assertTrue(p instanceof IndexScanPlanNode);
assertNotNull(p.getInlinePlanNode(PlanNodeType.AGGREGATE));
pn = compileToFragments("SELECT POINTS, count(1) from T1 Group by POINTS");
p = pn.get(0).getChild(0);
assertTrue(p instanceof IndexScanPlanNode);
assertNotNull(p.getInlinePlanNode(PlanNodeType.AGGREGATE));
}
use of org.voltdb.plannodes.IndexScanPlanNode in project voltdb by VoltDB.
the class TestJoinOrder method testMicroOptimizationJoinOrder.
public void testMicroOptimizationJoinOrder() {
// Microoptimization can be used for determinism only when working with replicated tables or
// single-partition queries.
List<AbstractPlanNode> pns;
AbstractPlanNode n;
pns = compileWithJoinOrderToFragments("select * from J1, P2 where A=B and A=1", "J1, P2");
n = pns.get(0).getChild(0).getChild(0);
assertTrue(((IndexScanPlanNode) n.getChild(0)).getTargetTableName().equals("J1"));
assertTrue(((SeqScanPlanNode) n.getChild(1)).getTargetTableName().equals("P2"));
pns = compileWithJoinOrderToFragments("select * from I1, T2 where A=B", "I1, T2");
//* enable to debug */ System.out.println(pns.get(0).toExplainPlanString());
n = pns.get(0).getChild(0).getChild(0);
assertTrue(((IndexScanPlanNode) n.getChild(0)).getTargetTableName().equals("I1"));
assertTrue(((SeqScanPlanNode) n.getChild(1)).getTargetTableName().equals("T2"));
}
use of org.voltdb.plannodes.IndexScanPlanNode in project voltdb by VoltDB.
the class PlanAssembler method switchToIndexScanForGroupBy.
/**
* For a seqscan feeding a GROUP BY, consider substituting an IndexScan
* that pre-sorts by the GROUP BY keys.
* If a candidate is already an indexscan,
* simply calculate GROUP BY column coverage
*
* @param candidate
* @param gbInfo
* @return true when planner can switch to index scan
* from a sequential scan, and when the index scan
* has no parent plan node or the candidate is already
* an indexscan and covers all or some GROUP BY columns
*/
private boolean switchToIndexScanForGroupBy(AbstractPlanNode candidate, IndexGroupByInfo gbInfo) {
if (!m_parsedSelect.isGrouped()) {
return false;
}
if (candidate instanceof IndexScanPlanNode) {
calculateIndexGroupByInfo((IndexScanPlanNode) candidate, gbInfo);
if (gbInfo.m_coveredGroupByColumns != null && !gbInfo.m_coveredGroupByColumns.isEmpty()) {
// The candidate index does cover all or some
// of the GROUP BY columns and can be serialized
gbInfo.m_indexAccess = candidate;
return true;
}
return false;
}
AbstractPlanNode sourceSeqScan = findSeqScanCandidateForGroupBy(candidate);
if (sourceSeqScan == null) {
return false;
}
assert (sourceSeqScan instanceof SeqScanPlanNode);
AbstractPlanNode parent = null;
if (sourceSeqScan.getParentCount() > 0) {
parent = sourceSeqScan.getParent(0);
}
AbstractPlanNode indexAccess = indexAccessForGroupByExprs((SeqScanPlanNode) sourceSeqScan, gbInfo);
if (indexAccess.getPlanNodeType() != PlanNodeType.INDEXSCAN) {
// does not find proper index to replace sequential scan
return false;
}
gbInfo.m_indexAccess = indexAccess;
if (parent != null) {
// have a parent and would like to replace
// the sequential scan with an index scan
indexAccess.clearParents();
// For two children join node, index 0 is its outer side
parent.replaceChild(0, indexAccess);
return false;
}
// parent is null and switched to index scan from sequential scan
return true;
}
use of org.voltdb.plannodes.IndexScanPlanNode 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;
}
Aggregations