use of org.voltdb.plannodes.IndexScanPlanNode in project voltdb by VoltDB.
the class ReplaceWithIndexCounter method recursivelyApply.
@Override
protected AbstractPlanNode recursivelyApply(AbstractPlanNode plan) {
assert (plan != null);
// depth first:
// find AggregatePlanNode with exactly one child
// where that child is an AbstractScanPlanNode.
// Replace any qualifying AggregatePlanNode / AbstractScanPlanNode pair
// with an IndexCountPlanNode or TableCountPlanNode
ArrayList<AbstractPlanNode> children = new ArrayList<AbstractPlanNode>();
for (int i = 0; i < plan.getChildCount(); i++) children.add(plan.getChild(i));
for (AbstractPlanNode child : children) {
// TODO this will break when children feed multiple parents
AbstractPlanNode newChild = recursivelyApply(child);
// Do a graft into the (parent) plan only if a replacement for a child was found.
if (newChild == child) {
continue;
}
boolean replaced = plan.replaceChild(child, newChild);
assert (true == replaced);
}
// check for an aggregation of the right form
if ((plan instanceof AggregatePlanNode) == false)
return plan;
assert (plan.getChildCount() == 1);
AggregatePlanNode aggplan = (AggregatePlanNode) plan;
// ENG-6131 fixed here.
if (!(aggplan.isTableCountStar() || aggplan.isTableNonDistinctCountConstant() || aggplan.isTableCountNonDistinctNullableColumn())) {
return plan;
}
AbstractPlanNode child = plan.getChild(0);
// A table count can replace a seq scan only if it has no predicates.
if (child instanceof SeqScanPlanNode) {
if (((SeqScanPlanNode) child).getPredicate() != null) {
return plan;
}
AbstractExpression postPredicate = aggplan.getPostPredicate();
if (postPredicate != null) {
List<AbstractExpression> aggList = postPredicate.findAllAggregateSubexpressions();
boolean allCountStar = true;
for (AbstractExpression expr : aggList) {
if (expr.getExpressionType() != ExpressionType.AGGREGATE_COUNT_STAR) {
allCountStar = false;
break;
}
}
if (allCountStar) {
return plan;
}
}
if (hasInlineLimit(aggplan)) {
// table count EE executor does not handle inline limit stuff
return plan;
}
return new TableCountPlanNode((AbstractScanPlanNode) child, aggplan);
}
// Otherwise, optimized counts only replace particular cases of index scan.
if ((child instanceof IndexScanPlanNode) == false)
return plan;
IndexScanPlanNode isp = (IndexScanPlanNode) child;
// Guard against (possible future?) cases of indexable subquery.
if (((IndexScanPlanNode) child).isSubQuery()) {
return plan;
}
// except those (post-)predicates are artifact predicates we added for reverse scan purpose only
if (isp.getPredicate() != null && !isp.isPredicatesOptimizableForAggregate()) {
return plan;
}
// With no start or end keys, there's not much a counting index can do.
if (isp.getEndExpression() == null && isp.getSearchKeyExpressions().size() == 0) {
if (hasInlineLimit(aggplan)) {
return plan;
}
return new TableCountPlanNode(isp, aggplan);
}
// check for the index's support for counting
Index idx = isp.getCatalogIndex();
if (!idx.getCountable()) {
return plan;
}
// The core idea is that counting index needs to know the start key and end key to
// jump to to get counts instead of actually doing any scanning.
// Options to be determined are:
// - whether each of the start/end keys is missing, partial (a prefix of a compund key), or complete,
// - whether the count should include or exclude entries exactly matching each of the start/end keys.
// Not all combinations of these options are supported;
// unsupportable cases cause the factory method to return null.
IndexCountPlanNode countingPlan = IndexCountPlanNode.createOrNull(isp, aggplan);
if (countingPlan == null) {
return plan;
}
return countingPlan;
}
use of org.voltdb.plannodes.IndexScanPlanNode in project voltdb by VoltDB.
the class ReplaceWithIndexLimit method recursivelyApply.
@Override
protected AbstractPlanNode recursivelyApply(AbstractPlanNode plan) {
assert (plan != null);
// depth first:
// Find AggregatePlanNode with exactly one child
// where that child is an AbstractScanPlanNode.
// Replace qualifying SeqScanPlanNode with an
// IndexScanPlanNode with an inlined LimitPlanNode;
// or appending the LimitPlanNode to the existing
// qualified IndexScanPlanNode.
ArrayList<AbstractPlanNode> children = new ArrayList<AbstractPlanNode>();
for (int i = 0; i < plan.getChildCount(); i++) children.add(plan.getChild(i));
for (AbstractPlanNode child : children) {
// TODO this will break when children feed multiple parents
AbstractPlanNode newChild = recursivelyApply(child);
// Do a graft into the (parent) plan only if a replacement for a child was found.
if (newChild == child) {
continue;
}
child.removeFromGraph();
plan.addAndLinkChild(newChild);
}
// check for an aggregation of the right form
if ((plan instanceof AggregatePlanNode) == false)
return plan;
assert (plan.getChildCount() == 1);
AggregatePlanNode aggplan = (AggregatePlanNode) plan;
// handle one single min() / max() now
// TODO: combination of [min(), max(), count()]
SortDirectionType sortDirection = SortDirectionType.INVALID;
if (aggplan.isTableMin()) {
sortDirection = SortDirectionType.ASC;
} else if (aggplan.isTableMax()) {
sortDirection = SortDirectionType.DESC;
} else {
return plan;
}
AbstractPlanNode child = plan.getChild(0);
AbstractExpression aggExpr = aggplan.getFirstAggregateExpression();
// for a SEQSCAN, replace it with a INDEXSCAN node with an inline LIMIT plan node
if (child instanceof SeqScanPlanNode) {
// should have other index access plan if any qualified index found for the predicate
if (((SeqScanPlanNode) child).getPredicate() != null) {
return plan;
}
if (((AbstractScanPlanNode) child).isSubQuery()) {
return plan;
}
// create an empty bindingExprs list, used for store (possible) bindings for adHoc query
ArrayList<AbstractExpression> bindings = new ArrayList<AbstractExpression>();
Index ret = findQualifiedIndex(((SeqScanPlanNode) child), aggExpr, bindings);
if (ret == null) {
return plan;
} else {
// 1. create one INDEXSCAN plan node with inlined LIMIT
// and replace the SEQSCAN node with it
// 2. we know which end row we want to fetch, so it's safe to
// specify sorting direction here
IndexScanPlanNode ispn = new IndexScanPlanNode((SeqScanPlanNode) child, aggplan, ret, sortDirection);
ispn.setBindings(bindings);
assert (ispn.getSearchKeyExpressions().size() == 0);
if (sortDirection == SortDirectionType.ASC) {
assert (aggplan.isTableMin());
ispn.setSkipNullPredicate(0);
}
LimitPlanNode lpn = new LimitPlanNode();
lpn.setLimit(1);
lpn.setOffset(0);
ispn.addInlinePlanNode(lpn);
// remove old SeqScan node and link the new generated IndexScan node
plan.clearChildren();
plan.addAndLinkChild(ispn);
return plan;
}
}
if ((child instanceof IndexScanPlanNode) == false) {
return plan;
}
// already have the IndexScanPlanNode
IndexScanPlanNode ispn = (IndexScanPlanNode) child;
// we added for reverse scan purpose only
if (((IndexScanPlanNode) child).getPredicate() != null && !((IndexScanPlanNode) child).isPredicatesOptimizableForAggregate()) {
return plan;
}
// Guard against (possible future?) cases of indexable subquery.
if (((AbstractScanPlanNode) child).isSubQuery()) {
return plan;
}
// 2. Handle equality filters and one other comparison operator (<, <=, >, >=), see comments below
if (ispn.getLookupType() != IndexLookupType.EQ && Math.abs(ispn.getSearchKeyExpressions().size() - ExpressionUtil.uncombinePredicate(ispn.getEndExpression()).size()) > 1) {
return plan;
}
// exprs will be used as filterExprs to check the index
// For forward scan, the initial value is endExprs and might be changed in different values in variant cases
// For reverse scan, the initial value is initialExprs which is the "old" endExprs
List<AbstractExpression> exprs;
int numOfSearchKeys = ispn.getSearchKeyExpressions().size();
if (ispn.getLookupType() == IndexLookupType.LT || ispn.getLookupType() == IndexLookupType.LTE) {
exprs = ExpressionUtil.uncombinePredicate(ispn.getInitialExpression());
numOfSearchKeys -= 1;
} else {
exprs = ExpressionUtil.uncombinePredicate(ispn.getEndExpression());
}
int numberOfExprs = exprs.size();
/* Retrieve the index expressions from the target index. (ENG-8819, Ethan)
* This is because we found that for the following two queries:
* #1: explain select max(c2/2) from t where c1=1 and c2/2<=3;
* #2: explain select max(c2/2) from t where c1=1 and c2/2<=?;
* We can get an inline limit 1 for #2 but not for #1. This is because all constants in #1 got parameterized.
* The result is that the query cannot pass the bindingToIndexedExpression() tests below
* because we lost all the constant value expressions (cannot attempt to bind a pve to a pve!).
* Those constant values expressions can only be accessed from the idnex.
* We will not add those bindings to the ispn.getBindings() here because they will be added anyway in checkIndex().
* PS: For this case (i.e. index on expressions), checkIndex() will call checkExpressionIndex(),
* where bindings will be added.
*/
Index indexToUse = ispn.getCatalogIndex();
String tableAlias = ispn.getTargetTableAlias();
List<AbstractExpression> indexedExprs = null;
if (!indexToUse.getExpressionsjson().isEmpty()) {
StmtTableScan tableScan = m_parsedStmt.getStmtTableScanByAlias(tableAlias);
try {
indexedExprs = AbstractExpression.fromJSONArrayString(indexToUse.getExpressionsjson(), tableScan);
} catch (JSONException e) {
e.printStackTrace();
assert (false);
return plan;
}
}
/* If there is only 1 difference between searchkeyExprs and endExprs,
* 1. trivial filters can be discarded, 2 possibilities:
* a. SELECT MIN(X) FROM T WHERE [other prefix filters] X < / <= ?
* <=> SELECT MIN(X) FROM T WHERE [other prefix filters] && the X < / <= ? filter
* b. SELECT MAX(X) FROM T WHERE X > / >= ?
* <=> SELECT MAX(X) FROM T with post-filter
* 2. filter should act as equality filter, 2 possibilities
* SELECT MIN(X) FROM T WHERE [other prefix filters] X > / >= ?
* SELECT MAX(X) FROM T WHERE [other prefix filters] X < / <= ?
* check if there is other filters for SELECT MAX(X) FROM T WHERE [other prefix filter AND ] X > / >= ?
* but we should allow SELECT MAX(X) FROM T WHERE X = ?
* This is for queries having MAX() but no ORDER BY. (ENG-8819, Ethan)
* sortDirection == DESC if max, ASC if min. ispn.getSortDirection() == INVALID if no ORDER BY. */
if (sortDirection == SortDirectionType.DESC && ispn.getSortDirection() == SortDirectionType.INVALID) {
/* numberOfExprs = exprs.size(), exprs are initial expressions for reversed index scans (lookupType LT, LTE),
* are end expressions for forward index scans (lookupType GT, GTE, EQ).
* Note, lookupType doesn't decide the scan direction for sure. MIN(X) where X < ? is still a forward scan.
* X < ? will be a post filter for the scan rather than an initial expression. */
if (numberOfExprs == 1) {
// e.g.: explain select max(c2/2) from t where c2/2<=3;
// In this case, as long as the where condition (exprs.get(0)) matches the aggregation argument, continue.
AbstractExpression exprToBind = indexedExprs == null ? exprs.get(0).getLeft() : indexedExprs.get(0);
if (aggExpr.bindingToIndexedExpression(exprToBind) == null) {
return plan;
}
} else if (numberOfExprs > 1) {
// ENG-4016: Optimization for query SELECT MAX(X) FROM T WHERE [other prefix filters] X < / <= ?
// Just keep trying, don't return early.
boolean earlyReturn = true;
for (int i = 0; i < numberOfExprs; ++i) {
AbstractExpression expr = exprs.get(i);
AbstractExpression indexedExpr = indexedExprs == null ? expr.getLeft() : indexedExprs.get(i);
if (aggExpr.bindingToIndexedExpression(indexedExpr) != null && (expr.getExpressionType() == ExpressionType.COMPARE_LESSTHANOREQUALTO || expr.getExpressionType() == ExpressionType.COMPARE_LESSTHAN || expr.getExpressionType() == ExpressionType.COMPARE_EQUAL)) {
earlyReturn = false;
break;
}
}
if (earlyReturn) {
return plan;
}
}
}
// have an upper bound: # of endingExpr is more than # of searchExpr
if (numberOfExprs > numOfSearchKeys) {
AbstractExpression lastEndExpr = exprs.get(numberOfExprs - 1);
// check last ending condition, see whether it is
// SELECT MIN(X) FROM T WHERE [other prefix filters] X < / <= ? or
// other filters will be checked later
AbstractExpression exprToBind = indexedExprs == null ? lastEndExpr.getLeft() : indexedExprs.get(numberOfExprs - 1);
if ((lastEndExpr.getExpressionType() == ExpressionType.COMPARE_LESSTHAN || lastEndExpr.getExpressionType() == ExpressionType.COMPARE_LESSTHANOREQUALTO) && aggExpr.bindingToIndexedExpression(exprToBind) != null) {
exprs.remove(lastEndExpr);
}
}
// and we can take advantage of that
if (checkIndex(ispn.getCatalogIndex(), aggExpr, exprs, ispn.getBindings(), tableAlias)) {
// we know which end we want to fetch, set the sort direction
ispn.setSortDirection(sortDirection);
// for SELECT MIN(X) FROM T WHERE [prefix filters] = ?
if (numberOfExprs == numOfSearchKeys && sortDirection == SortDirectionType.ASC) {
if (ispn.getLookupType() == IndexLookupType.GTE) {
assert (aggplan.isTableMin());
ispn.setSkipNullPredicate(numOfSearchKeys);
}
}
// reset the IndexLookupType, remove "added" searchKey, add back to endExpression, and clear "added" predicate
if (sortDirection == SortDirectionType.ASC && (ispn.getLookupType() == IndexLookupType.LT || ispn.getLookupType() == IndexLookupType.LTE)) {
ispn.setLookupType(IndexLookupType.GTE);
ispn.removeLastSearchKey();
ispn.addEndExpression(ExpressionUtil.uncombinePredicate(ispn.getInitialExpression()).get(numberOfExprs - 1));
ispn.setSkipNullPredicate(numOfSearchKeys);
ispn.resetPredicate();
}
// add an inline LIMIT plan node to this index scan plan node
LimitPlanNode lpn = new LimitPlanNode();
lpn.setLimit(1);
lpn.setOffset(0);
ispn.addInlinePlanNode(lpn);
// |__LimitPlanNode
if (sortDirection == SortDirectionType.DESC && !ispn.getSearchKeyExpressions().isEmpty() && exprs.isEmpty() && ExpressionUtil.uncombinePredicate(ispn.getInitialExpression()).isEmpty()) {
AbstractExpression newPredicate = new ComparisonExpression();
if (ispn.getLookupType() == IndexLookupType.GT)
newPredicate.setExpressionType(ExpressionType.COMPARE_GREATERTHAN);
if (ispn.getLookupType() == IndexLookupType.GTE)
newPredicate.setExpressionType(ExpressionType.COMPARE_GREATERTHANOREQUALTO);
newPredicate.setRight(ispn.getSearchKeyExpressions().get(0));
newPredicate.setLeft(aggExpr);
newPredicate.setValueType(aggExpr.getValueType());
ispn.clearSearchKeyExpression();
aggplan.setPrePredicate(newPredicate);
}
}
return plan;
}
use of org.voltdb.plannodes.IndexScanPlanNode in project voltdb by VoltDB.
the class TestSelfJoins method testIndexedSelfJoin.
public void testIndexedSelfJoin() {
AbstractPlanNode.enableVerboseExplainForDebugging();
IndexScanPlanNode c;
AbstractPlanNode apn;
AbstractPlanNode pn;
NestLoopIndexPlanNode nlij;
List<AbstractExpression> searchKeys;
// SELF JOIN using two different indexes on the same table
// sometimes with a surviving sort ordering that supports GROUP BY and/or ORDER BY.
apn = compile("select * FROM R2 A, R2 B WHERE A.A = B.A AND B.C > 1 ORDER BY B.C");
//* for debug */ System.out.println(apn.toExplainPlanString());
// Some day, the wasteful projection node will not be here to skip.
pn = apn.getChild(0).getChild(0);
assertTrue(pn instanceof NestLoopIndexPlanNode);
nlij = (NestLoopIndexPlanNode) pn;
assertNull(nlij.getPreJoinPredicate());
assertNull(nlij.getJoinPredicate());
assertNull(nlij.getWherePredicate());
assertEquals(1, nlij.getChildCount());
c = (IndexScanPlanNode) nlij.getChild(0);
assertNull(c.getPredicate());
assertEquals(IndexLookupType.GT, c.getLookupType());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof ConstantValueExpression);
c = (IndexScanPlanNode) nlij.getInlinePlanNode(PlanNodeType.INDEXSCAN);
assertEquals(IndexLookupType.GTE, c.getLookupType());
assertNull(c.getPredicate());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof TupleValueExpression);
apn = compile("select * FROM R2 A, R2 B WHERE A.A = B.A AND B.C > 1 ORDER BY B.A, B.C");
//* for debug */ System.out.println(apn.toExplainPlanString());
// Some day, the wasteful projection node will not be here to skip.
pn = apn.getChild(0).getChild(0);
assertTrue(pn instanceof OrderByPlanNode);
pn = pn.getChild(0);
assertTrue(pn instanceof NestLoopIndexPlanNode);
nlij = (NestLoopIndexPlanNode) pn;
assertNull(nlij.getPreJoinPredicate());
assertNull(nlij.getJoinPredicate());
assertNull(nlij.getWherePredicate());
assertEquals(1, nlij.getChildCount());
c = (IndexScanPlanNode) nlij.getChild(0);
assertNull(c.getPredicate());
assertEquals(IndexLookupType.GT, c.getLookupType());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof ConstantValueExpression);
c = (IndexScanPlanNode) nlij.getInlinePlanNode(PlanNodeType.INDEXSCAN);
assertEquals(IndexLookupType.GTE, c.getLookupType());
assertNull(c.getPredicate());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof TupleValueExpression);
apn = compile("select * FROM R2 A, R2 B WHERE A.A = B.A AND B.A > 1 ORDER BY B.A, B.C");
//* for debug */ System.out.println(apn.toExplainPlanString());
// Some day, the wasteful projection node will not be here to skip.
pn = apn.getChild(0).getChild(0);
assertTrue(pn instanceof NestLoopIndexPlanNode);
nlij = (NestLoopIndexPlanNode) pn;
assertNull(nlij.getPreJoinPredicate());
assertNull(nlij.getJoinPredicate());
assertNull(nlij.getWherePredicate());
assertEquals(1, nlij.getChildCount());
assertTrue(nlij.getChild(0) instanceof IndexScanPlanNode);
c = (IndexScanPlanNode) nlij.getChild(0);
assertEquals(IndexLookupType.GT, c.getLookupType());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof ConstantValueExpression);
c = (IndexScanPlanNode) nlij.getInlinePlanNode(PlanNodeType.INDEXSCAN);
assertEquals(IndexLookupType.GTE, c.getLookupType());
assertNull(c.getPredicate());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof TupleValueExpression);
apn = compile("select B.C, MAX(A.C) FROM R2 A, R2 B WHERE A.A = B.A AND B.C > 1 GROUP BY B.C ORDER BY B.C");
//* for debug */ System.out.println(apn.toExplainPlanString());
// Some day, the wasteful projection node will not be here to skip.
pn = apn.getChild(0);
assertNotNull(AggregatePlanNode.getInlineAggregationNode(pn));
assertTrue(pn instanceof NestLoopIndexPlanNode);
nlij = (NestLoopIndexPlanNode) pn;
assertNull(nlij.getPreJoinPredicate());
assertNull(nlij.getJoinPredicate());
assertNull(nlij.getWherePredicate());
assertEquals(1, nlij.getChildCount());
c = (IndexScanPlanNode) nlij.getChild(0);
assertNull(c.getPredicate());
assertEquals(IndexLookupType.GT, c.getLookupType());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof ConstantValueExpression);
c = (IndexScanPlanNode) nlij.getInlinePlanNode(PlanNodeType.INDEXSCAN);
assertEquals(IndexLookupType.GTE, c.getLookupType());
assertNull(c.getPredicate());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof TupleValueExpression);
apn = compile("select B.C, B.A FROM R2 A, R2 B WHERE A.A = B.A AND B.C > 1 GROUP BY B.A, B.C ORDER BY B.A, B.C");
//* for debug */ System.out.println(apn.toExplainPlanString());
// Some day, the wasteful projection node will not be here to skip.
pn = apn.getChild(0).getChild(0);
assertTrue(pn instanceof OrderByPlanNode);
pn = pn.getChild(0);
assertNotNull(AggregatePlanNode.getInlineAggregationNode(pn));
assertTrue(pn instanceof NestLoopIndexPlanNode);
nlij = (NestLoopIndexPlanNode) pn;
assertNull(nlij.getPreJoinPredicate());
assertNull(nlij.getJoinPredicate());
assertNull(nlij.getWherePredicate());
assertEquals(1, nlij.getChildCount());
c = (IndexScanPlanNode) nlij.getChild(0);
assertNull(c.getPredicate());
assertEquals(IndexLookupType.GT, c.getLookupType());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof ConstantValueExpression);
c = (IndexScanPlanNode) nlij.getInlinePlanNode(PlanNodeType.INDEXSCAN);
assertEquals(IndexLookupType.GTE, c.getLookupType());
assertNull(c.getPredicate());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof TupleValueExpression);
apn = compile("select B.C, B.A FROM R2 A, R2 B WHERE A.A = B.A AND B.A > 1 GROUP BY B.A, B.C ORDER BY B.A, B.C");
//* for debug */ System.out.println(apn.toExplainPlanString());
pn = apn.getChild(0);
assertNotNull(AggregatePlanNode.getInlineAggregationNode(pn));
assertTrue(pn instanceof NestLoopIndexPlanNode);
nlij = (NestLoopIndexPlanNode) pn;
assertNull(nlij.getPreJoinPredicate());
assertNull(nlij.getJoinPredicate());
assertNull(nlij.getWherePredicate());
assertEquals(1, nlij.getChildCount());
assertTrue(nlij.getChild(0) instanceof IndexScanPlanNode);
c = (IndexScanPlanNode) nlij.getChild(0);
assertEquals(IndexLookupType.GT, c.getLookupType());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof ConstantValueExpression);
c = (IndexScanPlanNode) nlij.getInlinePlanNode(PlanNodeType.INDEXSCAN);
assertEquals(IndexLookupType.GTE, c.getLookupType());
assertNull(c.getPredicate());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof TupleValueExpression);
// Here's a case that can't be optimized because it purposely uses the "wrong" column
// in the GROUP BY and ORDER BY.
apn = compile("select B.C FROM R2 A, R2 B WHERE B.A = A.A AND B.C > 1 GROUP BY A.A, B.C ORDER BY A.A, B.C");
//* for debug */ System.out.println(apn.toExplainPlanString());
// Complex ORDER BY case: GROUP BY column that is not in the display column list
pn = apn.getChild(0);
assertTrue(pn instanceof ProjectionPlanNode);
pn = pn.getChild(0);
assertTrue(pn instanceof OrderByPlanNode);
pn = pn.getChild(0);
assertNotNull(AggregatePlanNode.getInlineAggregationNode(pn));
assertTrue(pn instanceof NestLoopIndexPlanNode);
nlij = (NestLoopIndexPlanNode) pn;
assertNull(nlij.getPreJoinPredicate());
assertNull(nlij.getJoinPredicate());
assertNull(nlij.getWherePredicate());
assertEquals(1, nlij.getChildCount());
c = (IndexScanPlanNode) nlij.getChild(0);
assertNull(c.getPredicate());
assertEquals(IndexLookupType.GT, c.getLookupType());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof ConstantValueExpression);
c = (IndexScanPlanNode) nlij.getInlinePlanNode(PlanNodeType.INDEXSCAN);
assertEquals(IndexLookupType.GTE, c.getLookupType());
assertNull(c.getPredicate());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof TupleValueExpression);
// Here's a case that can't be optimized because it purposely uses the "wrong" column
// in the GROUP BY and ORDER BY.
apn = compile("select B.C FROM R2 A, R2 B WHERE B.A = A.A AND B.C > 1 GROUP BY A.A, B.C ORDER BY B.C");
//* for debug */ System.out.println(apn.toExplainPlanString());
// Project-first case: GROUP BY column that is not in the order by or the display column list
pn = apn.getChild(0);
assertTrue(pn instanceof OrderByPlanNode);
pn = pn.getChild(0);
//TODO: This represents a missed optimization.
// The projection could have been inlined.
assertTrue(pn instanceof ProjectionPlanNode);
pn = pn.getChild(0);
assertNotNull(AggregatePlanNode.getInlineAggregationNode(pn));
assertTrue(pn instanceof NestLoopIndexPlanNode);
nlij = (NestLoopIndexPlanNode) pn;
assertNull(nlij.getPreJoinPredicate());
assertNull(nlij.getJoinPredicate());
assertNull(nlij.getWherePredicate());
assertEquals(1, nlij.getChildCount());
c = (IndexScanPlanNode) nlij.getChild(0);
assertNull(c.getPredicate());
assertEquals(IndexLookupType.GT, c.getLookupType());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof ConstantValueExpression);
c = (IndexScanPlanNode) nlij.getInlinePlanNode(PlanNodeType.INDEXSCAN);
assertEquals(IndexLookupType.GTE, c.getLookupType());
assertNull(c.getPredicate());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof TupleValueExpression);
// This variant shows that the GROUP BY can be a permutation of the sort order
// without messing up the optimization
apn = compile("select B.C, B.A FROM R2 A, R2 B WHERE A.A = B.A AND B.A > 1 GROUP BY B.C, B.A ORDER BY B.A, B.C");
//* for debug */ System.out.println(apn.toExplainPlanString());
// Some day, the wasteful projection node will not be here to skip.
pn = apn.getChild(0);
assertNotNull(AggregatePlanNode.getInlineAggregationNode(pn));
assertTrue(pn instanceof NestLoopIndexPlanNode);
nlij = (NestLoopIndexPlanNode) pn;
assertNull(nlij.getPreJoinPredicate());
assertNull(nlij.getJoinPredicate());
assertNull(nlij.getWherePredicate());
assertEquals(1, nlij.getChildCount());
assertTrue(nlij.getChild(0) instanceof IndexScanPlanNode);
c = (IndexScanPlanNode) nlij.getChild(0);
assertEquals(IndexLookupType.GT, c.getLookupType());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof ConstantValueExpression);
c = (IndexScanPlanNode) nlij.getInlinePlanNode(PlanNodeType.INDEXSCAN);
assertEquals(IndexLookupType.GTE, c.getLookupType());
assertNull(c.getPredicate());
searchKeys = c.getSearchKeyExpressions();
assertEquals(1, searchKeys.size());
assertTrue(searchKeys.get(0) instanceof TupleValueExpression);
}
use of org.voltdb.plannodes.IndexScanPlanNode in project voltdb by VoltDB.
the class TestPlansSubQueries method testJoins.
public void testJoins() {
AbstractPlanNode pn;
List<AbstractPlanNode> planNodes;
AbstractPlanNode nlpn;
String sql, sqlNoSimplification, equivalentSql;
// Left Outer join
sql = "SELECT R1.A, R1.C FROM R1 LEFT JOIN (SELECT A, C FROM R2) T1 ON T1.C = R1.C ";
sqlNoSimplification = "SELECT R1.A, R1.C FROM R1 LEFT JOIN (SELECT A, C FROM R2 LIMIT 10) T1 ON T1.C = R1.C ";
equivalentSql = "SELECT R1.A, R1.C FROM R1 LEFT JOIN R2 T1 ON T1.C = R1.C ";
planNodes = compileToFragments(sqlNoSimplification);
assertEquals(1, planNodes.size());
pn = planNodes.get(0).getChild(0);
assertTrue(pn instanceof ProjectionPlanNode);
nlpn = pn.getChild(0);
assertTrue(nlpn instanceof NestLoopPlanNode);
assertEquals(JoinType.LEFT, ((NestLoopPlanNode) nlpn).getJoinType());
pn = nlpn.getChild(0);
checkSeqScan(pn, "R1", "A", "C");
pn = nlpn.getChild(1);
checkSeqScan(pn, "T1", "C");
pn = pn.getChild(0);
checkSeqScan(pn, "R2", "A", "C");
checkSubquerySimplification(sql, equivalentSql);
// Join with partitioned tables
// Join on coordinator: LEFT OUTER JOIN, replicated table on left side
sql = "SELECT R1.A, R1.C FROM R1 LEFT JOIN (SELECT A, C FROM P1) T1 ON T1.C = R1.C ";
sqlNoSimplification = "SELECT R1.A, R1.C FROM R1 LEFT JOIN (SELECT DISTINCT A, C FROM P1) T1 ON T1.C = R1.C ";
equivalentSql = "SELECT R1.A, R1.C FROM R1 LEFT JOIN P1 T1 ON T1.C = R1.C ";
planNodes = compileToFragments(sqlNoSimplification);
assertEquals(2, planNodes.size());
pn = planNodes.get(0).getChild(0);
assertTrue(pn instanceof ProjectionPlanNode);
nlpn = pn.getChild(0);
assertTrue(nlpn instanceof NestLoopPlanNode);
assertEquals(JoinType.LEFT, ((NestLoopPlanNode) nlpn).getJoinType());
pn = nlpn.getChild(0);
checkSeqScan(pn, "R1", "A", "C");
pn = nlpn.getChild(1);
assertEquals(PlanNodeType.RECEIVE, pn.getPlanNodeType());
pn = planNodes.get(1);
assertTrue(pn instanceof SendPlanNode);
pn = pn.getChild(0);
checkSeqScan(pn, "T1", "C");
checkSubquerySimplification(sql, equivalentSql);
// Group by inside of the subquery
// whether it contains group by or not does not matter, because we check it by whether inner side is partitioned or not
planNodes = compileToFragments("SELECT R1.A, R1.C FROM R1 LEFT JOIN (SELECT A, count(*) C FROM P1 GROUP BY A) T1 ON T1.C = R1.C ");
assertEquals(2, planNodes.size());
pn = planNodes.get(0).getChild(0);
assertTrue(pn instanceof ProjectionPlanNode);
nlpn = pn.getChild(0);
assertTrue(nlpn instanceof NestLoopPlanNode);
assertEquals(JoinType.LEFT, ((NestLoopPlanNode) nlpn).getJoinType());
pn = nlpn.getChild(0);
checkSeqScan(pn, "R1", "A", "C");
pn = nlpn.getChild(1);
assertTrue(pn instanceof ReceivePlanNode);
pn = planNodes.get(1);
assertTrue(pn instanceof SendPlanNode);
pn = pn.getChild(0);
checkSeqScan(pn, "T1", "C");
pn = pn.getChild(0);
checkPrimaryKeyIndexScan(pn, "P1", "A", "C");
assertNotNull(pn.getInlinePlanNode(PlanNodeType.PROJECTION));
// Using index scan for group by only: use serial aggregate instead hash aggregate
assertNotNull(pn.getInlinePlanNode(PlanNodeType.AGGREGATE));
// LEFT partition table
planNodes = compileToFragments("SELECT T1.CC FROM P1 LEFT JOIN (SELECT A, count(*) CC FROM P2 GROUP BY A) T1 ON T1.A = P1.A ");
assertEquals(2, planNodes.size());
pn = planNodes.get(0).getChild(0);
assertTrue(pn instanceof ProjectionPlanNode);
pn = pn.getChild(0);
assertTrue(pn instanceof ReceivePlanNode);
pn = planNodes.get(1);
assertTrue(pn instanceof SendPlanNode);
nlpn = pn.getChild(0);
assertTrue(nlpn instanceof NestLoopPlanNode);
assertEquals(JoinType.LEFT, ((NestLoopPlanNode) nlpn).getJoinType());
pn = nlpn.getChild(0);
checkPrimaryKeyIndexScan(pn, "P1");
pn = nlpn.getChild(1);
checkSeqScan(pn, "T1");
pn = pn.getChild(0);
checkPrimaryKeyIndexScan(pn, "P2");
assertNotNull(pn.getInlinePlanNode(PlanNodeType.PROJECTION));
// Using index scan for group by only: use serial aggregate instead hash aggregate
assertNotNull(pn.getInlinePlanNode(PlanNodeType.AGGREGATE));
// Right outer join
planNodes = compileToFragments("SELECT R1.A, R1.C FROM R1 RIGHT JOIN (SELECT A, count(*) C FROM P1 GROUP BY A) T1 ON T1.C = R1.C ");
assertEquals(2, planNodes.size());
pn = planNodes.get(0).getChild(0);
assertTrue(pn instanceof ProjectionPlanNode);
pn = pn.getChild(0);
assertTrue(pn instanceof ReceivePlanNode);
pn = planNodes.get(1);
assertTrue(pn instanceof SendPlanNode);
nlpn = pn.getChild(0);
assertTrue(nlpn instanceof NestLoopPlanNode);
assertEquals(JoinType.LEFT, ((NestLoopPlanNode) nlpn).getJoinType());
pn = nlpn.getChild(1);
checkSeqScan(pn, "R1", "A", "C");
pn = nlpn.getChild(0);
checkSeqScan(pn, "T1", "C");
pn = pn.getChild(0);
checkPrimaryKeyIndexScan(pn, "P1", "A", "C");
assertNotNull(pn.getInlinePlanNode(PlanNodeType.PROJECTION));
// Using index scan for group by only: use serial aggregate instead hash aggregate
assertNotNull(pn.getInlinePlanNode(PlanNodeType.AGGREGATE));
// RIGHT partition table
planNodes = compileToFragments("SELECT T1.CC FROM P1 RIGHT JOIN (SELECT A, count(*) CC FROM P2 GROUP BY A) T1 ON T1.A = P1.A ");
assertEquals(2, planNodes.size());
pn = planNodes.get(0).getChild(0);
assertTrue(pn instanceof ProjectionPlanNode);
pn = pn.getChild(0);
assertTrue(pn instanceof ReceivePlanNode);
pn = planNodes.get(1);
assertTrue(pn instanceof SendPlanNode);
nlpn = pn.getChild(0);
assertTrue(nlpn instanceof NestLoopIndexPlanNode);
assertEquals(JoinType.LEFT, ((NestLoopIndexPlanNode) nlpn).getJoinType());
pn = nlpn.getInlinePlanNode(PlanNodeType.INDEXSCAN);
checkPrimaryKeyIndexScan(pn, "P1");
pn = nlpn.getChild(0);
checkSeqScan(pn, "T1");
pn = pn.getChild(0);
checkPrimaryKeyIndexScan(pn, "P2");
assertNotNull(pn.getInlinePlanNode(PlanNodeType.PROJECTION));
// Using index scan for group by only: use serial aggregate instead hash aggregate
assertNotNull(pn.getInlinePlanNode(PlanNodeType.AGGREGATE));
// Join locally: inner join case for subselects
sql = "SELECT R1.A, R1.C FROM R1 INNER JOIN (SELECT A, C FROM P1) T1 ON T1.C = R1.C ";
sqlNoSimplification = "SELECT R1.A, R1.C FROM R1 INNER JOIN (SELECT DISTINCT A, C FROM P1) T1 ON T1.C = R1.C ";
equivalentSql = "SELECT R1.A, R1.C FROM R1 INNER JOIN P1 T1 ON T1.C = R1.C ";
planNodes = compileToFragments(sqlNoSimplification);
assertEquals(2, planNodes.size());
pn = planNodes.get(0).getChild(0);
assertTrue(pn instanceof ProjectionPlanNode);
pn = pn.getChild(0);
assertTrue(pn instanceof ReceivePlanNode);
pn = planNodes.get(1);
assertTrue(pn instanceof SendPlanNode);
nlpn = pn.getChild(0);
assertTrue(nlpn instanceof NestLoopPlanNode);
assertEquals(JoinType.INNER, ((NestLoopPlanNode) nlpn).getJoinType());
pn = nlpn.getChild(0);
checkSeqScan(pn, "R1", "A", "C");
pn = nlpn.getChild(1);
checkSeqScan(pn, "T1", "C");
checkSubquerySimplification(sql, equivalentSql);
// Two sub-queries. One is partitioned and the other one is replicated
sql = "select A, AC FROM (SELECT A FROM R1) T1, (SELECT C AC FROM P1) T2 WHERE T1.A = T2.AC ";
sqlNoSimplification = "select A, AC FROM (SELECT A FROM R1 LIMIT 10) T1, (SELECT DISTINCT A AC FROM P1) T2 WHERE T1.A = T2.AC ";
equivalentSql = "select T1.A, T2.C AC FROM R1 T1, P1 T2 WHERE T1.A = T2.C ";
planNodes = compileToFragments(sqlNoSimplification);
assertEquals(2, planNodes.size());
pn = planNodes.get(0).getChild(0);
assertTrue(pn instanceof ProjectionPlanNode);
pn = pn.getChild(0);
assertTrue(pn instanceof ReceivePlanNode);
pn = planNodes.get(1);
assertTrue(pn instanceof SendPlanNode);
nlpn = pn.getChild(0);
assertTrue(nlpn instanceof NestLoopPlanNode);
assertEquals(JoinType.INNER, ((NestLoopPlanNode) nlpn).getJoinType());
pn = nlpn.getChild(0);
checkSeqScan(pn, "T1", "A");
pn = pn.getChild(0);
checkSeqScan(pn, "R1", "A");
pn = nlpn.getChild(1);
checkSeqScan(pn, "T2", "AC");
checkSubquerySimplification(sql, equivalentSql);
// This is a single fragment plan because planner can detect "A = 3".
// Join locally
sql = "select A1, A2 FROM (SELECT A A1 FROM R1) T1, (SELECT A A2 FROM P1 where A = 3) T2 WHERE T1.A1 = T2.A2 ";
sqlNoSimplification = "select A2, A1 FROM (SELECT DISTINCT A A1 FROM R1) T1, (SELECT DISTINCT A A2 FROM P1 where A = 3) T2 WHERE T1.A1 = T2.A2 ";
equivalentSql = "select T1.A A1, T2.A A2 FROM R1 T1 join P1 T2 on T2.A = 3 and T1.A = T2.A";
planNodes = compileToFragments(sqlNoSimplification);
assertEquals(1, planNodes.size());
pn = planNodes.get(0);
assertTrue(pn instanceof SendPlanNode);
pn = pn.getChild(0);
assertTrue(pn instanceof ProjectionPlanNode);
nlpn = pn.getChild(0);
assertTrue(nlpn instanceof NestLoopPlanNode);
pn = nlpn.getChild(0);
checkSeqScan(pn, "T1", "A1");
pn = nlpn.getChild(1);
checkSeqScan(pn, "T2", "A2");
pn = pn.getChild(0);
checkPrimaryKeyIndexScan(pn, "P1", "A");
assertEquals(2, ((IndexScanPlanNode) pn).getInlinePlanNodes().size());
assertNotNull(((IndexScanPlanNode) pn).getInlinePlanNode(PlanNodeType.PROJECTION));
assertNotNull(((IndexScanPlanNode) pn).getInlinePlanNode(PlanNodeType.AGGREGATE));
checkSubquerySimplification(sql, equivalentSql);
// More single partition detection
planNodes = compileToFragments("select C FROM (SELECT P1.C FROM P1, P2 " + "WHERE P1.A = P2.A AND P1.A = 3) T1 ");
assertEquals(1, planNodes.size());
planNodes = compileToFragments("select T1.C FROM (SELECT P1.C FROM P1, P2 " + "WHERE P1.A = P2.A AND P1.A = 3) T1, R1 where T1.C > R1.C ");
assertEquals(1, planNodes.size());
planNodes = compileToFragments("select T1.C FROM (SELECT P1.C FROM P1, P2 " + "WHERE P1.A = P2.A AND P1.A = 3) T1, (select C FROM R1) T2 where T1.C > T2.C ");
assertEquals(1, planNodes.size());
}
use of org.voltdb.plannodes.IndexScanPlanNode in project voltdb by VoltDB.
the class TestPlansSubQueries method testExpressionSubqueryWithIndexScan.
/**
* Expression subquery currently is not optimized to use any index. But this does not prevent the
* parent query to use index for other purposes.
*/
public void testExpressionSubqueryWithIndexScan() {
AbstractPlanNode pn;
String sql;
// INDEX on A, for sort order only
sql = "SELECT A FROM R4 where A in (select A from R4 where A > 3) order by A;";
pn = compile(sql);
pn = pn.getChild(0);
assertTrue(pn instanceof IndexScanPlanNode);
assertEquals(0, ((IndexScanPlanNode) pn).getSearchKeyExpressions().size());
assertNotNull(((IndexScanPlanNode) pn).getPredicate());
// INDEX on A, uniquely match A = 4,
sql = "SELECT A FROM R4 where A = 4 and C in (select A from R4 where A > 3);";
pn = compile(sql);
pn = pn.getChild(0);
assertTrue(pn instanceof IndexScanPlanNode);
assertEquals(1, ((IndexScanPlanNode) pn).getSearchKeyExpressions().size());
AbstractExpression comp = ((IndexScanPlanNode) pn).getSearchKeyExpressions().get(0);
assertEquals(ExpressionType.VALUE_CONSTANT, comp.getExpressionType());
assertEquals("4", ((ConstantValueExpression) comp).getValue());
assertNotNull(((IndexScanPlanNode) pn).getPredicate());
}
Aggregations