You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by wa...@apache.org on 2018/02/16 19:04:20 UTC
[14/16] asterixdb git commit: [ASTERIXDB-1972][COMP][RT][TX]
index-only plan
http://git-wip-us.apache.org/repos/asf/asterixdb/blob/c3c23574/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/BTreeAccessMethod.java
----------------------------------------------------------------------
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/BTreeAccessMethod.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/BTreeAccessMethod.java
index 1f4676c..a82e780 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/BTreeAccessMethod.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/BTreeAccessMethod.java
@@ -46,10 +46,13 @@ import org.apache.commons.lang3.mutable.Mutable;
import org.apache.commons.lang3.mutable.MutableObject;
import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.algebricks.common.utils.Quadruple;
+import org.apache.hyracks.algebricks.common.utils.Triple;
import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalOperatorTag;
import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
import org.apache.hyracks.algebricks.core.algebra.expressions.IVariableTypeEnvironment;
@@ -65,7 +68,6 @@ import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractBina
import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractDataSourceOperator;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator.ExecutionMode;
-import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractUnnestMapOperator;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.SelectOperator;
@@ -86,14 +88,22 @@ public class BTreeAccessMethod implements IAccessMethod {
EQUAL
}
- private static final List<FunctionIdentifier> FUNC_IDENTIFIERS =
- Collections.unmodifiableList(Arrays.asList(AlgebricksBuiltinFunctions.EQ, AlgebricksBuiltinFunctions.LE,
- AlgebricksBuiltinFunctions.GE, AlgebricksBuiltinFunctions.LT, AlgebricksBuiltinFunctions.GT));
+ // The second boolean value tells whether the given function generates false positive results.
+ // That is, this function can produce false positive results if it is set to true.
+ // In this case, an index-search alone cannot replace the given SELECT condition and
+ // that SELECT condition needs to be applied after the index-search to get the correct results.
+ // For B+Tree indexes, there are no false positive results unless the given index is a composite index.
+ private static final List<Pair<FunctionIdentifier, Boolean>> FUNC_IDENTIFIERS = Collections
+ .unmodifiableList(Arrays.asList(new Pair<FunctionIdentifier, Boolean>(AlgebricksBuiltinFunctions.EQ, false),
+ new Pair<FunctionIdentifier, Boolean>(AlgebricksBuiltinFunctions.LE, false),
+ new Pair<FunctionIdentifier, Boolean>(AlgebricksBuiltinFunctions.GE, false),
+ new Pair<FunctionIdentifier, Boolean>(AlgebricksBuiltinFunctions.LT, false),
+ new Pair<FunctionIdentifier, Boolean>(AlgebricksBuiltinFunctions.GT, false)));
public static final BTreeAccessMethod INSTANCE = new BTreeAccessMethod();
@Override
- public List<FunctionIdentifier> getOptimizableFunctions() {
+ public List<Pair<FunctionIdentifier, Boolean>> getOptimizableFunctions() {
return FUNC_IDENTIFIERS;
}
@@ -123,59 +133,124 @@ public class BTreeAccessMethod implements IAccessMethod {
public boolean applySelectPlanTransformation(List<Mutable<ILogicalOperator>> afterSelectRefs,
Mutable<ILogicalOperator> selectRef, OptimizableOperatorSubTree subTree, Index chosenIndex,
AccessMethodAnalysisContext analysisCtx, IOptimizationContext context) throws AlgebricksException {
- SelectOperator select = (SelectOperator) selectRef.getValue();
- Mutable<ILogicalExpression> conditionRef = select.getCondition();
+ SelectOperator selectOp = (SelectOperator) selectRef.getValue();
+ Mutable<ILogicalExpression> conditionRef = selectOp.getCondition();
+ AbstractFunctionCallExpression funcExpr = (AbstractFunctionCallExpression) conditionRef.getValue();
- ILogicalOperator primaryIndexUnnestOp =
- createSecondaryToPrimaryPlan(conditionRef, subTree, null, chosenIndex, analysisCtx,
- AccessMethodUtils.retainInputs(subTree.getDataSourceVariables(),
- subTree.getDataSourceRef().getValue(), afterSelectRefs),
- false, subTree.getDataSourceRef().getValue().getInputs().get(0).getValue()
- .getExecutionMode() == ExecutionMode.UNPARTITIONED,
- context);
+ // Check whether assign (unnest) operator exists before the select operator
+ Mutable<ILogicalOperator> assignBeforeSelectOpRef =
+ subTree.getAssignsAndUnnestsRefs().isEmpty() ? null : subTree.getAssignsAndUnnestsRefs().get(0);
+ ILogicalOperator assignBeforeSelectOp = null;
+ if (assignBeforeSelectOpRef != null) {
+ assignBeforeSelectOp = assignBeforeSelectOpRef.getValue();
+ }
- if (primaryIndexUnnestOp == null) {
+ Dataset dataset = subTree.getDataset();
+
+ // To check whether the given plan is an index-only plan.
+ // index-only plan possible?
+ boolean isIndexOnlyPlan = false;
+
+ // Whether a verification is required after this secondary index search.
+ // In other words, can the chosen method generate any false positive results?
+ // Currently, for the B+ Tree index, there cannot be any false positive results unless it's a composite index.
+ boolean requireVerificationAfterSIdxSearch = false;
+
+ Pair<Boolean, Boolean> functionFalsePositiveCheck =
+ AccessMethodUtils.canFunctionGenerateFalsePositiveResultsUsingIndex(funcExpr, FUNC_IDENTIFIERS);
+ if (functionFalsePositiveCheck.first) {
+ // An index-utilizable function found? then, get the info about false positive results generation.
+ requireVerificationAfterSIdxSearch = functionFalsePositiveCheck.second;
+ } else {
return false;
}
- Mutable<ILogicalOperator> opRef =
- subTree.getAssignsAndUnnestsRefs().isEmpty() ? null : subTree.getAssignsAndUnnestsRefs().get(0);
- ILogicalOperator op = null;
- if (opRef != null) {
- op = opRef.getValue();
+
+ Quadruple<Boolean, Boolean, Boolean, Boolean> indexOnlyPlanInfo =
+ new Quadruple<>(isIndexOnlyPlan, false, requireVerificationAfterSIdxSearch, false);
+
+ if (dataset.getDatasetType() == DatasetType.INTERNAL && !chosenIndex.isPrimaryIndex()) {
+ AccessMethodUtils.indexOnlyPlanCheck(afterSelectRefs, selectRef, subTree, null, chosenIndex, analysisCtx,
+ context, indexOnlyPlanInfo);
+ isIndexOnlyPlan = indexOnlyPlanInfo.getFirst();
}
- // Generate new select using the new condition.
+
+ // Sets the result of index-only plan check into AccessMethodAnalysisContext.
+ analysisCtx.setIndexOnlyPlanInfo(indexOnlyPlanInfo);
+
+ // Transform the current path to the path that is utilizing the corresponding indexes
+ ILogicalOperator primaryIndexUnnestOp = createIndexSearchPlan(afterSelectRefs, selectRef, conditionRef,
+ subTree.getAssignsAndUnnestsRefs(), subTree, null, chosenIndex, analysisCtx,
+ AccessMethodUtils.retainInputs(subTree.getDataSourceVariables(), subTree.getDataSourceRef().getValue(),
+ afterSelectRefs),
+ false, subTree.getDataSourceRef().getValue().getInputs().get(0).getValue()
+ .getExecutionMode() == ExecutionMode.UNPARTITIONED,
+ context, null);
+
+ if (primaryIndexUnnestOp == null) {
+ return false;
+ }
+
+ // Generate new path using the new condition.
if (conditionRef.getValue() != null) {
- select.getInputs().clear();
- if (op != null) {
- subTree.getDataSourceRef().setValue(primaryIndexUnnestOp);
- select.getInputs().add(new MutableObject<>(op));
+ if (assignBeforeSelectOp != null) {
+ if (isIndexOnlyPlan && dataset.getDatasetType() == DatasetType.INTERNAL) {
+ // Case 1: index-only plan
+ // The whole plan is now changed. Replace the current path with the given new plan.
+ // Gets the revised dataSourceRef operator
+ // - a secondary index-search that generates trustworthy results.
+ ILogicalOperator dataSourceRefOp =
+ AccessMethodUtils.findDataSourceFromIndexUtilizationPlan(primaryIndexUnnestOp);
+
+ if (dataSourceRefOp.getOperatorTag() == LogicalOperatorTag.UNNEST_MAP) {
+ subTree.getDataSourceRef().setValue(dataSourceRefOp);
+ }
+
+ // Replace the current operator with the newly created operator.
+ selectRef.setValue(primaryIndexUnnestOp);
+ } else {
+ // Case 2: Non-index only plan case
+ // Right now, the order of operators is: select <- assign <- unnest-map (primary index look-up).
+ selectOp.getInputs().clear();
+ subTree.getDataSourceRef().setValue(primaryIndexUnnestOp);
+ selectOp.getInputs().add(new MutableObject<ILogicalOperator>(assignBeforeSelectOp));
+ }
} else {
- select.getInputs().add(new MutableObject<>(primaryIndexUnnestOp));
+ // A secondary-index-only plan without any assign cannot exist. This is a non-index only plan.
+ selectOp.getInputs().clear();
+ selectOp.getInputs().add(new MutableObject<ILogicalOperator>(primaryIndexUnnestOp));
}
} else {
+ // All condition is now gone. UNNEST-MAP can replace the SELECT operator.
((AbstractLogicalOperator) primaryIndexUnnestOp).setExecutionMode(ExecutionMode.PARTITIONED);
- if (op != null) {
+ if (assignBeforeSelectOp != null) {
subTree.getDataSourceRef().setValue(primaryIndexUnnestOp);
- selectRef.setValue(op);
+ selectRef.setValue(assignBeforeSelectOp);
} else {
selectRef.setValue(primaryIndexUnnestOp);
}
}
+
return true;
}
@Override
- public boolean applyJoinPlanTransformation(Mutable<ILogicalOperator> joinRef,
- OptimizableOperatorSubTree leftSubTree, OptimizableOperatorSubTree rightSubTree, Index chosenIndex,
- AccessMethodAnalysisContext analysisCtx, IOptimizationContext context, boolean isLeftOuterJoin,
- boolean hasGroupBy) throws AlgebricksException {
+ public boolean applyJoinPlanTransformation(List<Mutable<ILogicalOperator>> afterJoinRefs,
+ Mutable<ILogicalOperator> joinRef, OptimizableOperatorSubTree leftSubTree,
+ OptimizableOperatorSubTree rightSubTree, Index chosenIndex, AccessMethodAnalysisContext analysisCtx,
+ IOptimizationContext context, boolean isLeftOuterJoin, boolean hasGroupBy) throws AlgebricksException {
AbstractBinaryJoinOperator joinOp = (AbstractBinaryJoinOperator) joinRef.getValue();
Mutable<ILogicalExpression> conditionRef = joinOp.getCondition();
- // Determine if the index is applicable on the left or right side
- // (if both, we arbitrarily prefer the left side).
- Dataset dataset = analysisCtx.getDatasetFromIndexDatasetMap(chosenIndex);
- OptimizableOperatorSubTree indexSubTree;
- OptimizableOperatorSubTree probeSubTree;
+
+ AbstractFunctionCallExpression funcExpr = null;
+ if (conditionRef.getValue().getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
+ funcExpr = (AbstractFunctionCallExpression) conditionRef.getValue();
+ }
+
+ Dataset dataset = analysisCtx.getIndexDatasetMap().get(chosenIndex);
+
+ // Determine if the index is applicable on the right (inner) side.
+ OptimizableOperatorSubTree indexSubTree = null;
+ OptimizableOperatorSubTree probeSubTree = null;
// We assume that the left subtree is the outer branch and the right subtree is the inner branch.
// This assumption holds true since we only use an index from the right subtree.
// The following is just a sanity check.
@@ -189,42 +264,41 @@ public class BTreeAccessMethod implements IAccessMethod {
LogicalVariable newNullPlaceHolderVar = null;
if (isLeftOuterJoin) {
- //get a new null place holder variable that is the first field variable of the primary key
- //from the indexSubTree's datasourceScanOp
+ // Gets a new null place holder variable that is the first field variable of the primary key
+ // from the indexSubTree's datasourceScanOp.
newNullPlaceHolderVar = indexSubTree.getDataSourceVariables().get(0);
}
- ILogicalOperator primaryIndexUnnestOp = createSecondaryToPrimaryPlan(conditionRef, indexSubTree, probeSubTree,
- chosenIndex, analysisCtx, true, isLeftOuterJoin, true, context);
- if (primaryIndexUnnestOp == null) {
+ boolean canContinue = AccessMethodUtils.setIndexOnlyPlanInfo(afterJoinRefs, joinRef, probeSubTree, indexSubTree,
+ chosenIndex, analysisCtx, context, funcExpr, FUNC_IDENTIFIERS);
+ if (!canContinue) {
return false;
}
- if (isLeftOuterJoin && hasGroupBy) {
- //reset the null place holder variable
- AccessMethodUtils.resetLOJNullPlaceholderVariableInGroupByOp(analysisCtx, newNullPlaceHolderVar, context);
- }
+ ILogicalOperator indexSearchOp = createIndexSearchPlan(afterJoinRefs, joinRef, conditionRef,
+ indexSubTree.getAssignsAndUnnestsRefs(), indexSubTree, probeSubTree, chosenIndex, analysisCtx, true,
+ isLeftOuterJoin, true, context, newNullPlaceHolderVar);
- // If there are conditions left, add a new select operator on top.
- indexSubTree.getDataSourceRef().setValue(primaryIndexUnnestOp);
- if (conditionRef.getValue() != null) {
- SelectOperator topSelect = new SelectOperator(conditionRef, isLeftOuterJoin, newNullPlaceHolderVar);
- topSelect.getInputs().add(indexSubTree.getRootRef());
- topSelect.setExecutionMode(ExecutionMode.LOCAL);
- context.computeAndSetTypeEnvironmentForOperator(topSelect);
- // Replace the original join with the new subtree rooted at the select op.
- joinRef.setValue(topSelect);
- } else {
- joinRef.setValue(indexSubTree.getRootRef().getValue());
+ if (indexSearchOp == null) {
+ return false;
}
- return true;
+
+ return AccessMethodUtils.finalizeJoinPlanTransformation(afterJoinRefs, joinRef, indexSubTree, analysisCtx,
+ context, isLeftOuterJoin, hasGroupBy, indexSearchOp, newNullPlaceHolderVar, conditionRef, dataset);
}
+ /**
+ * Creates an index utilization plan optimization - in case of an index-only select plan:
+ * union < project < select < assign? < unnest-map(pidx) < split < unnest-map(sidx) < assign? < datasource-scan
+ * ..... < project < ................................... <
+ */
@Override
- public ILogicalOperator createSecondaryToPrimaryPlan(Mutable<ILogicalExpression> conditionRef,
- OptimizableOperatorSubTree indexSubTree, OptimizableOperatorSubTree probeSubTree, Index chosenIndex,
- AccessMethodAnalysisContext analysisCtx, boolean retainInput, boolean retainNull, boolean requiresBroadcast,
- IOptimizationContext context) throws AlgebricksException {
+ public ILogicalOperator createIndexSearchPlan(List<Mutable<ILogicalOperator>> afterTopOpRefs,
+ Mutable<ILogicalOperator> topOpRef, Mutable<ILogicalExpression> conditionRef,
+ List<Mutable<ILogicalOperator>> assignBeforeTheOpRefs, OptimizableOperatorSubTree indexSubTree,
+ OptimizableOperatorSubTree probeSubTree, Index chosenIndex, AccessMethodAnalysisContext analysisCtx,
+ boolean retainInput, boolean retainMissing, boolean requiresBroadcast, IOptimizationContext context,
+ LogicalVariable newMissingPlaceHolderForLOJ) throws AlgebricksException {
Dataset dataset = indexSubTree.getDataset();
ARecordType recordType = indexSubTree.getRecordType();
ARecordType metaRecordType = indexSubTree.getMetaRecordType();
@@ -233,10 +307,20 @@ public class BTreeAccessMethod implements IAccessMethod {
(AbstractDataSourceOperator) indexSubTree.getDataSourceRef().getValue();
List<Pair<Integer, Integer>> exprAndVarList = analysisCtx.getIndexExprsFromIndexExprsAndVars(chosenIndex);
int numSecondaryKeys = analysisCtx.getNumberOfMatchedKeys(chosenIndex);
+
+ // Whether the given plan is an index-only plan or not.
+ Quadruple<Boolean, Boolean, Boolean, Boolean> indexOnlyPlanInfo = analysisCtx.getIndexOnlyPlanInfo();
+ boolean isIndexOnlyPlan = indexOnlyPlanInfo.getFirst();
+
+ // We only apply index-only plan for an internal dataset.
+ boolean generateInstantTrylockResultFromIndexSearch = false;
+ if (dataset.getDatasetType() == DatasetType.INTERNAL && isIndexOnlyPlan) {
+ generateInstantTrylockResultFromIndexSearch = true;
+ }
+
// List of function expressions that will be replaced by the secondary-index search.
// These func exprs will be removed from the select condition at the very end of this method.
Set<ILogicalExpression> replacedFuncExprs = new HashSet<>();
-
// Info on high and low keys for the BTree search predicate.
ILogicalExpression[] lowKeyExprs = new ILogicalExpression[numSecondaryKeys];
ILogicalExpression[] highKeyExprs = new ILogicalExpression[numSecondaryKeys];
@@ -257,6 +341,9 @@ public class BTreeAccessMethod implements IAccessMethod {
BitSet setHighKeys = new BitSet(numSecondaryKeys);
// Go through the func exprs listed as optimizable by the chosen index,
// and formulate a range predicate on the secondary-index keys.
+
+ // Checks whether a type casting happened from a real (FLOAT, DOUBLE) value to an INT value
+ // since we have a round issue when dealing with LT(<) OR GT(>) operator.
for (Pair<Integer, Integer> exprIndex : exprAndVarList) {
// Position of the field of matchedFuncExprs.get(exprIndex) in the chosen index's indexed exprs.
IOptimizableFuncExpr optFuncExpr = analysisCtx.getMatchedFuncExpr(exprIndex.first);
@@ -268,10 +355,16 @@ public class BTreeAccessMethod implements IAccessMethod {
if (keyPos < 0) {
throw CompilationException.create(ErrorCode.NO_INDEX_FIELD_NAME_FOR_GIVEN_FUNC_EXPR);
}
+ // returnedSearchKeyExpr contains a pair of search expression.
+ // The second expression will not be null only if we are creating an EQ search predicate
+ // with a FLOAT or a DOUBLE constant that will be fed into an INTEGER index.
+ // This is required because of type-casting. Refer to AccessMethodUtils.createSearchKeyExpr for details.
IAType indexedFieldType = chosenIndex.getKeyFieldTypes().get(keyPos);
- Pair<ILogicalExpression, Boolean> returnedSearchKeyExpr = AccessMethodUtils.createSearchKeyExpr(chosenIndex,
- optFuncExpr, indexedFieldType, indexSubTree, probeSubTree);
+ Triple<ILogicalExpression, ILogicalExpression, Boolean> returnedSearchKeyExpr =
+ AccessMethodUtils.createSearchKeyExpr(chosenIndex, optFuncExpr, indexedFieldType, probeSubTree);
ILogicalExpression searchKeyExpr = returnedSearchKeyExpr.first;
+ ILogicalExpression searchKeyEQExpr = null;
+ boolean realTypeConvertedToIntegerType = returnedSearchKeyExpr.third;
if (searchKeyExpr.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
constantAtRuntimeExpressions[keyPos] = searchKeyExpr;
constAtRuntimeExprVars[keyPos] = context.newVar();
@@ -283,10 +376,13 @@ public class BTreeAccessMethod implements IAccessMethod {
return null;
}
- // checks whether a type casting happened from a real (FLOAT, DOUBLE) value to an INT value
- // since we have a round issues when dealing with LT(<) OR GT(>) operator.
- boolean realTypeConvertedToIntegerType = returnedSearchKeyExpr.second;
+ if (limit == LimitType.EQUAL && returnedSearchKeyExpr.second != null) {
+ // The given search predicate is EQ and
+ // we have two type-casted values from FLOAT or DOUBLE to an INT constant.
+ searchKeyEQExpr = returnedSearchKeyExpr.second;
+ }
+ // Deals with the non-enforced index case here.
if (relaxLimitTypeToInclusive(chosenIndex, keyPos, realTypeConvertedToIntegerType)) {
if (limit == LimitType.HIGH_EXCLUSIVE) {
limit = LimitType.HIGH_INCLUSIVE;
@@ -300,7 +396,16 @@ public class BTreeAccessMethod implements IAccessMethod {
if (lowKeyLimits[keyPos] == null && highKeyLimits[keyPos] == null) {
lowKeyLimits[keyPos] = highKeyLimits[keyPos] = limit;
lowKeyInclusive[keyPos] = highKeyInclusive[keyPos] = true;
- lowKeyExprs[keyPos] = highKeyExprs[keyPos] = searchKeyExpr;
+ if (searchKeyEQExpr == null) {
+ // No type-casting was happened.
+ lowKeyExprs[keyPos] = highKeyExprs[keyPos] = searchKeyExpr;
+ } else {
+ // We have two type-casted FLOAT or DOUBLE values to be fed into an INT index.
+ // They contain the same value if their fraction value is 0.
+ // Refer to AccessMethodUtils.createSearchKeyExpr() for more details.
+ lowKeyExprs[keyPos] = searchKeyExpr;
+ highKeyExprs[keyPos] = searchKeyEQExpr;
+ }
setLowKeys.set(keyPos);
setHighKeys.set(keyPos);
isEqCondition = true;
@@ -444,8 +549,8 @@ public class BTreeAccessMethod implements IAccessMethod {
highKeyInclusive[0] = true;
}
- // Here we generate vars and funcs for assigning the secondary-index keys to be fed into the secondary-index
- // search.
+ // Here we generate vars and funcs for assigning the secondary-index keys to be fed into
+ // the secondary-index search.
// List of variables for the assign.
ArrayList<LogicalVariable> keyVarList = new ArrayList<>();
// List of variables and expressions for the assign.
@@ -477,7 +582,9 @@ public class BTreeAccessMethod implements IAccessMethod {
} else {
// We are optimizing a join, place the assign op top of the probe subtree.
assignSearchKeys.getInputs().add(probeSubTree.getRootRef());
+ assignSearchKeys.setExecutionMode(probeSubTree.getRootRef().getValue().getExecutionMode());
}
+ context.computeAndSetTypeEnvironmentForOperator(assignSearchKeys);
inputOp = assignSearchKeys;
} else if (probeSubTree == null) {
//nonpure case
@@ -496,36 +603,60 @@ public class BTreeAccessMethod implements IAccessMethod {
inputOp = probeSubTree.getRoot();
}
+ // Creates an unnest-map for the secondary index search.
+ // The result: SK, PK, [Optional - the result of an instantTrylock on PK]
ILogicalOperator secondaryIndexUnnestOp = AccessMethodUtils.createSecondaryIndexUnnestMap(dataset, recordType,
- metaRecordType, chosenIndex, inputOp, jobGenParams, context, false, retainInput, retainNull);
+ metaRecordType, chosenIndex, inputOp, jobGenParams, context, retainInput, retainMissing,
+ generateInstantTrylockResultFromIndexSearch);
// Generate the rest of the upstream plan which feeds the search results into the primary index.
- AbstractUnnestMapOperator primaryIndexUnnestOp = null;
+ ILogicalOperator indexSearchOp = null;
boolean isPrimaryIndex = chosenIndex.isPrimaryIndex();
if (dataset.getDatasetType() == DatasetType.EXTERNAL) {
// External dataset
- UnnestMapOperator externalDataAccessOp = AccessMethodUtils.createExternalDataLookupUnnestMap(dataSourceOp,
- dataset, recordType, secondaryIndexUnnestOp, context, retainInput, retainNull);
+ UnnestMapOperator externalDataAccessOp =
+ AccessMethodUtils.createExternalDataLookupUnnestMap(dataSourceOp, dataset, recordType,
+ metaRecordType, secondaryIndexUnnestOp, context, chosenIndex, retainInput, retainMissing);
indexSubTree.getDataSourceRef().setValue(externalDataAccessOp);
return externalDataAccessOp;
} else if (!isPrimaryIndex) {
- primaryIndexUnnestOp = AccessMethodUtils.createPrimaryIndexUnnestMap(dataSourceOp, dataset, recordType,
- metaRecordType, secondaryIndexUnnestOp, context, true, retainInput, retainNull, false);
-
- // Adds equivalence classes --- one equivalent class between a primary key
- // variable and a record field-access expression.
- EquivalenceClassUtils.addEquivalenceClassesForPrimaryIndexAccess(primaryIndexUnnestOp,
- dataSourceOp.getVariables(), recordType, metaRecordType, dataset, context);
+ indexSearchOp = AccessMethodUtils.createRestOfIndexSearchPlan(afterTopOpRefs, topOpRef, conditionRef,
+ assignBeforeTheOpRefs, dataSourceOp, dataset, recordType, metaRecordType, secondaryIndexUnnestOp,
+ context, true, retainInput, retainMissing, false, chosenIndex, analysisCtx, indexSubTree,
+ newMissingPlaceHolderForLOJ);
+
+ // Replaces the datasource scan with the new plan rooted at
+ // Get dataSourceRef operator -
+ // 1) unnest-map (PK, record) for a non-index only plan
+ // 2) unnest-map (SK, PK) for an index-only plan
+ if (isIndexOnlyPlan) {
+ // Index-only plan
+ ILogicalOperator dataSourceRefOp =
+ AccessMethodUtils.findDataSourceFromIndexUtilizationPlan(indexSearchOp);
+
+ if (dataSourceRefOp.getOperatorTag() == LogicalOperatorTag.UNNEST_MAP
+ || dataSourceRefOp.getOperatorTag() == LogicalOperatorTag.LEFT_OUTER_UNNEST_MAP) {
+ // Adds equivalence classes --- one equivalent class between a primary key
+ // variable and a record field-access expression.
+ EquivalenceClassUtils.addEquivalenceClassesForPrimaryIndexAccess(indexSearchOp,
+ dataSourceOp.getVariables(), recordType, metaRecordType, dataset, context);
+ }
+ } else {
+ // Non-indexonly plan cases
+ // Adds equivalence classes --- one equivalent class between a primary key
+ // variable and a record field-access expression.
+ EquivalenceClassUtils.addEquivalenceClassesForPrimaryIndexAccess(indexSearchOp,
+ dataSourceOp.getVariables(), recordType, metaRecordType, dataset, context);
+ }
} else {
+ // Primary index search case
List<Object> primaryIndexOutputTypes = new ArrayList<>();
AccessMethodUtils.appendPrimaryIndexTypes(dataset, recordType, metaRecordType, primaryIndexOutputTypes);
List<LogicalVariable> scanVariables = dataSourceOp.getVariables();
- // Checks whether the primary index search can replace the given
- // SELECT condition.
- // If so, condition will be set to null and eventually the SELECT
- // operator will be removed.
+ // Checks whether the primary index search can replace the given SELECT condition.
+ // If so, the condition will be set to null and eventually the SELECT operator will be removed.
// If not, we create a new condition based on remaining ones.
if (!primaryIndexPostProccessingIsNeeded) {
List<Mutable<ILogicalExpression>> remainingFuncExprs = new ArrayList<>();
@@ -534,7 +665,7 @@ public class BTreeAccessMethod implements IAccessMethod {
} catch (CompilationException e) {
return null;
}
- // Generate new condition.
+ // Generates the new condition.
if (!remainingFuncExprs.isEmpty()) {
ILogicalExpression pulledCond = createSelectCondition(remainingFuncExprs);
conditionRef.setValue(pulledCond);
@@ -545,7 +676,7 @@ public class BTreeAccessMethod implements IAccessMethod {
// Checks whether LEFT_OUTER_UNNESTMAP operator is required.
boolean leftOuterUnnestMapRequired = false;
- if (retainNull && retainInput) {
+ if (retainMissing && retainInput) {
leftOuterUnnestMapRequired = true;
} else {
leftOuterUnnestMapRequired = false;
@@ -556,42 +687,41 @@ public class BTreeAccessMethod implements IAccessMethod {
// via the UnnestMapOperator's function arguments.
List<Mutable<ILogicalExpression>> primaryIndexFuncArgs = new ArrayList<>();
jobGenParams.writeToFuncArgs(primaryIndexFuncArgs);
- // An index search is expressed as an unnest-map over an
- // index-search function.
+ // An index search is expressed as an unnest-map over an index-search function.
IFunctionInfo primaryIndexSearch = FunctionUtil.getFunctionInfo(BuiltinFunctions.INDEX_SEARCH);
UnnestingFunctionCallExpression primaryIndexSearchFunc =
new UnnestingFunctionCallExpression(primaryIndexSearch, primaryIndexFuncArgs);
primaryIndexSearchFunc.setReturnsUniqueValues(true);
if (!leftOuterUnnestMapRequired) {
- primaryIndexUnnestOp = new UnnestMapOperator(scanVariables,
+ indexSearchOp = new UnnestMapOperator(scanVariables,
new MutableObject<ILogicalExpression>(primaryIndexSearchFunc), primaryIndexOutputTypes,
retainInput);
} else {
- primaryIndexUnnestOp = new LeftOuterUnnestMapOperator(scanVariables,
+ indexSearchOp = new LeftOuterUnnestMapOperator(scanVariables,
new MutableObject<ILogicalExpression>(primaryIndexSearchFunc), primaryIndexOutputTypes,
true);
}
} else {
if (!leftOuterUnnestMapRequired) {
- primaryIndexUnnestOp = new UnnestMapOperator(scanVariables,
+ indexSearchOp = new UnnestMapOperator(scanVariables,
((UnnestMapOperator) secondaryIndexUnnestOp).getExpressionRef(), primaryIndexOutputTypes,
retainInput);
} else {
- primaryIndexUnnestOp = new LeftOuterUnnestMapOperator(scanVariables,
+ indexSearchOp = new LeftOuterUnnestMapOperator(scanVariables,
((LeftOuterUnnestMapOperator) secondaryIndexUnnestOp).getExpressionRef(),
primaryIndexOutputTypes, true);
}
}
- primaryIndexUnnestOp.getInputs().add(new MutableObject<>(inputOp));
+ indexSearchOp.getInputs().add(new MutableObject<>(inputOp));
// Adds equivalence classes --- one equivalent class between a primary key
// variable and a record field-access expression.
- EquivalenceClassUtils.addEquivalenceClassesForPrimaryIndexAccess(primaryIndexUnnestOp, scanVariables,
- recordType, metaRecordType, dataset, context);
+ EquivalenceClassUtils.addEquivalenceClassesForPrimaryIndexAccess(indexSearchOp, scanVariables, recordType,
+ metaRecordType, dataset, context);
}
- return primaryIndexUnnestOp;
+ return indexSearchOp;
}
private int createKeyVarsAndExprs(int numKeys, LimitType[] keyLimits, ILogicalExpression[] searchKeyExprs,
@@ -706,6 +836,13 @@ public class BTreeAccessMethod implements IAccessMethod {
}
private boolean relaxLimitTypeToInclusive(Index chosenIndex, int keyPos, boolean realTypeConvertedToIntegerType) {
+ // For a non-enforced index or an enforced index that stores a casted value on the given index,
+ // we need to apply the following transformation.
+ // For an index on a closed field, this transformation is not necessary since the value between
+ // the index and the actual record match.
+ //
+ // Check AccessMethodUtils.createSearchKeyExpr for more details.
+ //
// If a DOUBLE or FLOAT constant is converted to an INT type value,
// we need to check a corner case where two real values are located between an INT value.
// For example, for the following query,
@@ -721,10 +858,7 @@ public class BTreeAccessMethod implements IAccessMethod {
// Therefore, we convert LT(<) to LE(<=) and GT(>) to GE(>=) to find candidates.
// This does not change the result of an actual comparison since this conversion is only applied
// for finding candidates from an index.
- //
- // We also need to do this for a non-enforced index that overrides key field type (for a numeric type)
-
- if (realTypeConvertedToIntegerType) {
+ if (chosenIndex.isEnforced() && realTypeConvertedToIntegerType) {
return true;
}
http://git-wip-us.apache.org/repos/asf/asterixdb/blob/c3c23574/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IAccessMethod.java
----------------------------------------------------------------------
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IAccessMethod.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IAccessMethod.java
index 870b425..559f336 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IAccessMethod.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IAccessMethod.java
@@ -23,9 +23,11 @@ import java.util.List;
import org.apache.asterix.metadata.entities.Index;
import org.apache.commons.lang3.mutable.Mutable;
import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.common.utils.Pair;
import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
import org.apache.hyracks.algebricks.core.algebra.expressions.IVariableTypeEnvironment;
import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
@@ -41,9 +43,10 @@ public interface IAccessMethod extends Comparable<IAccessMethod> {
/**
* @return A list of function identifiers that are optimizable by this
- * access method.
+ * access method. Also, the second boolean tells whether that
+ * function can generate a false-positive result.
*/
- public List<FunctionIdentifier> getOptimizableFunctions();
+ public List<Pair<FunctionIdentifier, Boolean>> getOptimizableFunctions();
/**
* Analyzes the arguments of a given optimizable funcExpr to see if this
@@ -86,20 +89,22 @@ public interface IAccessMethod extends Comparable<IAccessMethod> {
Mutable<ILogicalOperator> selectRef, OptimizableOperatorSubTree subTree, Index chosenIndex,
AccessMethodAnalysisContext analysisCtx, IOptimizationContext context) throws AlgebricksException;
- public ILogicalOperator createSecondaryToPrimaryPlan(Mutable<ILogicalExpression> conditionRef,
- OptimizableOperatorSubTree indexSubTree, OptimizableOperatorSubTree probeSubTree, Index chosenIndex,
- AccessMethodAnalysisContext analysisCtx, boolean retainInput, boolean retainNull, boolean requiresBroadcast,
- IOptimizationContext context) throws AlgebricksException;
+ public ILogicalOperator createIndexSearchPlan(List<Mutable<ILogicalOperator>> afterTopOpRefs,
+ Mutable<ILogicalOperator> topOpRef, Mutable<ILogicalExpression> conditionRef,
+ List<Mutable<ILogicalOperator>> assignBeforeTheOpRefs, OptimizableOperatorSubTree indexSubTree,
+ OptimizableOperatorSubTree probeSubTree, Index chosenIndex, AccessMethodAnalysisContext analysisCtx,
+ boolean retainInput, boolean retainNull, boolean requiresBroadcast, IOptimizationContext context,
+ LogicalVariable newNullPlaceHolderForLOJ) throws AlgebricksException;
/**
* Applies the plan transformation to use chosenIndex to optimize a join query.
* In the case of a LeftOuterJoin, there may or may not be a needed groupby operation
* If there is, we will need to include it in the transformation
*/
- public boolean applyJoinPlanTransformation(Mutable<ILogicalOperator> joinRef,
- OptimizableOperatorSubTree leftSubTree, OptimizableOperatorSubTree rightSubTree, Index chosenIndex,
- AccessMethodAnalysisContext analysisCtx, IOptimizationContext context, boolean isLeftOuterJoin,
- boolean hasGroupBy) throws AlgebricksException;
+ public boolean applyJoinPlanTransformation(List<Mutable<ILogicalOperator>> afterJoinRefs,
+ Mutable<ILogicalOperator> joinRef, OptimizableOperatorSubTree leftSubTree,
+ OptimizableOperatorSubTree rightSubTree, Index chosenIndex, AccessMethodAnalysisContext analysisCtx,
+ IOptimizationContext context, boolean isLeftOuterJoin, boolean hasGroupBy) throws AlgebricksException;
/**
* Analyzes expr to see whether it is optimizable by the given concrete index.
http://git-wip-us.apache.org/repos/asf/asterixdb/blob/c3c23574/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceJoinAccessMethodRule.java
----------------------------------------------------------------------
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceJoinAccessMethodRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceJoinAccessMethodRule.java
index 1171ae5..401ea23 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceJoinAccessMethodRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceJoinAccessMethodRule.java
@@ -18,6 +18,7 @@
*/
package org.apache.asterix.optimizer.rules.am;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -48,33 +49,37 @@ import org.apache.hyracks.algebricks.core.algebra.util.OperatorPropertiesUtil;
/**
* This rule optimizes a join with secondary indexes into an indexed nested-loop join.
- * Matches the following operator pattern:
- * (join) <-- (select)? <-- (assign | unnest)+ <-- (datasource scan)
- * <-- (select)? <-- (assign | unnest)+ <-- (datasource scan | unnest-map)
- * The order of the join inputs matters (left becomes the outer relation and right becomes the inner relation).
+ * This rule matches the following operator pattern:
+ * join <-- select? <-- (assign|unnest)+ <-- (datasource scan|unnest-map)
+ * The order of the join inputs matters (left branch - outer relation, right branch - inner relation).
* This rule tries to utilize an index on the inner relation.
* If that's not possible, it stops transforming the given join into an index-nested-loop join.
- * Replaces the above pattern with the following simplified plan:
- * (select) <-- (assign) <-- (btree search) <-- (sort) <-- (unnest(index search)) <-- (assign) <-- (A)
- * (A) <-- (datasource scan | unnest-map)
- * The sort is optional, and some access methods may choose not to sort.
+ *
+ * This rule replaces the above pattern with the following simplified plan:
+ * select <-- assign+ <-- unnest-map(pidx) <-- sort <-- unnest-map(sidx) <-- assign+ <-- (datasource scan|unnest-map)
+ * The sorting PK process is optional, and some access methods may choose not to sort.
* Note that for some index-based optimizations we do not remove the triggering
* condition from the join, since the secondary index may only act as a filter, and the
* final verification must still be done with the original join condition.
+ *
* The basic outline of this rule is:
* 1. Match operator pattern.
* 2. Analyze join condition to see if there are optimizable functions (delegated to IAccessMethods).
* 3. Check metadata to see if there are applicable indexes.
* 4. Choose an index to apply (for now only a single index will be chosen).
* 5. Rewrite plan using index (delegated to IAccessMethods).
+ *
* For left-outer-join, additional patterns are checked and additional treatment is needed as follows:
- * 1. First it checks if there is a groupByOp above the join: (groupby) <-- (leftouterjoin)
+ * 1. First it checks if there is a groupByOp above the join: groupby <-- leftouterjoin
* 2. Inherently, only the right-subtree of the lojOp can be used as indexSubtree.
- * So, the right-subtree must have at least one applicable index on join field(s)
+ * So, the right-subtree must have at least one applicable index on join field(s).
* 3. If there is a groupByOp, the null placeholder variable introduced in groupByOp should be taken care of correctly.
* Here, the primary key variable from datasourceScanOp replaces the introduced null placeholder variable.
- * If the primary key is composite key, then the first variable of the primary key variables becomes the
+ * If the primary key is a composite key, then the first variable of the primary key variables becomes the
* null place holder variable. This null placeholder variable works for all three types of indexes.
+ *
+ * If the inner-branch can be transformed as an index-only plan, this rule creates an index-only-plan path
+ * that is similar to one described in IntroduceSelectAccessMethod Rule.
*/
public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethodRule {
@@ -84,10 +89,10 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
protected final OptimizableOperatorSubTree leftSubTree = new OptimizableOperatorSubTree();
protected final OptimizableOperatorSubTree rightSubTree = new OptimizableOperatorSubTree();
protected IVariableTypeEnvironment typeEnvironment = null;
- protected boolean isLeftOuterJoin = false;
protected boolean hasGroupBy = true;
+ protected List<Mutable<ILogicalOperator>> afterJoinRefs = null;
- // Register access methods.
+ // Registers access methods.
protected static Map<FunctionIdentifier, List<IAccessMethod>> accessMethods = new HashMap<>();
static {
@@ -97,8 +102,8 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
}
/**
- * Recursively check the given plan from the root operator to transform a plan
- * with JOIN or LEFT-OUTER-JOIN operator into an index-utilized plan.
+ * Recursively checks the given plan from the root operator to transform a plan
+ * with INNERJOIN or LEFT-OUTER-JOIN operator into an index-utilized plan.
*/
@Override
@@ -114,7 +119,7 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
return false;
}
- // Check whether this operator is the root, which is DISTRIBUTE_RESULT or SINK since
+ // Checks whether this operator is the root, which is DISTRIBUTE_RESULT or SINK since
// we start the process from the root operator.
if (op.getOperatorTag() != LogicalOperatorTag.DISTRIBUTE_RESULT
&& op.getOperatorTag() != LogicalOperatorTag.SINK
@@ -127,7 +132,8 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
return false;
}
- // Recursively check the given plan whether the desired pattern exists in it.
+ afterJoinRefs = new ArrayList<>();
+ // Recursively checks the given plan whether the desired pattern exists in it.
// If so, try to optimize the plan.
boolean planTransformed = checkAndApplyJoinTransformation(opRef, context);
@@ -227,11 +233,11 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
joinRef = null;
joinOp = null;
joinCond = null;
- isLeftOuterJoin = false;
+ afterJoinRefs = null;
}
/**
- * Recursively traverse the given plan and check whether a INNERJOIN or LEFTOUTERJOIN operator exists.
+ * Recursively traverses the given plan and check whether a INNERJOIN or LEFTOUTERJOIN operator exists.
* If one is found, maintain the path from the root to the given join operator and
* optimize the path from the given join operator to the EMPTY_TUPLE_SOURCE operator
* if it is not already optimized.
@@ -241,6 +247,10 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
AbstractLogicalOperator op = (AbstractLogicalOperator) opRef.getValue();
boolean joinFoundAndOptimizationApplied;
+ // Adds the current operator to the operator list that contains operators after a join operator
+ // in case there is a descendant join operator and it could be transformed first.
+ afterJoinRefs.add(opRef);
+
// Recursively check the plan and try to optimize it. We first check the children of the given operator
// to make sure an earlier join in the path is optimized first.
for (Mutable<ILogicalOperator> inputOpRef : op.getInputs()) {
@@ -250,32 +260,39 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
}
}
- // Check the current operator pattern to see whether it is a JOIN or not.
+ // Now, we are sure that transformation attempts for earlier joins have been failed.
+ // Checks the current operator pattern to see whether it is a JOIN or not.
boolean isThisOpInnerJoin = isInnerJoin(op);
boolean isThisOpLeftOuterJoin = isLeftOuterJoin(op);
boolean isParentOpGroupBy = hasGroupBy;
Mutable<ILogicalOperator> joinRefFromThisOp = null;
AbstractBinaryJoinOperator joinOpFromThisOp = null;
-
+ // operators that need to be removed from the afterJoinRefs list.
+ Mutable<ILogicalOperator> opRefRemove = opRef;
if (isThisOpInnerJoin) {
- // Set join operator.
+ // Sets the join operator.
joinRef = opRef;
joinOp = (InnerJoinOperator) op;
joinRefFromThisOp = opRef;
joinOpFromThisOp = (InnerJoinOperator) op;
} else if (isThisOpLeftOuterJoin) {
- // Set left-outer-join op.
- // The current operator is GROUP and the child of this op is LEFTOUERJOIN.
+ // Sets the left-outer-join operator.
+ // The current operator is GROUP and the child of this operator is LEFTOUERJOIN.
joinRef = op.getInputs().get(0);
joinOp = (LeftOuterJoinOperator) joinRef.getValue();
joinRefFromThisOp = op.getInputs().get(0);
joinOpFromThisOp = (LeftOuterJoinOperator) joinRefFromThisOp.getValue();
+
+ // Group-by should not be removed at this point since the given left-outer-join can be transformed.
+ opRefRemove = op.getInputs().get(0);
}
+ afterJoinRefs.remove(opRefRemove);
- // For a JOIN case, try to transform the given plan.
+ // For a JOIN case, tries to transform the given plan.
if (isThisOpInnerJoin || isThisOpLeftOuterJoin) {
- // Restore the information from this operator since it might have been be set to null
+
+ // Restores the information from this operator since it might have been be set to null
// if there are other join operators in the earlier path.
joinRef = joinRefFromThisOp;
joinOp = joinOpFromThisOp;
@@ -294,14 +311,14 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
analyzedAMs = new HashMap<>();
}
- // Check the condition of JOIN operator is a function call since only function call can be transformed
- // using available indexes. If so, initialize the subtree information that will be used later to decide
+ // Checks the condition of JOIN operator is a function call since only function call can be transformed
+ // using available indexes. If so, initializes the subtree information that will be used later to decide
// whether the given plan is truly optimizable or not.
if (continueCheck && !checkJoinOpConditionAndInitSubTree(context)) {
continueCheck = false;
}
- // Analyze the condition of SELECT operator and initialize analyzedAMs.
+ // Analyzes the condition of SELECT operator and initializes analyzedAMs.
// Check whether the function in the SELECT operator can be truly transformed.
boolean matchInLeftSubTree = false;
boolean matchInRightSubTree = false;
@@ -316,7 +333,7 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
}
}
- // Find the dataset from the data-source and the record type of the dataset from the metadata.
+ // Finds the dataset from the data-source and the record type of the dataset from the metadata.
// This will be used to find an applicable index on the dataset.
boolean checkLeftSubTreeMetadata = false;
boolean checkRightSubTreeMetadata = false;
@@ -338,11 +355,11 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
}
fillSubTreeIndexExprs(rightSubTree, analyzedAMs, context);
- // Prune the access methods based on the function expression and access methods.
+ // Prunes the access methods based on the function expression and access methods.
pruneIndexCandidates(analyzedAMs, context, typeEnvironment);
// If the right subtree (inner branch) has indexes, one of those indexes will be used.
- // Remove the indexes from the outer branch in the optimizer's consideration list for this rule.
+ // Removes the indexes from the outer branch in the optimizer's consideration list for this rule.
pruneIndexCandidatesFromOuterBranch(analyzedAMs);
// We are going to use indexes from the inner branch.
@@ -354,7 +371,16 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
}
if (continueCheck) {
- // Apply plan transformation using chosen index.
+ // Finds the field name of each variable in the sub-tree such as variables for order by.
+ // This step is required when checking index-only plan.
+ if (checkLeftSubTreeMetadata) {
+ fillFieldNamesInTheSubTree(leftSubTree);
+ }
+ if (checkRightSubTreeMetadata) {
+ fillFieldNamesInTheSubTree(rightSubTree);
+ }
+
+ // Applies the plan transformation using chosen index.
AccessMethodAnalysisContext analysisCtx = analyzedAMs.get(chosenIndex.first);
// For LOJ with GroupBy, prepare objects to reset LOJ nullPlaceHolderVariable
@@ -363,7 +389,7 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
analysisCtx.setLOJGroupbyOpRef(opRef);
ScalarFunctionCallExpression isNullFuncExpr =
AccessMethodUtils.findLOJIsMissingFuncInGroupBy((GroupByOperator) opRef.getValue());
- analysisCtx.setLOJIsNullFuncInGroupBy(isNullFuncExpr);
+ analysisCtx.setLOJIsMissingFuncInGroupBy(isNullFuncExpr);
}
Dataset indexDataset = analysisCtx.getDatasetFromIndexDatasetMap(chosenIndex.second);
@@ -376,9 +402,10 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
return false;
}
- // Finally, try to apply plan transformation using chosen index.
- boolean res = chosenIndex.first.applyJoinPlanTransformation(joinRef, leftSubTree, rightSubTree,
- chosenIndex.second, analysisCtx, context, isThisOpLeftOuterJoin, isParentOpGroupBy);
+ // Finally, tries to apply plan transformation using the chosen index.
+ boolean res = chosenIndex.first.applyJoinPlanTransformation(afterJoinRefs, joinRef, leftSubTree,
+ rightSubTree, chosenIndex.second, analysisCtx, context, isThisOpLeftOuterJoin,
+ isParentOpGroupBy);
// If the plan transformation is successful, we don't need to traverse the plan
// any more, since if there are more JOIN operators, the next trigger on this plan
@@ -393,11 +420,19 @@ public class IntroduceJoinAccessMethodRule extends AbstractIntroduceAccessMethod
joinOp = null;
}
+ // Checked the given left-outer-join operator and it is not transformed. So, this group-by operator
+ // after the left-outer-join operator should be removed from the afterJoinRefs list
+ // since the current operator is a group-by operator.
+ if (isThisOpLeftOuterJoin) {
+ afterJoinRefs.remove(opRef);
+ }
+
return false;
}
/**
- * After the pattern is matched, check the condition and initialize the data sources from the both sub trees.
+ * After the pattern is matched, checks the condition and initializes the data source
+ * from the right (inner) sub tree.
*
* @throws AlgebricksException
*/
http://git-wip-us.apache.org/repos/asf/asterixdb/blob/c3c23574/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceLSMComponentFilterRule.java
----------------------------------------------------------------------
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceLSMComponentFilterRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceLSMComponentFilterRule.java
index 546652b..7938f49 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceLSMComponentFilterRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceLSMComponentFilterRule.java
@@ -62,6 +62,7 @@ import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperat
import org.apache.hyracks.algebricks.core.algebra.operators.logical.DataSourceScanOperator;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.SelectOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.SplitOperator;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestMapOperator;
import org.apache.hyracks.algebricks.core.algebra.util.OperatorPropertiesUtil;
import org.apache.hyracks.algebricks.core.rewriter.base.IAlgebraicRewriteRule;
@@ -79,7 +80,6 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
@Override
public boolean rewritePost(Mutable<ILogicalOperator> opRef, IOptimizationContext context)
throws AlgebricksException {
-
if (!checkIfRuleIsApplicable(opRef, context)) {
return false;
}
@@ -232,23 +232,23 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
queue.addAll(descendantOp.getInputs());
}
if (hasSecondaryIndexMap && !primaryUnnestMapOps.isEmpty()) {
- propagateFilterToPrimaryIndex(primaryUnnestMapOps, filterType, context);
+ propagateFilterToPrimaryIndex(primaryUnnestMapOps, filterType, context, false);
}
}
private void propagateFilterToPrimaryIndex(List<UnnestMapOperator> primaryUnnestMapOps, IAType filterType,
- IOptimizationContext context) throws AlgebricksException {
+ IOptimizationContext context, boolean isIndexOnlyPlan) throws AlgebricksException {
for (UnnestMapOperator primaryOp : primaryUnnestMapOps) {
Mutable<ILogicalOperator> assignOrOrderOrIntersect = primaryOp.getInputs().get(0);
- Mutable<ILogicalOperator> intersectOrSort = assignOrOrderOrIntersect;
+ Mutable<ILogicalOperator> intersectOrSortOrSplit = assignOrOrderOrIntersect;
if (assignOrOrderOrIntersect.getValue().getOperatorTag() == LogicalOperatorTag.ASSIGN) {
- intersectOrSort = assignOrOrderOrIntersect.getValue().getInputs().get(0);
+ intersectOrSortOrSplit = assignOrOrderOrIntersect.getValue().getInputs().get(0);
}
- switch (intersectOrSort.getValue().getOperatorTag()) {
+ switch (intersectOrSortOrSplit.getValue().getOperatorTag()) {
case INTERSECT:
- IntersectOperator intersect = (IntersectOperator) (intersectOrSort.getValue());
+ IntersectOperator intersect = (IntersectOperator) (intersectOrSortOrSplit.getValue());
List<List<LogicalVariable>> filterVars = new ArrayList<>(intersect.getInputs().size());
for (Mutable<ILogicalOperator> mutableOp : intersect.getInputs()) {
ILogicalOperator child = mutableOp.getValue();
@@ -267,13 +267,30 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
IntersectOperator intersectWithFilter =
createIntersectWithFilter(outputFilterVars, filterVars, intersect);
- intersectOrSort.setValue(intersectWithFilter);
+ intersectOrSortOrSplit.setValue(intersectWithFilter);
context.computeAndSetTypeEnvironmentForOperator(intersectWithFilter);
setPrimaryFilterVar(primaryOp, outputFilterVars.get(0), outputFilterVars.get(1), context);
}
break;
+ case SPLIT:
+ if (!isIndexOnlyPlan) {
+ throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+ intersectOrSortOrSplit.getValue().getOperatorTag().toString());
+ }
+ SplitOperator split = (SplitOperator) (intersectOrSortOrSplit.getValue());
+ for (Mutable<ILogicalOperator> childOp : split.getInputs()) {
+ ILogicalOperator child = childOp.getValue();
+ while (!child.getOperatorTag().equals(LogicalOperatorTag.UNNEST_MAP)) {
+ child = child.getInputs().get(0).getValue();
+ }
+ UnnestMapOperator unnestMap = (UnnestMapOperator) child;
+ propagateFilterInSecondaryUnnsetMap(unnestMap, filterType, context);
+ setPrimaryFilterVar(primaryOp, unnestMap.getPropagateIndexMinFilterVar(),
+ unnestMap.getPropagateIndexMaxFilterVar(), context);
+ }
+ break;
case ORDER:
- ILogicalOperator child = intersectOrSort.getValue().getInputs().get(0).getValue();
+ ILogicalOperator child = intersectOrSortOrSplit.getValue().getInputs().get(0).getValue();
if (child.getOperatorTag().equals(LogicalOperatorTag.UNNEST_MAP)) {
UnnestMapOperator secondaryMap = (UnnestMapOperator) child;
@@ -283,9 +300,10 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
secondaryMap.getPropagateIndexMaxFilterVar(), context);
}
break;
+
default:
throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
- intersectOrSort.getValue().getOperatorTag().toString());
+ intersectOrSortOrSplit.getValue().getOperatorTag().toString());
}
}
}
@@ -337,6 +355,7 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
IOptimizationContext context, IAType filterType) throws AlgebricksException {
List<UnnestMapOperator> primaryUnnestMapOps = new ArrayList<>();
boolean hasSecondaryIndexMap = false;
+ boolean isIndexOnlyPlan = false;
Queue<Mutable<ILogicalOperator>> queue = new LinkedList<>(op.getInputs());
while (!queue.isEmpty()) {
ILogicalOperator descendantOp = queue.poll().getValue();
@@ -356,6 +375,7 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
primaryUnnestMapOps.add(unnestMapOp);
} else {
hasSecondaryIndexMap = true;
+ isIndexOnlyPlan = unnestMapOp.getGenerateCallBackProceedResultVar();
}
}
}
@@ -363,7 +383,7 @@ public class IntroduceLSMComponentFilterRule implements IAlgebraicRewriteRule {
queue.addAll(descendantOp.getInputs());
}
if (hasSecondaryIndexMap && !primaryUnnestMapOps.isEmpty()) {
- propagateFilterToPrimaryIndex(primaryUnnestMapOps, filterType, context);
+ propagateFilterToPrimaryIndex(primaryUnnestMapOps, filterType, context, isIndexOnlyPlan);
}
}
http://git-wip-us.apache.org/repos/asf/asterixdb/blob/c3c23574/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroducePrimaryIndexForAggregationRule.java
----------------------------------------------------------------------
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroducePrimaryIndexForAggregationRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroducePrimaryIndexForAggregationRule.java
index 7b8b906..5eb0fc6 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroducePrimaryIndexForAggregationRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroducePrimaryIndexForAggregationRule.java
@@ -233,7 +233,7 @@ public class IntroducePrimaryIndexForAggregationRule implements IAlgebraicRewrit
AbstractUnnestMapOperator primaryIndexUnnestOperator =
(AbstractUnnestMapOperator) AccessMethodUtils.createSecondaryIndexUnnestMap(dataset, recordType,
metaRecordType, primaryIndex, scanOperator.getInputs().get(0).getValue(),
- newBTreeParameters, context, true, retainInput, false);
+ newBTreeParameters, context, retainInput, false, false);
// re-use the PK variables of the original scan operator
primaryIndexUnnestOperator.getVariables().clear();
http://git-wip-us.apache.org/repos/asf/asterixdb/blob/c3c23574/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceSelectAccessMethodRule.java
----------------------------------------------------------------------
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceSelectAccessMethodRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceSelectAccessMethodRule.java
index d0e973f..27e5645 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceSelectAccessMethodRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/IntroduceSelectAccessMethodRule.java
@@ -26,6 +26,8 @@ import java.util.Optional;
import java.util.TreeMap;
import org.apache.asterix.algebra.operators.CommitOperator;
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
import org.apache.asterix.metadata.declared.MetadataProvider;
import org.apache.asterix.metadata.entities.Index;
import org.apache.commons.lang3.mutable.Mutable;
@@ -51,30 +53,62 @@ import org.apache.hyracks.algebricks.core.algebra.operators.logical.SelectOperat
import org.apache.hyracks.algebricks.core.algebra.util.OperatorPropertiesUtil;
/**
- * This rule optimizes simple selections with secondary or primary indexes. The use of an
- * index is expressed as an unnest-map over an index-search function which will be
+ * This rule optimizes simple selections with secondary or primary indexes.
+ * The use of an index is expressed as an UNNESTMAP operator over an index-search function which will be
* replaced with the appropriate embodiment during codegen.
- * Matches the following operator patterns:
- * Standard secondary index pattern:
- * There must be at least one assign, but there may be more, e.g., when matching similarity-jaccard-check().
- * (select) <-- (assign | unnest)+ <-- (datasource scan)
- * Primary index lookup pattern:
- * Since no assign is necessary to get the primary key fields (they are already stored fields in the BTree tuples).
- * (select) <-- (datasource scan)
- * Replaces the above patterns with this plan:
- * (select) <-- (assign) <-- (btree search) <-- (sort) <-- (unnest-map(index search)) <-- (assign)
- * The sort is optional, and some access methods implementations may choose not to sort.
+ * This rule seeks to change the following patterns.
+ * For the secondary-index searches, a SELECT operator is followed by one or more ASSIGN / UNNEST operators.
+ * A DATASOURCE_SCAN operator should be placed before these operators.
+ * For the primary-index search, a SELECT operator is followed by DATASOURE_SCAN operator since no ASSIGN / UNNEST
+ * operator is required to get the primary key fields (they are already stored fields in the BTree tuples).
+ * If the above pattern is found, this rule replaces the pattern with the following pattern.
+ * If the given plan is both a secondary-index search and an index-only plan, it builds two paths.
+ * The left path has a UNIONALL operator at the top. And the original SELECT operator is followed. Also, the original
+ * ASSIGN / UNNEST operators are followed. Then, UNNEST-MAP for the primary-index-search is followed
+ * to fetch the record. Before that, a SPLIT operator is introduced. Before this, an UNNEST-MAP for
+ * the secondary-index-search is followed to conduct a secondary-index search. The search key (original ASSIGN/UNNEST)
+ * to the secondary-index-search (UNNEST-MAP) is placed before that.
+ * The right path has the above UNIONALL operator at the top. Then, possibly has optional SELECT and/or ASSIGN/UNNEST
+ * operators for the composite BTree or RTree search cases. Then, the above SPLIT operator is followed. Before the SPLIT
+ * operator, it shares the same operators with the left path.
+ * To be qualified as an index-only plan, there are two conditions.
+ * 1) Search predicate can be covered by a secondary index-search.
+ * 2) there are only PK and/or SK fields in the return clause.
+ * If the given query satisfies the above conditions, we call it an index-only plan.
+ * The benefit of the index-only plan is that we don't need to traverse the primary index
+ * after fetching SK, PK entries from a secondary index.
+ * The index-only plan works as follows.
+ * 1) During a secondary-index search, after fetching <SK, PK> pair that satisfies the given predicate,
+ * we try to get an instantTryLock on PK to verify that <SK, PK> is a valid pair.
+ * If it succeeds, the given <SK, PK> pair is trustworthy so that we can return this as a valid output.
+ * This tuple goes to the right path of UNIONALL operator since we don't need to traverse the primary index.
+ * If instantTryLock on PK fails, an operation on the PK record is ongoing, so we need to traverse
+ * the primary index to fetch the entire record and verify the search predicate. So, this <SK, PK> pair
+ * goes to the left path of UNIONALL operator to traverse the primary index.
+ * In the left path, we fetch the record using the given PK and fetch SK field and does SELECT verification.
+ * 2) A UNIONALL operator combines tuples from the left path and the right path and the rest of the plan continues.
+ * In an index-only plan, sort before the primary index-search is not required since we assume that
+ * the chances that a tuple (<SK, PK> pair) goes into the left path are low.
+ * If the given query plan is not an index-only plan, we call this plan as non-index only plan.
+ * In this case, the original plan will be transformed into the following pattern.
+ * The original SELECT operator is placed at the top. And the original ASSIGN / UNNEST operators are followed.
+ * An UNNEST-MAP that conducts the primary-index-search to fetch the primary keys are placed before that. An ORDER
+ * operator is placed to sort the primary keys before feed them into the primary-index. Then, an UNNEST-MAP is followed
+ * to conduct a secondary-index search. Then, the search key (ASSIGN / UNNEST) is followed.
+ * In this case, the sort is optional, and some access methods implementations may choose not to sort.
* Note that for some index-based optimizations we do not remove the triggering
* condition from the select, since the index may only acts as a filter, and the
* final verification must still be done with the original select condition.
* The basic outline of this rule is:
* 1. Match operator pattern.
* 2. Analyze select condition to see if there are optimizable functions (delegated to IAccessMethods).
- * 3. Check metadata to see if there are applicable indexes.
- * 4. Choose an index to apply (for now only a single index will be chosen).
- * 5. Rewrite plan using index (delegated to IAccessMethods).
- * If there are multiple secondary index access path available, we will use the intersection operator to get the
- * intersected primary key from all the secondary indexes. The detailed documentation is here
+ * 3. Check meta-data to see if there are applicable indexes.
+ * 4. Choose an index (or more indexes) to apply.
+ * 5. Rewrite the plan using index(es) (delegated to IAccessMethods).
+ * If multiple secondary index access paths are available, the optimizer uses the intersection operator
+ * to get the intersected primary key from all the chosen secondary indexes. In this case, we don't check
+ * whether the given plan is an index-only plan.
+ * The detailed documentation of intersecting multiple secondary indexes is here:
* https://cwiki.apache.org/confluence/display/ASTERIXDB/Intersect+multiple+secondary+index
*/
public class IntroduceSelectAccessMethodRule extends AbstractIntroduceAccessMethodRule {
@@ -169,26 +203,15 @@ public class IntroduceSelectAccessMethodRule extends AbstractIntroduceAccessMeth
}
/**
- * Construct all applicable secondary index-based access paths in the given selection plan and
- * intersect them using INTERSECT operator to guide to the common primary index search.
- * In case where the applicable index is one, we only construct one path.
+ * Constructs all applicable secondary index-based access paths in the given selection plan and
+ * intersects them using INTERSECT operator to guide to the common primary-index search.
+ * This method assumes that there are two or more secondary indexes in the given path.
*/
private boolean intersectAllSecondaryIndexes(List<Pair<IAccessMethod, Index>> chosenIndexes,
Map<IAccessMethod, AccessMethodAnalysisContext> analyzedAMs, IOptimizationContext context)
throws AlgebricksException {
- Pair<IAccessMethod, Index> chosenIndex = null;
- Optional<Pair<IAccessMethod, Index>> primaryIndex =
- chosenIndexes.stream().filter(pair -> pair.second.isPrimaryIndex()).findFirst();
if (chosenIndexes.size() == 1) {
- chosenIndex = chosenIndexes.get(0);
- } else if (primaryIndex.isPresent()) {
- // one primary + secondary indexes, choose the primary index directly.
- chosenIndex = primaryIndex.get();
- }
- if (chosenIndex != null) {
- AccessMethodAnalysisContext analysisCtx = analyzedAMs.get(chosenIndex.first);
- return chosenIndex.first.applySelectPlanTransformation(afterSelectRefs, selectRef, subTree,
- chosenIndex.second, analysisCtx, context);
+ throw CompilationException.create(ErrorCode.CHOSEN_INDEX_COUNT_SHOULD_BE_GREATER_THAN_ONE);
}
// Intersect all secondary indexes, and postpone the primary index search.
@@ -197,12 +220,13 @@ public class IntroduceSelectAccessMethodRule extends AbstractIntroduceAccessMeth
List<ILogicalOperator> subRoots = new ArrayList<>();
for (Pair<IAccessMethod, Index> pair : chosenIndexes) {
AccessMethodAnalysisContext analysisCtx = analyzedAMs.get(pair.first);
- subRoots.add(pair.first.createSecondaryToPrimaryPlan(conditionRef, subTree, null, pair.second, analysisCtx,
+ subRoots.add(pair.first.createIndexSearchPlan(afterSelectRefs, selectRef, conditionRef,
+ subTree.getAssignsAndUnnestsRefs(), subTree, null, pair.second, analysisCtx,
AccessMethodUtils.retainInputs(subTree.getDataSourceVariables(),
subTree.getDataSourceRef().getValue(), afterSelectRefs),
false, subTree.getDataSourceRef().getValue().getInputs().get(0).getValue()
.getExecutionMode() == ExecutionMode.UNPARTITIONED,
- context));
+ context, null));
}
// Connect each secondary index utilization plan to a common intersect operator.
ILogicalOperator primaryUnnestOp = connectAll2ndarySearchPlanWithIntersect(subRoots, context);
@@ -212,6 +236,24 @@ public class IntroduceSelectAccessMethodRule extends AbstractIntroduceAccessMeth
}
/**
+ * Checks whether the primary index exists among the applicable indexes and return it if is exists.
+ *
+ * @param chosenIndexes
+ * @return Pair<IAccessMethod, Index> for the primary index
+ * null otherwise
+ * @throws AlgebricksException
+ */
+ private Pair<IAccessMethod, Index> fetchPrimaryIndexAmongChosenIndexes(
+ List<Pair<IAccessMethod, Index>> chosenIndexes) throws AlgebricksException {
+ Optional<Pair<IAccessMethod, Index>> primaryIndex =
+ chosenIndexes.stream().filter(pair -> pair.second.isPrimaryIndex()).findFirst();
+ if (primaryIndex.isPresent()) {
+ return primaryIndex.get();
+ }
+ return null;
+ }
+
+ /**
* Connect each secondary index utilization plan to a common INTERSECT operator.
*/
private ILogicalOperator connectAll2ndarySearchPlanWithIntersect(List<ILogicalOperator> subRoots,
@@ -352,9 +394,36 @@ public class IntroduceSelectAccessMethodRule extends AbstractIntroduceAccessMeth
}
// Apply plan transformation using chosen index.
- boolean res = intersectAllSecondaryIndexes(chosenIndexes, analyzedAMs, context);
- context.addToDontApplySet(this, selectOp);
+ boolean res;
+
+ // Primary index applicable?
+ Pair<IAccessMethod, Index> chosenPrimaryIndex = fetchPrimaryIndexAmongChosenIndexes(chosenIndexes);
+ if (chosenPrimaryIndex != null) {
+ AccessMethodAnalysisContext analysisCtx = analyzedAMs.get(chosenPrimaryIndex.first);
+ res = chosenPrimaryIndex.first.applySelectPlanTransformation(afterSelectRefs, selectRef, subTree,
+ chosenPrimaryIndex.second, analysisCtx, context);
+ context.addToDontApplySet(this, selectRef.getValue());
+ } else if (chosenIndexes.size() == 1) {
+ // Index-only plan possible?
+ // Gets the analysis context for the given index.
+ AccessMethodAnalysisContext analysisCtx = analyzedAMs.get(chosenIndexes.get(0).first);
+
+ // Finds the field name of each variable in the sub-tree.
+ fillFieldNamesInTheSubTree(subTree);
+
+ // Finally, try to apply plan transformation using chosen index.
+ res = chosenIndexes.get(0).first.applySelectPlanTransformation(afterSelectRefs, selectRef, subTree,
+ chosenIndexes.get(0).second, analysisCtx, context);
+ context.addToDontApplySet(this, selectRef.getValue());
+ } else {
+ // Multiple secondary indexes applicable?
+ res = intersectAllSecondaryIndexes(chosenIndexes, analyzedAMs, context);
+ context.addToDontApplySet(this, selectRef.getValue());
+ }
+ // If the plan transformation is successful, we don't need to traverse
+ // the plan any more, since if there are more SELECT operators, the next
+ // trigger on this plan will find them.
if (res) {
OperatorPropertiesUtil.typeOpRec(opRef, context);
return res;
@@ -366,7 +435,7 @@ public class IntroduceSelectAccessMethodRule extends AbstractIntroduceAccessMeth
afterSelectRefs.add(opRef);
}
- // Clean the path after SELECT operator by removing the current operator in the list.
+ // Cleans the path after SELECT operator by removing the current operator in the list.
afterSelectRefs.remove(opRef);
return false;