http://git-wip-us.apache.org/repos/asf/asterixdb/blob/c3c23574/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java ---------------------------------------------------------------------- diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java index 10630a5..9950e37 100644 --- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java +++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AccessMethodUtils.java @@ -20,8 +20,12 @@ package org.apache.asterix.optimizer.rules.am; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.asterix.algebra.operators.physical.ExternalDataLookupPOperator; @@ -50,11 +54,14 @@ import org.apache.asterix.om.types.ATypeTag; import org.apache.asterix.om.types.BuiltinType; import org.apache.asterix.om.types.IAType; import org.apache.asterix.om.types.hierachy.ATypeHierarchy; +import org.apache.asterix.om.types.hierachy.ATypeHierarchy.TypeCastingMathFunctionType; import org.apache.asterix.om.utils.ConstantExpressionUtil; 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; @@ -68,15 +75,21 @@ import org.apache.hyracks.algebricks.core.algebra.expressions.ScalarFunctionCall import org.apache.hyracks.algebricks.core.algebra.expressions.UnnestingFunctionCallExpression; import org.apache.hyracks.algebricks.core.algebra.expressions.VariableReferenceExpression; import org.apache.hyracks.algebricks.core.algebra.functions.AlgebricksBuiltinFunctions; +import org.apache.hyracks.algebricks.core.algebra.functions.AlgebricksBuiltinFunctions.ComparisonKind; +import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier; import org.apache.hyracks.algebricks.core.algebra.functions.IFunctionInfo; 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.AggregateOperator; +import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator; import org.apache.hyracks.algebricks.core.algebra.operators.logical.GroupByOperator; import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator; 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.SplitOperator; +import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnionAllOperator; import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestMapOperator; import org.apache.hyracks.algebricks.core.algebra.operators.logical.visitors.VariableUtilities; import org.apache.hyracks.algebricks.core.algebra.plan.ALogicalPlanImpl; @@ -89,6 +102,13 @@ import org.apache.hyracks.storage.am.lsm.invertedindex.tokenizers.DelimitedUTF8S */ public class AccessMethodUtils { + // Output variable type from a secondary unnest-map + enum SecondaryUnnestMapOutputVarType { + PRIMARY_KEY, + SECONDARY_KEY, + CONDITIONAL_SPLIT_VAR + } + public static void appendPrimaryIndexTypes(Dataset dataset, IAType itemType, IAType metaItemType, List<Object> target) throws AlgebricksException { ARecordType recordType = (ARecordType) itemType; @@ -292,7 +312,9 @@ public class AccessMethodUtils { * Appends the types of the fields produced by the given secondary index to dest. */ public static void appendSecondaryIndexTypes(Dataset dataset, ARecordType recordType, ARecordType metaRecordType, - Index index, boolean primaryKeysOnly, List<Object> dest) throws AlgebricksException { + Index index, List<Object> dest, boolean requireResultOfInstantTryLock) throws AlgebricksException { + // In case of an inverted-index search, secondary keys will not be generated. + boolean primaryKeysOnly = isInvertedIndex(index); if (!primaryKeysOnly) { switch (index.getIndexType()) { case BTREE: @@ -301,10 +323,6 @@ public class AccessMethodUtils { case RTREE: dest.addAll(KeyFieldTypeUtil.getRTreeIndexKeyTypes(index, recordType, metaRecordType)); break; - case SINGLE_PARTITION_WORD_INVIX: - case SINGLE_PARTITION_NGRAM_INVIX: - case LENGTH_PARTITIONED_NGRAM_INVIX: - case LENGTH_PARTITIONED_WORD_INVIX: default: break; } @@ -316,12 +334,25 @@ public class AccessMethodUtils { } else { dest.addAll(KeyFieldTypeUtil.getPartitoningKeyTypes(dataset, recordType, metaRecordType)); } + + // Adds one more type to apply an index-only plan optimization. + // Currently, we use AINT32 to decode result values for this. + // Refer to appendSecondaryIndexOutputVars() for more details. + if (requireResultOfInstantTryLock) { + dest.add(BuiltinType.AINT32); + } + } + /** + * Creates output variables for the given unnest-map or left-outer-unnestmap operator + * that does a secondary index lookup. + * The order: SK, PK, [Optional: the result of a instantTryLock on PK] + */ public static void appendSecondaryIndexOutputVars(Dataset dataset, ARecordType recordType, - ARecordType metaRecordType, Index index, boolean primaryKeysOnly, IOptimizationContext context, - List<LogicalVariable> dest) throws AlgebricksException { - int numPrimaryKeys = 0; + ARecordType metaRecordType, Index index, IOptimizationContext context, List<LogicalVariable> dest, + boolean requireResultOfInstantTryLock) throws AlgebricksException { + int numPrimaryKeys; if (dataset.getDatasetType() == DatasetType.EXTERNAL) { numPrimaryKeys = IndexingConstants .getRIDSize(((ExternalDatasetDetails) dataset.getDatasetDetails()).getProperties()); @@ -329,33 +360,80 @@ public class AccessMethodUtils { numPrimaryKeys = dataset.getPrimaryKeys().size(); } int numSecondaryKeys = KeyFieldTypeUtil.getNumSecondaryKeys(index, recordType, metaRecordType); - int numVars = (primaryKeysOnly) ? numPrimaryKeys : numPrimaryKeys + numSecondaryKeys; + // In case of an inverted-index search, secondary keys will not be generated. + int numVars = isInvertedIndex(index) ? numPrimaryKeys : numPrimaryKeys + numSecondaryKeys; + + // If it's an index-only plan, add one more variable to put the result of instantTryLock on PK - + // whether this lock can be granted on a primary key. + // If it is granted, then we don't need to do a post verification (select). + // If it is not granted, then we need to do a secondary index lookup, do a primary index lookup, and select. + if (requireResultOfInstantTryLock) { + numVars += 1; + } + for (int i = 0; i < numVars; i++) { dest.add(context.newVar()); } } - public static List<LogicalVariable> getPrimaryKeyVarsFromSecondaryUnnestMap(Dataset dataset, - ILogicalOperator unnestMapOp) { + /** + * Gets the primary key variables from the unnest-map or left-outer-unnest-map operator + * that does a secondary index lookup. + * The order: SK, PK, [Optional: the result of a TryLock on PK] + */ + public static List<LogicalVariable> getKeyVarsFromSecondaryUnnestMap(Dataset dataset, ARecordType recordType, + ARecordType metaRecordType, ILogicalOperator unnestMapOp, Index index, + SecondaryUnnestMapOutputVarType keyVarType) throws AlgebricksException { int numPrimaryKeys; + int numSecondaryKeys = KeyFieldTypeUtil.getNumSecondaryKeys(index, recordType, metaRecordType); if (dataset.getDatasetType() == DatasetType.EXTERNAL) { numPrimaryKeys = IndexingConstants .getRIDSize(((ExternalDatasetDetails) dataset.getDatasetDetails()).getProperties()); } else { numPrimaryKeys = dataset.getPrimaryKeys().size(); } - List<LogicalVariable> primaryKeyVars = new ArrayList<>(); - List<LogicalVariable> sourceVars = null; - - sourceVars = ((AbstractUnnestMapOperator) unnestMapOp).getVariables(); + List<LogicalVariable> keyVars = new ArrayList<>(); + AbstractUnnestMapOperator abstractUnnestMapOp = (AbstractUnnestMapOperator) unnestMapOp; + List<LogicalVariable> sourceVars = abstractUnnestMapOp.getVariables(); + // Assumption: the primary keys are located after the secondary key. + int start; + int stop; + + // If a secondary-index search didn't generate SKs, set it to zero. + // Currently, only an inverted-index search doesn't generate any SKs. + boolean isNgramOrKeywordIndex = isInvertedIndex(index); + if (isNgramOrKeywordIndex) { + numSecondaryKeys = 0; + } - // Assumes the primary keys are located at the end. - int start = sourceVars.size() - numPrimaryKeys; - int stop = sourceVars.size(); + // Fetches keys: type 0 - PK, type 1 - SK, type 2 - the result of instantTryLock() on PK + switch (keyVarType) { + case PRIMARY_KEY: + // Fetches primary keys - the second position + start = numSecondaryKeys; + stop = numSecondaryKeys + numPrimaryKeys; + break; + case SECONDARY_KEY: + // Fetches secondary keys - the first position + start = 0; + stop = numSecondaryKeys; + break; + case CONDITIONAL_SPLIT_VAR: + // Sanity check - the given unnest map should generate this variable. + if (!abstractUnnestMapOp.getGenerateCallBackProceedResultVar()) { + throw CompilationException.create(ErrorCode.CANNOT_GET_CONDITIONAL_SPLIT_KEY_VARIABLE); + } + // Fetches conditional splitter - the last position + start = numSecondaryKeys + numPrimaryKeys; + stop = start + 1; + break; + default: + return Collections.emptyList(); + } for (int i = start; i < stop; i++) { - primaryKeyVars.add(sourceVars.get(i)); + keyVars.add(sourceVars.get(i)); } - return primaryKeyVars; + return keyVars; } public static List<LogicalVariable> getPrimaryKeyVarsFromPrimaryUnnestMap(Dataset dataset, @@ -384,10 +462,9 @@ public class AccessMethodUtils { * * @throws AlgebricksException */ - public static Pair<ILogicalExpression, Boolean> createSearchKeyExpr(Index index, IOptimizableFuncExpr optFuncExpr, - IAType indexedFieldType, OptimizableOperatorSubTree indexSubTree, OptimizableOperatorSubTree probeSubTree) + public static Triple<ILogicalExpression, ILogicalExpression, Boolean> createSearchKeyExpr(Index index, + IOptimizableFuncExpr optFuncExpr, IAType indexedFieldType, OptimizableOperatorSubTree probeSubTree) throws AlgebricksException { - if (probeSubTree == null) { // We are optimizing a selection query. Search key is a constant. // Type Checking and type promotion is done here @@ -396,7 +473,7 @@ public class AccessMethodUtils { //We are looking at a selection case, but using two variables //This means that the second variable comes from a nonPure function call //TODO: Right now we miss on type promotion for nonpure functions - return new Pair<>(new VariableReferenceExpression(optFuncExpr.getLogicalVar(1)), false); + return new Triple<>(new VariableReferenceExpression(optFuncExpr.getLogicalVar(1)), null, false); } ILogicalExpression constantAtRuntimeExpression = optFuncExpr.getConstantExpr(0); @@ -408,25 +485,103 @@ public class AccessMethodUtils { ATypeTag constantValueTag = optFuncExpr.getConstantType(0).getTypeTag(); ATypeTag indexedFieldTypeTag = TypeComputeUtils.getActualType(indexedFieldType).getTypeTag(); - // if the constant type and target type does not match, we do a type conversion - AsterixConstantValue replacedConstantValue = null; // type casting happened from real (FLOAT, DOUBLE) value -> INT value? boolean realTypeConvertedToIntegerType = false; + // constant value after type-casting is applied. + AsterixConstantValue replacedConstantValue = null; + // constant value after type-casting is applied - this value is used only for equality case. + // Refer to the following switch case for more details. + AsterixConstantValue replacedConstantValueForEQCase = null; + // If the constant type and target type does not match, we may need to do a type conversion. if (constantValueTag != indexedFieldTypeTag && constantValue != null) { - try { - replacedConstantValue = ATypeHierarchy.getAsterixConstantValueFromNumericTypeObject( - constantValue.getObject(), indexedFieldTypeTag, index.isEnforced()); - realTypeConvertedToIntegerType = - isRealTypeConvertedToIntegerType(constantValueTag, indexedFieldTypeTag); - } catch (HyracksDataException e) { - throw new AlgebricksException(e); + // To check whether the constant is REAL values, and target field is an INT type field. + // In this case, we need to change the search parameter. Refer to the caller section for the detail. + realTypeConvertedToIntegerType = + isRealTypeConvertedToIntegerType(constantValueTag, indexedFieldTypeTag); + if (realTypeConvertedToIntegerType && !index.isEnforced() && !index.isOverridingKeyFieldTypes()) { + // For the index on a closed-type field, + // 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, the following query, + // + // for $emp in dataset empDataset + // where $emp.age > double("2.3") and $emp.age < double("3.3") + // return $emp.id; + // + // It should generate a result if there is a tuple that satisfies the condition, + // which is 3. However, it does not generate the desired result since finding + // candidates fails after truncating the fraction part + // (there is no INT whose value is greater than 2 and less than 3.) + // + // Thus, + // when converting FLOAT or DOUBLE values, we need to apply ceil() or floor(). + // The following transformation will generate the final result, not a superset of it. + // + // LT + // IntVar < 4.9 ==> round-up: IntVar < 5 + // + // LE + // IntVar <= 4.9 ==> round-down: IntVar <= 4 + // + // GT + // IntVar > 4.9 ==> round-down: IntVar > 4 + // + // GE + // IntVar >= 4.9 ==> round-up: IntVar >= 5 + // + // EQ: two values are required to do a correct type-casting. + // IntVar = 4.3 ==> round-down and round-up: IntVar = 4 and IntVar = 5 : false + // IntVar = 4.0 ==> round-down and round-up: IntVar = 4 and IntVar = 4 : true + FunctionIdentifier functionID = optFuncExpr.getFuncExpr().getFunctionIdentifier(); + ComparisonKind cKind = AlgebricksBuiltinFunctions.getComparisonType(functionID); + + switch (cKind) { + case LT: + case GE: + // round-up + replacedConstantValue = + getReplacedConstantValue(constantValue.getObject(), constantValueTag, + indexedFieldTypeTag, index.isEnforced(), TypeCastingMathFunctionType.CEIL); + break; + case LE: + case GT: + // round-down + replacedConstantValue = + getReplacedConstantValue(constantValue.getObject(), constantValueTag, + indexedFieldTypeTag, index.isEnforced(), TypeCastingMathFunctionType.FLOOR); + break; + case EQ: + // equality case - both CEIL and FLOOR need to be applied. + replacedConstantValue = + getReplacedConstantValue(constantValue.getObject(), constantValueTag, + indexedFieldTypeTag, index.isEnforced(), TypeCastingMathFunctionType.FLOOR); + replacedConstantValueForEQCase = + getReplacedConstantValue(constantValue.getObject(), constantValueTag, + indexedFieldTypeTag, index.isEnforced(), TypeCastingMathFunctionType.CEIL); + break; + default: + // NEQ should not be a case. + throw new IllegalStateException(); + } + } else if (!realTypeConvertedToIntegerType) { + // Type conversion only case: (e.g., INT -> BIGINT) + replacedConstantValue = getReplacedConstantValue(constantValue.getObject(), constantValueTag, + indexedFieldTypeTag, index.isEnforced(), TypeCastingMathFunctionType.NONE); } } - - return replacedConstantValue != null - ? new Pair<>(new ConstantExpression(replacedConstantValue), realTypeConvertedToIntegerType) - : new Pair<>(constantAtRuntimeExpression, false); + // No type-casting at all + if (replacedConstantValue == null) { + return new Triple<>(constantAtRuntimeExpression, null, false); + } + // A type-casting happened, but not EQ case + if (replacedConstantValueForEQCase == null) { + return new Triple<>(new ConstantExpression(replacedConstantValue), null, + realTypeConvertedToIntegerType); + } + // A type-casting happened and it's an EQ case. + return new Triple<>(new ConstantExpression(replacedConstantValue), + new ConstantExpression(replacedConstantValueForEQCase), realTypeConvertedToIntegerType); } else { // We are optimizing a join query. Determine which variable feeds the secondary index. OptimizableOperatorSubTree opSubTree0 = optFuncExpr.getOperatorSubTree(0); @@ -445,11 +600,22 @@ public class AccessMethodUtils { TypeCastUtils.setRequiredAndInputTypes(castFunc, indexedFieldType, probeType); boolean realTypeConvertedToIntegerType = isRealTypeConvertedToIntegerType(probeTypeTypeTag, indexedFieldTypeTag); - return new Pair<>(castFunc, realTypeConvertedToIntegerType); + return new Triple<>(castFunc, null, realTypeConvertedToIntegerType); } } + return new Triple<>(probeExpr, null, false); + } + } - return new Pair<>(probeExpr, false); + private static AsterixConstantValue getReplacedConstantValue(IAObject sourceObject, ATypeTag sourceTypeTag, + ATypeTag targetTypeTag, boolean strictDemote, TypeCastingMathFunctionType mathFunction) + throws CompilationException { + try { + return ATypeHierarchy.getAsterixConstantValueFromNumericTypeObject(sourceObject, targetTypeTag, + strictDemote, mathFunction); + } catch (HyracksDataException e) { + throw new CompilationException(ErrorCode.ERROR_OCCURRED_BETWEEN_TWO_TYPES_CONVERSION, e, sourceTypeTag, + targetTypeTag); } } @@ -490,10 +656,119 @@ public class AccessMethodUtils { return indexExprs.get(0).second; } + /** + * Checks whether the given function expression can utilize the given index. + * If so, checks the given join plan is an index-only plan or not. + */ + public static boolean setIndexOnlyPlanInfo(List<Mutable<ILogicalOperator>> afterJoinRefs, + Mutable<ILogicalOperator> joinRef, OptimizableOperatorSubTree probeSubTree, + OptimizableOperatorSubTree indexSubTree, Index chosenIndex, AccessMethodAnalysisContext analysisCtx, + IOptimizationContext context, AbstractFunctionCallExpression funcExpr, + List<Pair<FunctionIdentifier, Boolean>> funcIdentifiers) throws AlgebricksException { + // index-only plan possible? + boolean isIndexOnlyPlan = false; + + // Whether a verification (especially for R-Tree case) is required after the 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 except the composite index case. + boolean requireVerificationAfterSIdxSearch = false; + + // Does the given index can cover all search predicates? + boolean doesSIdxSearchCoverAllPredicates = false; + + Pair<Boolean, Boolean> functionFalsePositiveCheck = + AccessMethodUtils.canFunctionGenerateFalsePositiveResultsUsingIndex(funcExpr, funcIdentifiers); + + if (functionFalsePositiveCheck.first) { + requireVerificationAfterSIdxSearch = functionFalsePositiveCheck.second; + } else { + // Function not found? + return false; + } + + Quadruple<Boolean, Boolean, Boolean, Boolean> indexOnlyPlanInfo = new Quadruple<>(isIndexOnlyPlan, false, + requireVerificationAfterSIdxSearch, doesSIdxSearchCoverAllPredicates); + + if (analysisCtx.getIndexDatasetMap().get(chosenIndex).getDatasetType() == DatasetType.INTERNAL) { + AccessMethodUtils.indexOnlyPlanCheck(afterJoinRefs, joinRef, indexSubTree, probeSubTree, chosenIndex, + analysisCtx, context, indexOnlyPlanInfo); + } else { + // We don't consider an index on an external dataset to be an index-only plan. + isIndexOnlyPlan = false; + indexOnlyPlanInfo.setFirst(isIndexOnlyPlan); + } + + analysisCtx.setIndexOnlyPlanInfo(indexOnlyPlanInfo); + + return true; + } + + /** + * Finalizes the index-nested-loop join plan transformation. + */ + public static boolean finalizeJoinPlanTransformation(List<Mutable<ILogicalOperator>> afterJoinRefs, + Mutable<ILogicalOperator> joinRef, OptimizableOperatorSubTree indexSubTree, + AccessMethodAnalysisContext analysisCtx, IOptimizationContext context, boolean isLeftOuterJoin, + boolean hasGroupBy, ILogicalOperator indexSearchOp, LogicalVariable newNullPlaceHolderVar, + Mutable<ILogicalExpression> conditionRef, Dataset dataset) throws AlgebricksException { + ILogicalOperator finalIndexSearchOp = indexSearchOp; + if (isLeftOuterJoin && hasGroupBy) { + ScalarFunctionCallExpression lojFuncExprs = analysisCtx.getLOJIsMissingFuncInGroupBy(); + List<LogicalVariable> lojMissingVariables = new ArrayList<>(); + lojFuncExprs.getUsedVariables(lojMissingVariables); + boolean lojMissingVarExist = false; + if (!lojMissingVariables.isEmpty()) { + lojMissingVarExist = true; + } + + // Resets the missing place holder variable. + AccessMethodUtils.resetLOJMissingPlaceholderVarInGroupByOp(analysisCtx, newNullPlaceHolderVar, context); + + // For the index-only plan, if newNullPlaceHolderVar is not in the variable map of the union operator + // or if the variable is removed during the above method, we need to refresh the variable mapping in UNION. + finalIndexSearchOp = AccessMethodUtils.resetVariableMappingInUnionOpInIndexOnlyPlan(lojMissingVarExist, + lojMissingVariables, indexSearchOp, afterJoinRefs, context); + } + + boolean isIndexOnlyPlan = analysisCtx.getIndexOnlyPlanInfo().getFirst(); + // If there are any left conditions, add a new select operator on top. + indexSubTree.getDataSourceRef().setValue(finalIndexSearchOp); + if (conditionRef.getValue() != null) { + // If an index-only plan is possible, the whole plan is now changed. + // Replaces the current path with the new index-only plan. + if (isIndexOnlyPlan && dataset.getDatasetType() == DatasetType.INTERNAL) { + // Gets the revised dataSourceRef operator from the secondary index-search. + ILogicalOperator dataSourceRefOp = + AccessMethodUtils.findDataSourceFromIndexUtilizationPlan(finalIndexSearchOp); + if (dataSourceRefOp != null && (dataSourceRefOp.getOperatorTag() == LogicalOperatorTag.UNNEST_MAP + || dataSourceRefOp.getOperatorTag() == LogicalOperatorTag.LEFT_OUTER_UNNEST_MAP)) { + indexSubTree.getDataSourceRef().setValue(dataSourceRefOp); + } + // Replaces the current operator with the newly created UNIONALL operator. + joinRef.setValue(finalIndexSearchOp); + } else { + // Non-index only plan case + indexSubTree.getDataSourceRef().setValue(finalIndexSearchOp); + SelectOperator topSelectOp = new SelectOperator(conditionRef, isLeftOuterJoin, newNullPlaceHolderVar); + topSelectOp.getInputs().add(indexSubTree.getRootRef()); + topSelectOp.setExecutionMode(ExecutionMode.LOCAL); + context.computeAndSetTypeEnvironmentForOperator(topSelectOp); + joinRef.setValue(topSelectOp); + } + } else { + if (finalIndexSearchOp.getOperatorTag() == LogicalOperatorTag.UNIONALL) { + joinRef.setValue(finalIndexSearchOp); + } else { + joinRef.setValue(indexSubTree.getRootRef().getValue()); + } + } + return true; + } + public static ILogicalOperator createSecondaryIndexUnnestMap(Dataset dataset, ARecordType recordType, ARecordType metaRecordType, Index index, ILogicalOperator inputOp, AccessMethodJobGenParams jobGenParams, - IOptimizationContext context, boolean outputPrimaryKeysOnly, boolean retainInput, boolean retainNull) - throws AlgebricksException { + IOptimizationContext context, boolean retainInput, boolean retainNull, + boolean generateInstantTrylockResultFromIndexSearch) throws AlgebricksException { // The job gen parameters are transferred to the actual job gen via the UnnestMapOperator's function arguments. ArrayList<Mutable<ILogicalExpression>> secondaryIndexFuncArgs = new ArrayList<>(); jobGenParams.writeToFuncArgs(secondaryIndexFuncArgs); @@ -501,17 +776,19 @@ public class AccessMethodUtils { List<LogicalVariable> secondaryIndexUnnestVars = new ArrayList<>(); List<Object> secondaryIndexOutputTypes = new ArrayList<>(); // Append output variables/types generated by the secondary-index search (not forwarded from input). - appendSecondaryIndexOutputVars(dataset, recordType, metaRecordType, index, outputPrimaryKeysOnly, context, - secondaryIndexUnnestVars); - appendSecondaryIndexTypes(dataset, recordType, metaRecordType, index, outputPrimaryKeysOnly, - secondaryIndexOutputTypes); + // Output: SK, PK, [Optional: the result of instantTryLock] + appendSecondaryIndexOutputVars(dataset, recordType, metaRecordType, index, context, secondaryIndexUnnestVars, + generateInstantTrylockResultFromIndexSearch); + appendSecondaryIndexTypes(dataset, recordType, metaRecordType, index, secondaryIndexOutputTypes, + generateInstantTrylockResultFromIndexSearch); // An index search is expressed as an unnest over an index-search function. IFunctionInfo secondaryIndexSearch = FunctionUtil.getFunctionInfo(BuiltinFunctions.INDEX_SEARCH); UnnestingFunctionCallExpression secondaryIndexSearchFunc = new UnnestingFunctionCallExpression(secondaryIndexSearch, secondaryIndexFuncArgs); secondaryIndexSearchFunc.setReturnsUniqueValues(true); - // This is the operator that jobgen will be looking for. It contains an unnest function that has all necessary arguments to determine - // which index to use, which variables contain the index-search keys, what is the original dataset, etc. + // This is the operator that jobgen will be looking for. It contains an unnest function that has all + // necessary arguments to determine which index to use, which variables contain the index-search keys, + // what is the original dataset, etc. // Left-outer-join (retainInput and retainNull) case? // Then, we use the LEFT-OUTER-UNNEST-MAP operator instead of unnest-map operator. @@ -520,6 +797,8 @@ public class AccessMethodUtils { LeftOuterUnnestMapOperator secondaryIndexLeftOuterUnnestOp = new LeftOuterUnnestMapOperator( secondaryIndexUnnestVars, new MutableObject<ILogicalExpression>(secondaryIndexSearchFunc), secondaryIndexOutputTypes, true); + secondaryIndexLeftOuterUnnestOp + .setGenerateCallBackProceedResultVar(generateInstantTrylockResultFromIndexSearch); secondaryIndexLeftOuterUnnestOp.getInputs().add(new MutableObject<>(inputOp)); context.computeAndSetTypeEnvironmentForOperator(secondaryIndexLeftOuterUnnestOp); secondaryIndexLeftOuterUnnestOp.setExecutionMode(ExecutionMode.PARTITIONED); @@ -533,6 +812,7 @@ public class AccessMethodUtils { UnnestMapOperator secondaryIndexUnnestOp = new UnnestMapOperator(secondaryIndexUnnestVars, new MutableObject<ILogicalExpression>(secondaryIndexSearchFunc), secondaryIndexOutputTypes, retainInput); + secondaryIndexUnnestOp.setGenerateCallBackProceedResultVar(generateInstantTrylockResultFromIndexSearch); secondaryIndexUnnestOp.getInputs().add(new MutableObject<>(inputOp)); context.computeAndSetTypeEnvironmentForOperator(secondaryIndexUnnestOp); secondaryIndexUnnestOp.setExecutionMode(ExecutionMode.PARTITIONED); @@ -540,12 +820,11 @@ public class AccessMethodUtils { } } - public static AbstractUnnestMapOperator createPrimaryIndexUnnestMap(AbstractDataSourceOperator dataSourceOp, - Dataset dataset, ARecordType recordType, ARecordType metaRecordType, ILogicalOperator inputOp, - IOptimizationContext context, boolean sortPrimaryKeys, boolean retainInput, boolean retainNull, - boolean requiresBroadcast) throws AlgebricksException { - List<LogicalVariable> primaryKeyVars = - AccessMethodUtils.getPrimaryKeyVarsFromSecondaryUnnestMap(dataset, inputOp); + private static AbstractUnnestMapOperator createFinalNonIndexOnlySearchPlan(Dataset dataset, + ILogicalOperator inputOp, IOptimizationContext context, boolean sortPrimaryKeys, boolean retainInput, + boolean retainMissing, boolean requiresBroadcast, List<LogicalVariable> primaryKeyVars, + List<LogicalVariable> primaryIndexUnnestVars, List<Object> primaryIndexOutputTypes) + throws AlgebricksException { // Optionally add a sort on the primary-index keys before searching the primary index. OrderOperator order = null; if (sortPrimaryKeys) { @@ -559,6 +838,493 @@ public class AccessMethodUtils { order.setExecutionMode(ExecutionMode.LOCAL); context.computeAndSetTypeEnvironmentForOperator(order); } + // Creates the primary-index search unnest-map operator. + AbstractUnnestMapOperator primaryIndexUnnestMapOp = createPrimaryIndexUnnestMapOp(dataset, retainInput, + retainMissing, requiresBroadcast, primaryKeyVars, primaryIndexUnnestVars, primaryIndexOutputTypes); + if (sortPrimaryKeys) { + primaryIndexUnnestMapOp.getInputs().add(new MutableObject<ILogicalOperator>(order)); + } else { + primaryIndexUnnestMapOp.getInputs().add(new MutableObject<>(inputOp)); + } + context.computeAndSetTypeEnvironmentForOperator(primaryIndexUnnestMapOp); + primaryIndexUnnestMapOp.setExecutionMode(ExecutionMode.PARTITIONED); + return primaryIndexUnnestMapOp; + } + + private static ILogicalOperator createFinalIndexOnlySearchPlan(List<Mutable<ILogicalOperator>> afterTopOpRefs, + Mutable<ILogicalOperator> topOpRef, Mutable<ILogicalExpression> conditionRef, + List<Mutable<ILogicalOperator>> assignsBeforeTopOpRef, Dataset dataset, ARecordType recordType, + ARecordType metaRecordType, ILogicalOperator inputOp, IOptimizationContext context, boolean retainInput, + boolean retainMissing, boolean requiresBroadcast, Index secondaryIndex, + AccessMethodAnalysisContext analysisCtx, OptimizableOperatorSubTree subTree, + LogicalVariable newMissingPlaceHolderForLOJ, List<LogicalVariable> pkVarsFromSIdxUnnestMapOp, + List<LogicalVariable> primaryIndexUnnestVars, List<Object> primaryIndexOutputTypes) + throws AlgebricksException { + Quadruple<Boolean, Boolean, Boolean, Boolean> indexOnlyPlanInfo = analysisCtx.getIndexOnlyPlanInfo(); + // From now on, we deal with the index-only plan. + // Initializes the information required for the index-only plan optimization. + // Fetches SK variable(s) from the secondary-index search operator. + List<LogicalVariable> skVarsFromSIdxUnnestMap = AccessMethodUtils.getKeyVarsFromSecondaryUnnestMap(dataset, + recordType, metaRecordType, inputOp, secondaryIndex, SecondaryUnnestMapOutputVarType.SECONDARY_KEY); + boolean skFieldUsedAfterTopOp = indexOnlyPlanInfo.getSecond(); + boolean requireVerificationAfterSIdxSearch = indexOnlyPlanInfo.getThird(); + ILogicalOperator assignBeforeTopOp; + UnionAllOperator unionAllOp; + SelectOperator newSelectOpInLeftPath; + SelectOperator newSelectOpInRightPath; + SplitOperator splitOp = null; + // This variable map will be used as input to UNIONALL operator. The form is <left, right, output>. + // In our case, left: instantTryLock fail path, right: instantTryLock success path + List<Triple<LogicalVariable, LogicalVariable, LogicalVariable>> unionVarMap = new ArrayList<>(); + List<LogicalVariable> condSplitVars; + List<LogicalVariable> liveVarsAfterTopOp = new ArrayList<>(); + + // Constructs the variable mapping between newly constructed secondary + // key search (SK, PK) and those in the original plan (datasource scan). + LinkedHashMap<LogicalVariable, LogicalVariable> origVarToSIdxUnnestMapOpVarMap = new LinkedHashMap<>(); + + List<List<String>> chosenIndexFieldNames = secondaryIndex.getKeyFieldNames(); + IndexType idxType = secondaryIndex.getIndexType(); + + // variables used in SELECT or JOIN operator + List<LogicalVariable> usedVarsInTopOp = new ArrayList<>(); + List<LogicalVariable> uniqueUsedVarsInTopOp = new ArrayList<>(); + + // variables used in ASSIGN before SELECT operator + List<LogicalVariable> producedVarsInAssignsBeforeTopOp = new ArrayList<>(); + + // For the index-nested-loop join case, we need to exclude the variables from the left (outer) branch + // when deciding which variables should be propagated via UNIONALL operator. + // This is because these variables are already generated and is not related to the decision + // whether the plan is an index-only plan or not. Only the right (inner) branch matters. + List<LogicalVariable> liveVarsInSubTreeRootOp = new ArrayList<>(); + + // variables used after SELECT or JOIN operator + List<LogicalVariable> usedVarsAfterTopOp = new ArrayList<>(); + List<LogicalVariable> varsTmpList = new ArrayList<>(); + + // If the secondary key field is used after SELECT or JOIN operator (e.g., returning the field value), + // then we need to keep these secondary keys. In case of R-tree index, the result of an R-tree + // index search is an MBR. So, we need to reconstruct original field values from the result if that index + // is on a rectangle or point. + AssignOperator skVarAssignOpInRightPath = null; + List<LogicalVariable> restoredSKVarFromRTree = null; + // Original SK field variable to restored SK field variable in the right path mapping + LinkedHashMap<LogicalVariable, LogicalVariable> origSKFieldVarToNewSKFieldVarMap = new LinkedHashMap<>(); + // Index-only plan consideration for the R-Tree index only: + // Constructs an additional ASSIGN to restore the original secondary key field(s) from + // the results of the secondary index search in case the field is used after SELECT or JOIN operator or + // a verification is required since the given query shape is not RECTANGLE or POINT even though the type of + // index is RECTANGLE or POINT (in this case only, removing false-positive is possible.). + if (idxType == IndexType.RTREE && (skFieldUsedAfterTopOp || requireVerificationAfterSIdxSearch)) { + IOptimizableFuncExpr optFuncExpr = AccessMethodUtils.chooseFirstOptFuncExpr(secondaryIndex, analysisCtx); + int optFieldIdx = AccessMethodUtils.chooseFirstOptFuncVar(secondaryIndex, analysisCtx); + Pair<IAType, Boolean> keyPairType = Index.getNonNullableOpenFieldType(optFuncExpr.getFieldType(optFieldIdx), + optFuncExpr.getFieldName(optFieldIdx), recordType); + if (keyPairType == null) { + return null; + } + // Gets the number of dimensions corresponding to the field indexed by chosenIndex. + IAType spatialType = keyPairType.first; + ArrayList<Mutable<ILogicalExpression>> restoredSKFromRTreeExprs = new ArrayList<>(); + restoredSKVarFromRTree = new ArrayList<>(); + switch (spatialType.getTypeTag()) { + case POINT: + // Reconstructs a POINT value. + AbstractFunctionCallExpression createPointExpr = createPointExpression(skVarsFromSIdxUnnestMap); + restoredSKVarFromRTree.add(context.newVar()); + restoredSKFromRTreeExprs.add(new MutableObject<ILogicalExpression>(createPointExpr)); + skVarAssignOpInRightPath = new AssignOperator(restoredSKVarFromRTree, restoredSKFromRTreeExprs); + break; + case RECTANGLE: + // Reconstructs a RECTANGLE value. + AbstractFunctionCallExpression expr1 = createPointExpression(skVarsFromSIdxUnnestMap.subList(0, 2)); + AbstractFunctionCallExpression expr2 = createPointExpression(skVarsFromSIdxUnnestMap.subList(2, 4)); + AbstractFunctionCallExpression createRectangleExpr = createRectangleExpression(expr1, expr2); + restoredSKVarFromRTree.add(context.newVar()); + restoredSKFromRTreeExprs.add(new MutableObject<ILogicalExpression>(createRectangleExpr)); + skVarAssignOpInRightPath = new AssignOperator(restoredSKVarFromRTree, restoredSKFromRTreeExprs); + break; + default: + break; + } + } + + // Gets all variables from the right (inner) branch. + VariableUtilities.getLiveVariables((ILogicalOperator) subTree.getRootRef().getValue(), liveVarsInSubTreeRootOp); + // Gets the used variables from the SELECT or JOIN operator. + VariableUtilities.getUsedVariables((ILogicalOperator) topOpRef.getValue(), usedVarsInTopOp); + // Excludes the variables in the condition from the outer branch - in join case. + for (Iterator<LogicalVariable> iterator = usedVarsInTopOp.iterator(); iterator.hasNext();) { + LogicalVariable v = iterator.next(); + if (!liveVarsInSubTreeRootOp.contains(v)) { + iterator.remove(); + } + } + // Keeps the unique used variables in the SELECT or JOIN operator. + copyVarsToAnotherList(usedVarsInTopOp, uniqueUsedVarsInTopOp); + + // If there are ASSIGN operators (usually secondary key field) before the given SELECT or JOIN operator, + // we may need to propagate these produced variables via the UNIONALL operator if they are used afterwards. + if (assignsBeforeTopOpRef != null && !assignsBeforeTopOpRef.isEmpty()) { + for (int i = 0; i < assignsBeforeTopOpRef.size(); i++) { + assignBeforeTopOp = assignsBeforeTopOpRef.get(i).getValue(); + varsTmpList.clear(); + VariableUtilities.getProducedVariables(assignBeforeTopOp, varsTmpList); + copyVarsToAnotherList(varsTmpList, producedVarsInAssignsBeforeTopOp); + } + } + + // Adds an optional ASSIGN operator that sits right after the SELECT or JOIN operator. + // This assign operator keeps any constant expression(s) extracted from the original ASSIGN operators + // in the subtree and are used after the SELECT or JOIN operator. In usual case, + // this constant value would be used in a group-by after a left-outer-join and will be removed by the optimizer. + // We need to conduct this since this variable does not have to be in the both branch of an index-only plan. + AssignOperator constAssignOp = null; + ILogicalOperator currentOpAfterTopOp = null; + List<LogicalVariable> constAssignVars = new ArrayList<>(); + List<Mutable<ILogicalExpression>> constAssignExprs = new ArrayList<>(); + ILogicalOperator currentOp = inputOp; + + boolean constantAssignVarUsedInTopOp = false; + if (assignsBeforeTopOpRef != null) { + // From the first ASSIGN (earliest in the plan) to the last ASSGIN (latest) + for (int i = assignsBeforeTopOpRef.size() - 1; i >= 0; i--) { + AssignOperator tmpOp = (AssignOperator) assignsBeforeTopOpRef.get(i).getValue(); + List<LogicalVariable> tmpAssignVars = tmpOp.getVariables(); + List<Mutable<ILogicalExpression>> tmpAsssignExprs = tmpOp.getExpressions(); + Iterator<LogicalVariable> varIt = tmpAssignVars.iterator(); + Iterator<Mutable<ILogicalExpression>> exprIt = tmpAsssignExprs.iterator(); + boolean changed = false; + while (exprIt.hasNext()) { + Mutable<ILogicalExpression> tmpExpr = exprIt.next(); + LogicalVariable tmpVar = varIt.next(); + if (tmpExpr.getValue().getExpressionTag() == LogicalExpressionTag.CONSTANT) { + constAssignVars.add(tmpVar); + constAssignExprs.add(tmpExpr); + varIt.remove(); + exprIt.remove(); + changed = true; + } + } + if (changed) { + context.computeAndSetTypeEnvironmentForOperator(tmpOp); + } + } + + if (!constAssignVars.isEmpty()) { + // These constants should not be used in the SELECT or JOIN operator. + for (LogicalVariable v : constAssignVars) { + if (usedVarsInTopOp.contains(v)) { + constantAssignVarUsedInTopOp = true; + break; + } + } + // If this assign operator is not used in the SELECT or JOIN operator, + // we will add this operator after creating UNION operator in the last part of this method. + constAssignOp = new AssignOperator(constAssignVars, constAssignExprs); + if (constantAssignVarUsedInTopOp) { + // Places this assign after the secondary index-search op. + constAssignOp.getInputs().add(new MutableObject<ILogicalOperator>(inputOp)); + constAssignOp.setExecutionMode(ExecutionMode.PARTITIONED); + context.computeAndSetTypeEnvironmentForOperator(constAssignOp); + currentOp = constAssignOp; + } + } + } + + // variables used after SELECT or JOIN operator + HashSet<LogicalVariable> varsTmpSet = new HashSet<>(); + if (afterTopOpRefs != null) { + for (Mutable<ILogicalOperator> afterTopOpRef : afterTopOpRefs) { + varsTmpSet.clear(); + OperatorPropertiesUtil.getFreeVariablesInOp((ILogicalOperator) afterTopOpRef.getValue(), varsTmpSet); + copyVarsToAnotherList(varsTmpSet, usedVarsAfterTopOp); + } + } + + // Now, adds a SPLIT operator to propagate <SK, PK> pair from the secondary-index search to the two paths. + // And constructs the path from the secondary index search to the SPLIT operator. + + // Fetches the conditional split variable from the secondary-index search + condSplitVars = AccessMethodUtils.getKeyVarsFromSecondaryUnnestMap(dataset, recordType, metaRecordType, inputOp, + secondaryIndex, SecondaryUnnestMapOutputVarType.CONDITIONAL_SPLIT_VAR); + + // Adds a SPLIT operator after the given secondary index-search unnest-map operator. + splitOp = new SplitOperator(2, + new MutableObject<ILogicalExpression>(new VariableReferenceExpression(condSplitVars.get(0)))); + splitOp.getInputs().add(new MutableObject<ILogicalOperator>(currentOp)); + splitOp.setExecutionMode(ExecutionMode.PARTITIONED); + context.computeAndSetTypeEnvironmentForOperator(splitOp); + + // To maintain SSA, we assign new variables for the incoming variables in the left branch + // since the most tuples go to the right branch (instantTryLock success path). Also, the output of + // UNIONALL should be a new variable. (it cannot be the same to the left or right variable.) + + // Original variables (before SPLIT) to the variables in the left path mapping + LinkedHashMap<LogicalVariable, LogicalVariable> liveVarAfterSplitToLeftPathMap = new LinkedHashMap<>(); + // output variables to the variables generated in the left branch mapping + LinkedHashMap<LogicalVariable, LogicalVariable> origPKRecAndSKVarToleftPathMap = new LinkedHashMap<>(); + // Original variables (before SPLIT) to the output variables mapping (mainly for join case) + LinkedHashMap<LogicalVariable, LogicalVariable> origVarToOutputVarMap = new LinkedHashMap<>(); + List<LogicalVariable> liveVarsAfterSplitOp = new ArrayList<>(); + VariableUtilities.getLiveVariables(splitOp, liveVarsAfterSplitOp); + + ArrayList<LogicalVariable> assignVars = new ArrayList<>(); + ArrayList<Mutable<ILogicalExpression>> assignExprs = new ArrayList<>(); + for (LogicalVariable v : liveVarsAfterSplitOp) { + LogicalVariable newVar = context.newVar(); + liveVarAfterSplitToLeftPathMap.put(v, newVar); + assignVars.add(newVar); + assignExprs.add(new MutableObject<ILogicalExpression>(new VariableReferenceExpression(v))); + } + AssignOperator origVarsToLeftPathVarsAssignOp = new AssignOperator(assignVars, assignExprs); + origVarsToLeftPathVarsAssignOp.getInputs().add(new MutableObject<ILogicalOperator>(splitOp)); + context.computeAndSetTypeEnvironmentForOperator(origVarsToLeftPathVarsAssignOp); + origVarsToLeftPathVarsAssignOp.setExecutionMode(ExecutionMode.PARTITIONED); + + // Creates the variable mapping for the UNIONALL operator. + + // PK Variable(s) that will be fed into the primary index-search has been re-assigned in the left path. + List<LogicalVariable> pkVarsInLeftPathFromSIdxSearchBeforeSplit = new ArrayList<>(); + for (LogicalVariable v : pkVarsFromSIdxUnnestMapOp) { + pkVarsInLeftPathFromSIdxSearchBeforeSplit.add(liveVarAfterSplitToLeftPathMap.get(v)); + } + // PK and Record variable(s) from the primary-index search will be reassigned in the left path + // to make the output of the UNIONALL the original variables from the data-scan. + List<LogicalVariable> pkVarsFromPIdxSearchInLeftPath = new ArrayList<>(); + for (int i = 0; i < primaryIndexUnnestVars.size(); i++) { + LogicalVariable replacedVar = context.newVar(); + pkVarsFromPIdxSearchInLeftPath.add(replacedVar); + origPKRecAndSKVarToleftPathMap.put(primaryIndexUnnestVars.get(i), replacedVar); + } + + // Are the used variables after SELECT or JOIN operator from the primary index? + // Then, creates the variable mapping between two paths. + for (LogicalVariable tVar : usedVarsAfterTopOp) { + // Checks whether this variable is already added to the union variable map. + // It should be also a part of the primary key variables. + if (findVarInTripleVarList(unionVarMap, tVar, false) || !primaryIndexUnnestVars.contains(tVar)) { + continue; + } + int pIndexPKIdx = primaryIndexUnnestVars.indexOf(tVar); + // If the above value is -1, either it is a secondary key variable or a variable + // from different branch (join case). These cases will be dealt with later. + if (pIndexPKIdx == -1) { + continue; + } + unionVarMap.add(new Triple<>(pkVarsFromPIdxSearchInLeftPath.get(pIndexPKIdx), + pkVarsFromSIdxUnnestMapOp.get(pIndexPKIdx), tVar)); + origVarToOutputVarMap.put(pkVarsFromSIdxUnnestMapOp.get(pIndexPKIdx), tVar); + + // Constructs the mapping between the PK from the original data-scan to the PK + // from the secondary index search since they are different logical variables. + origVarToSIdxUnnestMapOpVarMap.put(tVar, pkVarsFromSIdxUnnestMapOp.get(pIndexPKIdx)); + } + + // Are the used variables after SELECT or JOIN operator from the given secondary index? + for (LogicalVariable tVar : usedVarsAfterTopOp) { + // Checks whether this variable is already added to the union variable map. + if (findVarInTripleVarList(unionVarMap, tVar, false)) { + continue; + } + // Should be either used in the condition or a composite index field that is not used in the condition. + if (!usedVarsInTopOp.contains(tVar) && !producedVarsInAssignsBeforeTopOp.contains(tVar)) { + continue; + } + int sIndexIdx = chosenIndexFieldNames.indexOf(subTree.getVarsToFieldNameMap().get(tVar)); + // For the join-case, the match might not exist. + // In this case, we just propagate the variables later. + if (sIndexIdx == -1) { + continue; + } + if (idxType == IndexType.RTREE) { + // R-Tree case: we need this variable in case if we need to do an additional verification, + // or the secondary key field is used after SELECT or JOIN operator. + // We need to use the re-constructed secondary key from the result (an MBR) of R-Tree search. + // For the join case, the match might not exist. + // In this case, we just propagate the variables later. + if (!skFieldUsedAfterTopOp && !requireVerificationAfterSIdxSearch) { + continue; + } + LogicalVariable replacedVar = context.newVar(); + origPKRecAndSKVarToleftPathMap.put(tVar, replacedVar); + origSKFieldVarToNewSKFieldVarMap.put(tVar, restoredSKVarFromRTree.get(sIndexIdx)); + unionVarMap.add(new Triple<>(replacedVar, restoredSKVarFromRTree.get(sIndexIdx), tVar)); + continue; + } + // B-Tree case: + LogicalVariable replacedVar = context.newVar(); + origPKRecAndSKVarToleftPathMap.put(tVar, replacedVar); + origVarToOutputVarMap.put(skVarsFromSIdxUnnestMap.get(sIndexIdx), tVar); + unionVarMap.add(new Triple<LogicalVariable, LogicalVariable, LogicalVariable>(replacedVar, + skVarsFromSIdxUnnestMap.get(sIndexIdx), tVar)); + // Constructs the mapping between the SK from the original data-scan + // and the SK from the secondary index search since they are different logical variables. + origVarToSIdxUnnestMapOpVarMap.put(tVar, skVarsFromSIdxUnnestMap.get(sIndexIdx)); + } + + // For R-Tree case: if the given secondary key field variable is used only in the select or join condition, + // we were not able to catch the mapping between the original secondary key field and the newly restored + // secondary key field in the assign operator in the right path. + if (idxType == IndexType.RTREE && (skFieldUsedAfterTopOp || requireVerificationAfterSIdxSearch)) { + for (LogicalVariable v : uniqueUsedVarsInTopOp) { + if (!primaryIndexUnnestVars.contains(v)) { + origSKFieldVarToNewSKFieldVarMap.put(v, restoredSKVarFromRTree.get(0)); + } + } + } + + // For the index-nested-loop join case, + // we propagate all variables that come from the outer relation and are used after join operator. + // Adds the variables that are both live after JOIN and used after the JOIN operator. + VariableUtilities.getLiveVariables((ILogicalOperator) topOpRef.getValue(), liveVarsAfterTopOp); + for (LogicalVariable v : usedVarsAfterTopOp) { + if (!liveVarsAfterTopOp.contains(v) || findVarInTripleVarList(unionVarMap, v, false)) { + continue; + } + LogicalVariable outputVar = context.newVar(); + origVarToOutputVarMap.put(v, outputVar); + unionVarMap.add(new Triple<>(liveVarAfterSplitToLeftPathMap.get(v), v, outputVar)); + } + + // Replaces the original variables in the operators after the SELECT or JOIN operator to satisfy SSA. + if (afterTopOpRefs != null) { + for (Mutable<ILogicalOperator> afterTopOpRef : afterTopOpRefs) { + VariableUtilities.substituteVariables((ILogicalOperator) afterTopOpRef.getValue(), + origVarToOutputVarMap, context); + } + } + + // Creates the primary index lookup operator. + // The job gen parameters are transferred to the actual job gen via the UnnestMapOperator's function arguments. + AbstractUnnestMapOperator primaryIndexUnnestMapOp = createPrimaryIndexUnnestMapOp(dataset, retainInput, + retainMissing, requiresBroadcast, pkVarsInLeftPathFromSIdxSearchBeforeSplit, + pkVarsFromPIdxSearchInLeftPath, primaryIndexOutputTypes); + primaryIndexUnnestMapOp.getInputs().add(new MutableObject<ILogicalOperator>(origVarsToLeftPathVarsAssignOp)); + context.computeAndSetTypeEnvironmentForOperator(primaryIndexUnnestMapOp); + primaryIndexUnnestMapOp.setExecutionMode(ExecutionMode.PARTITIONED); + + // Now, generates the UnionAllOperator to merge the left and right paths. + // If we are transforming a join, in the instantTryLock on PK fail path, a SELECT operator should be + // constructed from the join condition and placed after the primary index lookup + // to do the final verification. If this is a select plan, we just need to use the original + // SELECT operator after the primary index lookup to do the final verification. + LinkedHashMap<LogicalVariable, LogicalVariable> origVarToNewVarInLeftPathMap = new LinkedHashMap<>(); + origVarToNewVarInLeftPathMap.putAll(liveVarAfterSplitToLeftPathMap); + origVarToNewVarInLeftPathMap.putAll(origPKRecAndSKVarToleftPathMap); + ILogicalExpression conditionRefExpr = conditionRef.getValue().cloneExpression(); + // The retainMissing variable contains the information whether we are optimizing a left-outer join or not. + LogicalVariable newMissingPlaceHolderVar = retainMissing ? newMissingPlaceHolderForLOJ : null; + newSelectOpInLeftPath = new SelectOperator(new MutableObject<ILogicalExpression>(conditionRefExpr), + retainMissing, newMissingPlaceHolderVar); + VariableUtilities.substituteVariables(newSelectOpInLeftPath, origVarToNewVarInLeftPathMap, context); + + // If there are ASSIGN operators before the SELECT or JOIN operator, + // we need to put these operators between the SELECT or JOIN and the primary index lookup in the left path. + if (assignsBeforeTopOpRef != null) { + // Makes the primary unnest-map as the child of the last ASSIGN (from top) in the path. + assignBeforeTopOp = assignsBeforeTopOpRef.get(assignsBeforeTopOpRef.size() - 1).getValue(); + assignBeforeTopOp.getInputs().clear(); + assignBeforeTopOp.getInputs().add(new MutableObject<ILogicalOperator>(primaryIndexUnnestMapOp)); + + // Makes the first ASSIGN (from top) as the child of the SELECT operator. + for (int i = assignsBeforeTopOpRef.size() - 1; i >= 0; i--) { + if (assignsBeforeTopOpRef.get(i) != null) { + AbstractLogicalOperator assignTmpOp = + (AbstractLogicalOperator) assignsBeforeTopOpRef.get(i).getValue(); + assignTmpOp.setExecutionMode(ExecutionMode.PARTITIONED); + VariableUtilities.substituteVariables(assignTmpOp, origVarToNewVarInLeftPathMap, context); + context.computeAndSetTypeEnvironmentForOperator(assignTmpOp); + } + } + newSelectOpInLeftPath.getInputs().clear(); + newSelectOpInLeftPath.getInputs() + .add(new MutableObject<ILogicalOperator>(assignsBeforeTopOpRef.get(0).getValue())); + } else { + newSelectOpInLeftPath.getInputs().add(new MutableObject<ILogicalOperator>(primaryIndexUnnestMapOp)); + } + newSelectOpInLeftPath.setExecutionMode(ExecutionMode.PARTITIONED); + context.computeAndSetTypeEnvironmentForOperator(newSelectOpInLeftPath); + + // Now, we take care of the right path (instantTryLock on PK success path). + ILogicalOperator currentTopOpInRightPath = splitOp; + // For an R-Tree index, if there are operators that are using the secondary key field value, + // we need to reconstruct that field value from the result of R-Tree search. + // This is done by adding the following assign operator that we have made in the beginning of this method. + if (skVarAssignOpInRightPath != null) { + skVarAssignOpInRightPath.getInputs().add(new MutableObject<ILogicalOperator>(splitOp)); + skVarAssignOpInRightPath.setExecutionMode(ExecutionMode.PARTITIONED); + context.computeAndSetTypeEnvironmentForOperator(skVarAssignOpInRightPath); + currentTopOpInRightPath = skVarAssignOpInRightPath; + } + + // For an R-Tree index, if the given query shape is not RECTANGLE or POINT, + // we need to add the original SELECT operator to filter out the false positive results. + // (e.g., spatial-intersect($o.pointfield, create-circle(create-point(30.0,70.0), 5.0)) ) + // + // Also, for a B-Tree composite index, we need to apply SELECT operators in the right path + // to remove any false positive results from the secondary composite index search. + // + // Lastly, if there is an index-nested-loop-join and the join contains more conditions + // other than joining fields, then those conditions need to be applied to filter out + // false positive results in the right path. + // (e.g., where $a.authors /*+ indexnl */ = $b.authors and $a.id = $b.id <- authors:SK, id:PK) + if ((idxType == IndexType.RTREE || uniqueUsedVarsInTopOp.size() > 1) && requireVerificationAfterSIdxSearch) { + // Creates a new SELECT operator by deep-copying the SELECT operator in the left path + // since we need to change the variable reference in the SELECT operator. + // For the index-nested-loop join case, we copy the condition of the join operator. + ILogicalExpression conditionRefExpr2 = conditionRef.getValue().cloneExpression(); + newSelectOpInRightPath = new SelectOperator(new MutableObject<ILogicalExpression>(conditionRefExpr2), + retainMissing, newMissingPlaceHolderVar); + newSelectOpInRightPath.getInputs().add(new MutableObject<ILogicalOperator>(currentTopOpInRightPath)); + VariableUtilities.substituteVariables(newSelectOpInRightPath, origVarToSIdxUnnestMapOpVarMap, context); + VariableUtilities.substituteVariables(newSelectOpInRightPath, origSKFieldVarToNewSKFieldVarMap, context); + newSelectOpInRightPath.setExecutionMode(ExecutionMode.PARTITIONED); + context.computeAndSetTypeEnvironmentForOperator(newSelectOpInRightPath); + currentTopOpInRightPath = newSelectOpInRightPath; + } + + // Adds the new missing place holder in case of a left-outer-join if it's not been added yet. + // The assumption here is that this variable is the first PK variable that was set. + if (retainMissing && newMissingPlaceHolderForLOJ == primaryIndexUnnestVars.get(0) + && !findVarInTripleVarList(unionVarMap, newMissingPlaceHolderForLOJ, false)) { + unionVarMap.add(new Triple<>(origPKRecAndSKVarToleftPathMap.get(newMissingPlaceHolderForLOJ), + pkVarsFromSIdxUnnestMapOp.get(0), newMissingPlaceHolderForLOJ)); + } + + // UNIONALL operator that combines both paths. + unionAllOp = new UnionAllOperator(unionVarMap); + unionAllOp.getInputs().add(new MutableObject<ILogicalOperator>(newSelectOpInLeftPath)); + unionAllOp.getInputs().add(new MutableObject<ILogicalOperator>(currentTopOpInRightPath)); + + unionAllOp.setExecutionMode(ExecutionMode.PARTITIONED); + context.computeAndSetTypeEnvironmentForOperator(unionAllOp); + + // If an assign operator that keeps constant values was added, set the UNIONALL operator as its child. + if (!constAssignVars.isEmpty() && !constantAssignVarUsedInTopOp) { + constAssignOp.getInputs().clear(); + constAssignOp.getInputs().add(new MutableObject<ILogicalOperator>(unionAllOp)); + constAssignOp.setExecutionMode(ExecutionMode.PARTITIONED); + context.computeAndSetTypeEnvironmentForOperator(constAssignOp); + + // This constant assign operator is the new child of the first operator after the original + // SELECT or JOIN operator. + currentOpAfterTopOp = afterTopOpRefs.get(afterTopOpRefs.size() - 1).getValue(); + currentOpAfterTopOp.getInputs().clear(); + currentOpAfterTopOp.getInputs().add(new MutableObject<ILogicalOperator>(constAssignOp)); + context.computeAndSetTypeEnvironmentForOperator(currentOpAfterTopOp); + afterTopOpRefs.add(new MutableObject<ILogicalOperator>(constAssignOp)); + } + + // Index-only plan is now constructed. Return this operator to the caller. + return unionAllOp; + } + + private static AbstractUnnestMapOperator createPrimaryIndexUnnestMapOp(Dataset dataset, boolean retainInput, + boolean retainMissing, boolean requiresBroadcast, List<LogicalVariable> primaryKeyVars, + List<LogicalVariable> primaryIndexUnnestVars, List<Object> primaryIndexOutputTypes) + throws AlgebricksException { // The job gen parameters are transferred to the actual job gen via the UnnestMapOperator's function arguments. List<Mutable<ILogicalExpression>> primaryIndexFuncArgs = new ArrayList<>(); BTreeJobGenParams jobGenParams = new BTreeJobGenParams(dataset.getDatasetName(), IndexType.BTREE, @@ -570,22 +1336,17 @@ public class AccessMethodUtils { jobGenParams.setHighKeyVarList(primaryKeyVars, 0, primaryKeyVars.size()); jobGenParams.setIsEqCondition(true); jobGenParams.writeToFuncArgs(primaryIndexFuncArgs); - // Variables and types coming out of the primary-index search. - List<LogicalVariable> primaryIndexUnnestVars = new ArrayList<>(); - List<Object> primaryIndexOutputTypes = new ArrayList<>(); - // Append output variables/types generated by the primary-index search (not forwarded from input). - primaryIndexUnnestVars.addAll(dataSourceOp.getVariables()); - appendPrimaryIndexTypes(dataset, recordType, metaRecordType, primaryIndexOutputTypes); // An index search is expressed as an unnest over an index-search function. IFunctionInfo primaryIndexSearch = FunctionUtil.getFunctionInfo(BuiltinFunctions.INDEX_SEARCH); AbstractFunctionCallExpression primaryIndexSearchFunc = new ScalarFunctionCallExpression(primaryIndexSearch, primaryIndexFuncArgs); - // This is the operator that jobgen will be looking for. It contains an unnest function that has all necessary arguments to determine - // which index to use, which variables contain the index-search keys, what is the original dataset, etc. - AbstractUnnestMapOperator primaryIndexUnnestOp = null; - if (retainNull) { + // This is the operator that jobgen will be looking for. It contains an unnest function that has + // all necessary arguments to determine which index to use, which variables contain the index-search keys, + // what is the original dataset, etc. + AbstractUnnestMapOperator primaryIndexUnnestMapOp = null; + if (retainMissing) { if (retainInput) { - primaryIndexUnnestOp = new LeftOuterUnnestMapOperator(primaryIndexUnnestVars, + primaryIndexUnnestMapOp = new LeftOuterUnnestMapOperator(primaryIndexUnnestVars, new MutableObject<ILogicalExpression>(primaryIndexSearchFunc), primaryIndexOutputTypes, retainInput); } else { @@ -593,29 +1354,98 @@ public class AccessMethodUtils { throw new AlgebricksException("Left-outer-join should propagate all inputs from the outer branch."); } } else { - primaryIndexUnnestOp = new UnnestMapOperator(primaryIndexUnnestVars, + primaryIndexUnnestMapOp = new UnnestMapOperator(primaryIndexUnnestVars, new MutableObject<ILogicalExpression>(primaryIndexSearchFunc), primaryIndexOutputTypes, retainInput); } - // Fed by the order operator or the secondaryIndexUnnestOp. - if (sortPrimaryKeys) { - primaryIndexUnnestOp.getInputs().add(new MutableObject<ILogicalOperator>(order)); + return primaryIndexUnnestMapOp; + } + + /** + * Creates operators that do a primary index lookup in the plan. In case of an index-only plan, + * this creates two paths including the primary index lookup in the left path. + * If this is an index-only plan (only using PK and/or secondary field(s) after SELECT operator) and/or + * the combination of the SELECT (JOIN) condition and the chosen secondary index do not generate + * false positive results, we can apply instantTryLock() on PK optimization since a result from these indexes + * doesn't have to be verified by the primary index-lookup and a subsequent SELECT operator. + * (i.e., we can guarantee the correctness of the result.) + * + * Case A) non-index-only plan + * sidx-search -> (optional) sort -> pdix-search + * + * Case B) index-only plan + * left path (an instantTryLock() on the PK fail path): + * right path(an instantTryLock() on the PK success path): + * (left) sidx-search -> assign? -> split -> primary index-search -> select (verification) -> union -> + * (right) ........................ split -> assign? -> select? -> .......................... union ... + */ + public static ILogicalOperator createRestOfIndexSearchPlan(List<Mutable<ILogicalOperator>> afterTopOpRefs, + Mutable<ILogicalOperator> topOpRef, Mutable<ILogicalExpression> conditionRef, + List<Mutable<ILogicalOperator>> assignsBeforeTopOpRef, AbstractDataSourceOperator dataSourceOp, + Dataset dataset, ARecordType recordType, ARecordType metaRecordType, ILogicalOperator inputOp, + IOptimizationContext context, boolean sortPrimaryKeys, boolean retainInput, boolean retainMissing, + boolean requiresBroadcast, Index secondaryIndex, AccessMethodAnalysisContext analysisCtx, + OptimizableOperatorSubTree subTree, LogicalVariable newMissingPlaceHolderForLOJ) + throws AlgebricksException { + // Common part for the non-index-only plan and index-only plan + // Variables and types for the primary-index search. + List<LogicalVariable> primaryIndexUnnestVars = new ArrayList<>(); + List<Object> primaryIndexOutputTypes = new ArrayList<Object>(); + // Appends output variables/types generated by the primary-index search (not forwarded from input). + primaryIndexUnnestVars.addAll(dataSourceOp.getVariables()); + appendPrimaryIndexTypes(dataset, recordType, metaRecordType, primaryIndexOutputTypes); + + // Fetches PK variable(s) from the secondary-index search operator. + List<LogicalVariable> pkVarsFromSIdxUnnestMapOp = AccessMethodUtils.getKeyVarsFromSecondaryUnnestMap(dataset, + recordType, metaRecordType, inputOp, secondaryIndex, SecondaryUnnestMapOutputVarType.PRIMARY_KEY); + + // Index-only plan or not? + boolean isIndexOnlyPlan = analysisCtx.getIndexOnlyPlanInfo().getFirst(); + + // Non-index-only plan case: creates ORDER -> UNNEST-MAP(Primary-index search) and return that unnest-map op. + if (!isIndexOnlyPlan) { + return createFinalNonIndexOnlySearchPlan(dataset, inputOp, context, sortPrimaryKeys, retainInput, + retainMissing, requiresBroadcast, pkVarsFromSIdxUnnestMapOp, primaryIndexUnnestVars, + primaryIndexOutputTypes); } else { - primaryIndexUnnestOp.getInputs().add(new MutableObject<>(inputOp)); + // Index-only plan case: creates a UNIONALL operator that has two paths after the secondary unnest-map op, + // and returns it. + return createFinalIndexOnlySearchPlan(afterTopOpRefs, topOpRef, conditionRef, assignsBeforeTopOpRef, + dataset, recordType, metaRecordType, inputOp, context, retainInput, retainMissing, + requiresBroadcast, secondaryIndex, analysisCtx, subTree, newMissingPlaceHolderForLOJ, + pkVarsFromSIdxUnnestMapOp, primaryIndexUnnestVars, primaryIndexOutputTypes); } - context.computeAndSetTypeEnvironmentForOperator(primaryIndexUnnestOp); - primaryIndexUnnestOp.setExecutionMode(ExecutionMode.PARTITIONED); - return primaryIndexUnnestOp; + } + + private static AbstractFunctionCallExpression createPointExpression(List<LogicalVariable> pointVars) { + List<Mutable<ILogicalExpression>> expressions = new ArrayList<>(); + AbstractFunctionCallExpression createPointExpr1 = + new ScalarFunctionCallExpression(FunctionUtil.getFunctionInfo(BuiltinFunctions.CREATE_POINT)); + expressions.add(new MutableObject<ILogicalExpression>(new VariableReferenceExpression(pointVars.get(0)))); + expressions.add(new MutableObject<ILogicalExpression>(new VariableReferenceExpression(pointVars.get(1)))); + createPointExpr1.getArguments().addAll(expressions); + return createPointExpr1; + } + + private static AbstractFunctionCallExpression createRectangleExpression( + AbstractFunctionCallExpression createPointExpr1, AbstractFunctionCallExpression createPointExpr2) { + List<Mutable<ILogicalExpression>> expressions = new ArrayList<>(); + AbstractFunctionCallExpression createRectangleExpr = + new ScalarFunctionCallExpression(FunctionUtil.getFunctionInfo(BuiltinFunctions.CREATE_RECTANGLE)); + expressions.add(new MutableObject<ILogicalExpression>(createPointExpr1)); + expressions.add(new MutableObject<ILogicalExpression>(createPointExpr2)); + createRectangleExpr.getArguments().addAll(expressions); + return createRectangleExpr; } public static ScalarFunctionCallExpression findLOJIsMissingFuncInGroupBy(GroupByOperator lojGroupbyOp) throws AlgebricksException { - //find IS_NULL function of which argument has the nullPlaceholder variable in the nested plan of groupby. + //find IS_MISSING function of which argument has the nullPlaceholder variable in the nested plan of groupby. ALogicalPlanImpl subPlan = (ALogicalPlanImpl) lojGroupbyOp.getNestedPlans().get(0); Mutable<ILogicalOperator> subPlanRootOpRef = subPlan.getRoots().get(0); AbstractLogicalOperator subPlanRootOp = (AbstractLogicalOperator) subPlanRootOpRef.getValue(); - boolean foundSelectNonNull = false; - ScalarFunctionCallExpression isNullFuncExpr = null; + boolean foundSelectNonMissing = false; + ScalarFunctionCallExpression isMissingFuncExpr = null; AbstractLogicalOperator inputOp = subPlanRootOp; while (inputOp != null) { if (inputOp.getOperatorTag() == LogicalOperatorTag.SELECT) { @@ -629,11 +1459,11 @@ public class AccessMethodUtils { .getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) { if (((AbstractFunctionCallExpression) notFuncExpr.getArguments().get(0).getValue()) .getFunctionIdentifier().equals(AlgebricksBuiltinFunctions.IS_MISSING)) { - isNullFuncExpr = + isMissingFuncExpr = (ScalarFunctionCallExpression) notFuncExpr.getArguments().get(0).getValue(); - if (isNullFuncExpr.getArguments().get(0).getValue() + if (isMissingFuncExpr.getArguments().get(0).getValue() .getExpressionTag() == LogicalExpressionTag.VARIABLE) { - foundSelectNonNull = true; + foundSelectNonMissing = true; break; } } @@ -645,21 +1475,20 @@ public class AccessMethodUtils { : null; } - if (!foundSelectNonNull) { - throw new AlgebricksException( - "Could not find the non-null select operator in GroupByOperator for LEFTOUTERJOIN plan optimization."); + if (!foundSelectNonMissing) { + throw CompilationException.create(ErrorCode.CANNOT_FIND_NON_MISSING_SELECT_OPERATOR); } - return isNullFuncExpr; + return isMissingFuncExpr; } - public static void resetLOJNullPlaceholderVariableInGroupByOp(AccessMethodAnalysisContext analysisCtx, - LogicalVariable newNullPlaceholderVaraible, IOptimizationContext context) throws AlgebricksException { + public static void resetLOJMissingPlaceholderVarInGroupByOp(AccessMethodAnalysisContext analysisCtx, + LogicalVariable newMissingPlaceholderVaraible, IOptimizationContext context) throws AlgebricksException { - //reset the null placeholder variable in groupby operator - ScalarFunctionCallExpression isNullFuncExpr = analysisCtx.getLOJIsNullFuncInGroupBy(); - isNullFuncExpr.getArguments().clear(); - isNullFuncExpr.getArguments().add( - new MutableObject<ILogicalExpression>(new VariableReferenceExpression(newNullPlaceholderVaraible))); + //reset the missing placeholder variable in groupby operator + ScalarFunctionCallExpression isMissingFuncExpr = analysisCtx.getLOJIsMissingFuncInGroupBy(); + isMissingFuncExpr.getArguments().clear(); + isMissingFuncExpr.getArguments().add( + new MutableObject<ILogicalExpression>(new VariableReferenceExpression(newMissingPlaceholderVaraible))); //recompute type environment. OperatorPropertiesUtil.typeOpRec(analysisCtx.getLOJGroupbyOpRef(), context); @@ -695,11 +1524,11 @@ public class AccessMethodUtils { } public static UnnestMapOperator createExternalDataLookupUnnestMap(AbstractDataSourceOperator dataSourceOp, - Dataset dataset, ARecordType recordType, ILogicalOperator inputOp, IOptimizationContext context, - boolean retainInput, boolean retainNull) throws AlgebricksException { - List<LogicalVariable> primaryKeyVars = - AccessMethodUtils.getPrimaryKeyVarsFromSecondaryUnnestMap(dataset, inputOp); - + Dataset dataset, ARecordType recordType, ARecordType metaRecordType, ILogicalOperator inputOp, + IOptimizationContext context, Index secondaryIndex, boolean retainInput, boolean retainNull) + throws AlgebricksException { + List<LogicalVariable> primaryKeyVars = AccessMethodUtils.getKeyVarsFromSecondaryUnnestMap(dataset, recordType, + metaRecordType, inputOp, secondaryIndex, SecondaryUnnestMapOutputVarType.PRIMARY_KEY); // add a sort on the RID fields before fetching external data. OrderOperator order = new OrderOperator(); for (LogicalVariable pkVar : primaryKeyVars) { @@ -774,4 +1603,845 @@ public class AccessMethodUtils { return usedVars.isEmpty() ? false : true; } + /** + * Checks whether the given function can generate false-positive results when using a corresponding index type. + */ + public static Pair<Boolean, Boolean> canFunctionGenerateFalsePositiveResultsUsingIndex( + AbstractFunctionCallExpression funcExpr, List<Pair<FunctionIdentifier, Boolean>> funcIdents) { + boolean requireVerificationAfterSIdxSearch = true; + + // Check whether the given function-call can generate false positive results. + FunctionIdentifier argFuncIdent = funcExpr.getFunctionIdentifier(); + boolean functionFound = false; + for (int i = 0; i < funcIdents.size(); i++) { + if (argFuncIdent.equals(funcIdents.get(i).first)) { + functionFound = true; + requireVerificationAfterSIdxSearch = funcIdents.get(i).second; + break; + } + } + + // If function-call itself is not an index-based access method, we check its arguments. + if (!functionFound) { + for (Mutable<ILogicalExpression> arg : funcExpr.getArguments()) { + ILogicalExpression argExpr = arg.getValue(); + if (argExpr.getExpressionTag() != LogicalExpressionTag.FUNCTION_CALL) { + continue; + } + AbstractFunctionCallExpression argFuncExpr = (AbstractFunctionCallExpression) argExpr; + FunctionIdentifier argExprFuncIdent = argFuncExpr.getFunctionIdentifier(); + for (int i = 0; i < funcIdents.size(); i++) { + if (argExprFuncIdent.equals(funcIdents.get(i).first)) { + functionFound = true; + requireVerificationAfterSIdxSearch = funcIdents.get(i).second; + break; + } + } + } + } + + return new Pair<>(functionFound, requireVerificationAfterSIdxSearch); + } + + /** + * Checks whether the given plan is an index-only plan (a.k.a. instantTryLock() on PK optimization). + * Refer to the IntroduceSelectAccessMethodRule or IntroduceJoinAccessMethodRule for more details. + * + * @throws AlgebricksException + */ + public static void indexOnlyPlanCheck(List<Mutable<ILogicalOperator>> afterTopRefs, + Mutable<ILogicalOperator> topRef, OptimizableOperatorSubTree indexSubTree, + OptimizableOperatorSubTree probeSubTree, Index chosenIndex, AccessMethodAnalysisContext analysisCtx, + IOptimizationContext context, Quadruple<Boolean, Boolean, Boolean, Boolean> indexOnlyPlanInfo) + throws AlgebricksException { + // First, checks all cases that the index-only can't be applied. If so, we can stop here. + Dataset dataset = indexSubTree.getDataset(); + // For an external dataset, primary index, we don't apply index-only plan optimization. + // For a non-enforced index, we also don't apply index-only plan since it can contain a casted numeric value. + // For an enforced index, we also don't apply the index-only pan since the key field from a secondary index + // may not be equal to the actual value in the record. (e.g., INT index and BIGINT value in the actual record) + // Since index-only plan doesn't access the primary index, we can't get the actual value in this case. + // Also, if no-index-only option is given, we stop here to honor that request. + boolean noIndexOnlyPlanOption = getNoIndexOnlyOption(context); + // TODO: For the inverted index access-method cases only: + // Since an inverted index can contain multiple secondary key entries per one primary key, + // Index-only plan can't be applied. For example, suppose there are two entries (SK1, SK2) for one PK. + // Since we can't access <SK1, PK>, <SK2, PK> at the same time unless we use tryLock (we use instantTryLock), + // right now, we can't support an index-only plan on an inverted index. + // Once this issue is resolved, we can apply an index-only plan. + // One additional condition: + // Even if the above is resolved, if a secondary key field is used after + // SELECT or JOIN operator, this can't be qualified as an index-only plan since + // an inverted index contains a part of a field value, not all of it. + if (dataset.getDatasetType() == DatasetType.EXTERNAL || chosenIndex.isPrimaryIndex() + || chosenIndex.isOverridingKeyFieldTypes() || chosenIndex.isEnforced() || isInvertedIndex(chosenIndex) + || noIndexOnlyPlanOption) { + indexOnlyPlanInfo.setFirst(false); + return; + } + + // index-only plan possible? + boolean isIndexOnlyPlan = false; + + // secondary key field usage after the select (join) operators + // This boolean is mainly used for R-Tree case since R-Tree index generates an MBR + // and we can restore original point or rectangle from this MBR if an index is built on point or rectangle. + boolean secondaryKeyFieldUsedAfterSelectOrJoinOp; + + // Whether a post verification (especially for R-Tree case) is required after the secondary index search + // (e.g., the shape of the given query is not a point or rectangle. + // Then, we may need to apply the select again using the real polygon, not MBR of it to get the true + // result, not a super-set of it.) + boolean requireVerificationAfterSIdxSearch = indexOnlyPlanInfo.getThird(); + + // Does the given index can cover all search predicates? + boolean doesSIdxSearchCoverAllPredicates; + + // matched function expressions + List<IOptimizableFuncExpr> matchedFuncExprs = analysisCtx.getMatchedFuncExprs(); + + // logical variables that select (join) operator is using + List<LogicalVariable> usedVarsInSelJoinOp = new ArrayList<>(); + List<LogicalVariable> usedVarsInSelJoinOpTemp = new ArrayList<>(); + + // live variables that select (join) operator can access + List<LogicalVariable> liveVarsAfterSelJoinOp = new ArrayList<>(); + + // PK, record variable + List<LogicalVariable> dataScanPKRecordVars; + List<LogicalVariable> dataScanPKVars = new ArrayList<>(); + List<LogicalVariable> dataScanRecordVars = new ArrayList<>(); + + // Collects the used variables in the given select (join) operator. + VariableUtilities.getUsedVariables((ILogicalOperator) topRef.getValue(), usedVarsInSelJoinOpTemp); + + // Removes the duplicated variables that are used in the select (join) operator + // in case where the variable is used multiple times in the operator's expression. + // (e.g., $i < 100 and $i > 10) + copyVarsToAnotherList(usedVarsInSelJoinOpTemp, usedVarsInSelJoinOp); + + // If this is a join, we need to traverse the index subtree and find possible SELECT conditions + // since there may be more SELECT conditions and we need to collect used variables. + List<LogicalVariable> selectInIndexSubTreeVars = new ArrayList<>(); + if (probeSubTree != null) { + ILogicalOperator tmpOp = indexSubTree.getRoot(); + while (tmpOp.getOperatorTag() != LogicalOperatorTag.EMPTYTUPLESOURCE) { + if (tmpOp.getOperatorTag() == LogicalOperatorTag.SELECT) { + VariableUtilities.getUsedVariables(tmpOp, selectInIndexSubTreeVars); + // Remove any duplicated variables. + copyVarsToAnotherList(selectInIndexSubTreeVars, usedVarsInSelJoinOp); + selectInIndexSubTreeVars.clear(); + } + tmpOp = tmpOp.getInputs().get(0).getValue(); + } + } + usedVarsInSelJoinOpTemp.clear(); + + // For the index-nested-loop join case, we need to remove variables from the left (outer) relat
<TRUNCATED>