| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.asterix.optimizer.rules.am; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| 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.asterix.optimizer.rules.am.array.IIntroduceAccessMethodRuleLocalRewrite; |
| import org.apache.asterix.optimizer.rules.am.array.MergedSelectRewrite; |
| import org.apache.asterix.optimizer.rules.am.array.SelectFromSubplanRewrite; |
| 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.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; |
| import org.apache.hyracks.algebricks.core.algebra.expressions.VariableReferenceExpression; |
| import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier; |
| 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.DelegateOperator; |
| import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator; |
| import org.apache.hyracks.algebricks.core.algebra.operators.logical.OrderOperator; |
| import org.apache.hyracks.algebricks.core.algebra.operators.logical.SelectOperator; |
| import org.apache.hyracks.algebricks.core.algebra.operators.logical.visitors.VariableUtilities; |
| 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 UNNESTMAP operator over an index-search function which will be |
| * replaced with the appropriate embodiment during codegen. |
| * 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 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 { |
| |
| // Operators representing the patterns to be matched: |
| // These ops are set in matchesPattern() |
| protected Mutable<ILogicalOperator> selectRef = null; |
| protected SelectOperator selectOp = null; |
| protected AbstractFunctionCallExpression selectCond = null; |
| protected IVariableTypeEnvironment typeEnvironment = null; |
| protected final OptimizableOperatorSubTree subTree = new OptimizableOperatorSubTree(); |
| protected List<Mutable<ILogicalOperator>> afterSelectRefs = null; |
| |
| // For plan rewriting to recognize applicable array indexes. |
| private final SelectFromSubplanRewrite selectFromSubplanRewrite = new SelectFromSubplanRewrite(); |
| private final MergedSelectRewrite mergedSelectRewrite = new MergedSelectRewrite(); |
| |
| // Register access methods. |
| protected static Map<FunctionIdentifier, List<IAccessMethod>> accessMethods = new HashMap<>(); |
| |
| static { |
| registerAccessMethod(BTreeAccessMethod.INSTANCE, accessMethods); |
| registerAccessMethod(RTreeAccessMethod.INSTANCE, accessMethods); |
| registerAccessMethod(InvertedIndexAccessMethod.INSTANCE, accessMethods); |
| registerAccessMethod(ArrayBTreeAccessMethod.INSTANCE, accessMethods); |
| for (Pair<FunctionIdentifier, Boolean> f : ArrayBTreeAccessMethod.INSTANCE.getOptimizableFunctions()) { |
| SelectFromSubplanRewrite.addOptimizableFunction(f.first); |
| } |
| } |
| |
| /** |
| * Recursively check the given plan from the root operator to transform a plan |
| * with SELECT operator into an index-utilized plan. |
| */ |
| @Override |
| public boolean rewritePre(Mutable<ILogicalOperator> opRef, IOptimizationContext context) |
| throws AlgebricksException { |
| clear(); |
| setMetadataDeclarations(context); |
| |
| AbstractLogicalOperator op = (AbstractLogicalOperator) opRef.getValue(); |
| |
| // Already checked? |
| if (context.checkIfInDontApplySet(this, op)) { |
| return false; |
| } |
| |
| // We start at the top of the plan. Thus, check whether this operator is the root, |
| // which is DISTRIBUTE_RESULT, SINK, or COMMIT since we start the process from the root operator. |
| if (op.getOperatorTag() != LogicalOperatorTag.DISTRIBUTE_RESULT |
| && op.getOperatorTag() != LogicalOperatorTag.SINK |
| && op.getOperatorTag() != LogicalOperatorTag.DELEGATE_OPERATOR) { |
| return false; |
| } |
| |
| if (op.getOperatorTag() == LogicalOperatorTag.DELEGATE_OPERATOR |
| && !(((DelegateOperator) op).getDelegate() instanceof CommitOperator)) { |
| return false; |
| } |
| |
| afterSelectRefs = new ArrayList<>(); |
| // Recursively check the given plan whether the desired pattern exists in it. |
| // If so, try to optimize the plan. |
| boolean planTransformed = checkAndApplyTheSelectTransformation(opRef, context); |
| |
| if (selectOp != null) { |
| // We found an optimization here. Don't need to optimize this operator again. |
| context.addToDontApplySet(this, selectOp); |
| } |
| |
| if (!planTransformed) { |
| return false; |
| } else { |
| OperatorPropertiesUtil.typeOpRec(opRef, context); |
| } |
| |
| return planTransformed; |
| } |
| |
| /** |
| * Check that the given SELECT condition is a function call. |
| * Call initSubTree() to initialize the optimizable subtree that collects information from |
| * the operators below the given SELECT operator. |
| * In order to transform the given plan, a datasource should be configured |
| * since we are going to transform a datasource into an unnest-map operator. |
| */ |
| protected boolean checkSelectOpConditionAndInitSubTree(IOptimizationContext context) throws AlgebricksException { |
| // Set and analyze select. |
| ILogicalExpression condExpr = selectOp.getCondition().getValue(); |
| typeEnvironment = context.getOutputTypeEnvironment(selectOp); |
| if (condExpr.getExpressionTag() != LogicalExpressionTag.FUNCTION_CALL) { |
| return false; |
| } |
| selectCond = (AbstractFunctionCallExpression) condExpr; |
| |
| // Initialize the subtree information. |
| // Match and put assign, unnest, and datasource information. |
| boolean res = subTree.initFromSubTree(selectOp.getInputs().get(0)); |
| return res && subTree.hasDataSourceScan(); |
| } |
| |
| /** |
| * 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 { |
| if (chosenIndexes.size() == 1) { |
| throw CompilationException.create(ErrorCode.CHOSEN_INDEX_COUNT_SHOULD_BE_GREATER_THAN_ONE); |
| } |
| |
| // Intersect all secondary indexes, and postpone the primary index search. |
| Mutable<ILogicalExpression> conditionRef = selectOp.getCondition(); |
| |
| List<ILogicalOperator> subRoots = new ArrayList<>(); |
| for (Pair<IAccessMethod, Index> pair : chosenIndexes) { |
| AccessMethodAnalysisContext analysisCtx = analyzedAMs.get(pair.first); |
| boolean retainInput = AccessMethodUtils.retainInputs(subTree.getDataSourceVariables(), |
| subTree.getDataSourceRef().getValue(), afterSelectRefs); |
| boolean requiresBroadcast = subTree.getDataSourceRef().getValue().getInputs().get(0).getValue() |
| .getExecutionMode() == ExecutionMode.UNPARTITIONED; |
| ILogicalOperator subRoot = pair.first.createIndexSearchPlan(afterSelectRefs, selectRef, conditionRef, |
| subTree.getAssignsAndUnnestsRefs(), subTree, null, pair.second, analysisCtx, retainInput, false, |
| requiresBroadcast, context, null, null); |
| if (subRoot == null) { |
| return false; |
| } |
| subRoots.add(subRoot); |
| } |
| // Connect each secondary index utilization plan to a common intersect operator. |
| ILogicalOperator primaryUnnestOp = connectAll2ndarySearchPlanWithIntersect(subRoots, context); |
| |
| subTree.getDataSourceRef().setValue(primaryUnnestOp); |
| return primaryUnnestOp != null; |
| } |
| |
| /** |
| * 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, |
| IOptimizationContext context) throws AlgebricksException { |
| ILogicalOperator lop = subRoots.get(0); |
| List<List<LogicalVariable>> inputVars = new ArrayList<>(subRoots.size()); |
| for (int i = 0; i < subRoots.size(); i++) { |
| if (lop.getOperatorTag() != subRoots.get(i).getOperatorTag()) { |
| throw new CompilationException(ErrorCode.COMPILATION_ERROR, lop.getSourceLocation(), |
| "The data source root should have the same operator type."); |
| } |
| if (lop.getInputs().size() != 1) { |
| throw new CompilationException(ErrorCode.COMPILATION_ERROR, lop.getSourceLocation(), |
| "The primary search has multiple inputs."); |
| } |
| |
| ILogicalOperator curRoot = subRoots.get(i); |
| OrderOperator order = (OrderOperator) curRoot.getInputs().get(0).getValue(); |
| List<LogicalVariable> orderedColumn = new ArrayList<>(order.getOrderExpressions().size()); |
| for (Pair<OrderOperator.IOrder, Mutable<ILogicalExpression>> orderExpression : order |
| .getOrderExpressions()) { |
| if (orderExpression.second.getValue().getExpressionTag() != LogicalExpressionTag.VARIABLE) { |
| throw new CompilationException(ErrorCode.COMPILATION_ERROR, |
| orderExpression.second.getValue().getSourceLocation(), |
| "The order by expression should be variables, but they aren't variables."); |
| } |
| VariableReferenceExpression orderedVar = |
| (VariableReferenceExpression) orderExpression.second.getValue(); |
| orderedColumn.add(orderedVar.getVariableReference()); |
| } |
| inputVars.add(orderedColumn); |
| } |
| |
| List<LogicalVariable> inputVars0 = inputVars.get(0); |
| List<LogicalVariable> outputVars = new ArrayList<>(inputVars0.size()); |
| for (LogicalVariable inputVar : inputVars0) { |
| LogicalVariable outputVar = context.newVar(); |
| outputVars.add(outputVar); |
| VariableUtilities.substituteVariables(lop, inputVar, outputVar, context); |
| } |
| |
| IntersectOperator intersect = new IntersectOperator(outputVars, inputVars); |
| intersect.setSourceLocation(lop.getSourceLocation()); |
| for (ILogicalOperator secondarySearch : subRoots) { |
| intersect.getInputs().add(secondarySearch.getInputs().get(0)); |
| } |
| context.computeAndSetTypeEnvironmentForOperator(intersect); |
| lop.getInputs().set(0, new MutableObject<>(intersect)); |
| return lop; |
| } |
| |
| /** |
| * Recursively traverse the given plan and check whether a SELECT operator exists. |
| * If one is found, maintain the path from the root to SELECT operator and |
| * optimize the path from the SELECT operator to the EMPTY_TUPLE_SOURCE operator |
| * if it is not already optimized. |
| */ |
| protected boolean checkAndApplyTheSelectTransformation(Mutable<ILogicalOperator> opRef, |
| IOptimizationContext context) throws AlgebricksException { |
| AbstractLogicalOperator op = (AbstractLogicalOperator) opRef.getValue(); |
| boolean selectFoundAndOptimizationApplied; |
| boolean isSelectOp = false; |
| |
| Mutable<ILogicalOperator> selectRefFromThisOp = null; |
| SelectOperator selectOpFromThisOp = null; |
| |
| // Check the current operator pattern to see whether it is a JOIN or not. |
| if (op.getOperatorTag() == LogicalOperatorTag.SELECT) { |
| selectRef = opRef; |
| selectOp = (SelectOperator) op; |
| selectRefFromThisOp = opRef; |
| selectOpFromThisOp = (SelectOperator) op; |
| isSelectOp = true; |
| } else { |
| // This is not a SELECT operator. Remember this operator. |
| afterSelectRefs.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 select in the path is optimized first. |
| for (Mutable<ILogicalOperator> inputOpRef : op.getInputs()) { |
| selectFoundAndOptimizationApplied = checkAndApplyTheSelectTransformation(inputOpRef, context); |
| if (selectFoundAndOptimizationApplied) { |
| return true; |
| } |
| } |
| |
| // Traverse the plan until we find a SELECT operator. |
| if (isSelectOp) { |
| // Restore the information from this operator since it might have been be set to null |
| // if there are other select operators in the earlier path. |
| selectRef = selectRefFromThisOp; |
| selectOp = selectOpFromThisOp; |
| |
| // Decides the plan transformation check needs to be continued. |
| // This variable is needed since we can't just return false |
| // in order to keep this operator in the afterSelectRefs list. |
| boolean continueCheck = true; |
| |
| // Already checked this SELECT operator? If not, this operator may be optimized. |
| if (context.checkIfInDontApplySet(this, selectOp)) { |
| continueCheck = false; |
| } |
| |
| // For each access method, contains the information about |
| // whether an available index can be applicable or not. |
| Map<IAccessMethod, AccessMethodAnalysisContext> analyzedAMs = null; |
| if (continueCheck) { |
| analyzedAMs = new TreeMap<>(); |
| } |
| |
| if (continueCheck && context.getPhysicalOptimizationConfig().isArrayIndexEnabled() |
| && SelectFromSubplanRewrite.isApplicableForRewriteCursory(metadataProvider, selectOp)) { |
| // If there exists a composite atomic-array index, our conjuncts will be split across multiple |
| // SELECTs. This rewrite is to be used **solely** for the purpose of changing a DATA-SCAN into a |
| // non-index-only plan branch. No nodes introduced from this rewrite will be used beyond this point. |
| if (rewriteLocallyAndTransform(selectRef, context, mergedSelectRewrite)) { |
| return true; |
| } |
| |
| // If there exists a SUBPLAN in our plan, and we are conditioning on a variable, attempt to rewrite |
| // this subplan to allow an array-index AM to be introduced. Again, this rewrite is to be used |
| // **solely** for the purpose of changing a DATA-SCAN into a non-index-only plan branch. |
| if (rewriteLocallyAndTransform(selectRef, context, selectFromSubplanRewrite)) { |
| return true; |
| } |
| } |
| |
| // Check the condition of SELECT 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 whether |
| // the given plan is truly optimizable or not. |
| if (continueCheck && !checkSelectOpConditionAndInitSubTree(context)) { |
| continueCheck = false; |
| } |
| |
| // Analyze the condition of SELECT operator and initialize analyzedAMs. |
| // Check whether the function in the SELECT operator can be truly transformed. |
| if (continueCheck && !analyzeSelectOrJoinOpConditionAndUpdateAnalyzedAM(selectCond, |
| subTree.getAssignsAndUnnests(), analyzedAMs, context, typeEnvironment)) { |
| continueCheck = false; |
| } |
| |
| // Find 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. |
| if (continueCheck && !subTree.setDatasetAndTypeMetadata((MetadataProvider) context.getMetadataProvider())) { |
| continueCheck = false; |
| } |
| |
| if (continueCheck) { |
| // Map variables to the applicable indexes and find the field name and type. |
| // Then find the applicable indexes for the variables used in the SELECT condition. |
| fillSubTreeIndexExprs(subTree, analyzedAMs, context, false); |
| |
| // Prune the access methods based on the function expression and access methods. |
| pruneIndexCandidates(analyzedAMs, context, typeEnvironment); |
| |
| // Choose all indexes that will be applied. |
| List<Pair<IAccessMethod, Index>> chosenIndexes = chooseAllIndexes(analyzedAMs); |
| |
| if (chosenIndexes == null || chosenIndexes.isEmpty()) { |
| // We can't apply any index for this SELECT operator |
| context.addToDontApplySet(this, selectRef.getValue()); |
| return false; |
| } |
| |
| // Apply plan transformation using chosen index. |
| 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; |
| } |
| } |
| |
| selectRef = null; |
| selectOp = null; |
| afterSelectRefs.add(opRef); |
| } |
| |
| // Cleans the path after SELECT operator by removing the current operator in the list. |
| afterSelectRefs.remove(opRef); |
| |
| return false; |
| |
| } |
| |
| @Override |
| public Map<FunctionIdentifier, List<IAccessMethod>> getAccessMethods() { |
| return accessMethods; |
| } |
| |
| private boolean rewriteLocallyAndTransform(Mutable<ILogicalOperator> opRef, IOptimizationContext context, |
| IIntroduceAccessMethodRuleLocalRewrite<SelectOperator> rewriter) throws AlgebricksException { |
| SelectOperator selectRewrite = rewriter.createOperator(selectOp, context); |
| boolean transformationResult = false; |
| if (selectRewrite != null) { |
| Mutable<ILogicalOperator> selectRuleInput = new MutableObject<>(selectRewrite); |
| transformationResult = checkAndApplyTheSelectTransformation(selectRuleInput, context); |
| } |
| |
| // Restore our state, so we can look for more optimizations if this transformation failed. |
| selectOp = rewriter.restoreBeforeRewrite(null, null); |
| selectRef = opRef; |
| return transformationResult; |
| } |
| |
| private void clear() { |
| afterSelectRefs = null; |
| selectRef = null; |
| selectOp = null; |
| selectCond = null; |
| typeEnvironment = null; |
| subTree.reset(); |
| } |
| } |