in asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/AbstractIntroduceAccessMethodRule.java [371:590]
public void pruneIndexCandidates(IAccessMethod accessMethod, AccessMethodAnalysisContext analysisCtx,
IOptimizationContext context, IVariableTypeEnvironment typeEnvironment, boolean checkApplicableOnly)
throws AlgebricksException {
Iterator<Map.Entry<Index, List<Pair<Integer, Integer>>>> indexExprAndVarIt =
analysisCtx.getIteratorForIndexExprsAndVars();
boolean hasIndexPreferences = false;
ArrayList<Integer> matchedExpressions = new ArrayList<>();
while (indexExprAndVarIt.hasNext()) {
Map.Entry<Index, List<Pair<Integer, Integer>>> indexExprAndVarEntry = indexExprAndVarIt.next();
Index index = indexExprAndVarEntry.getKey();
IndexType indexType = index.getIndexType();
if (!accessMethod.matchIndexType(indexType)) {
indexExprAndVarIt.remove();
continue;
}
List<List<String>> keyFieldNames = findKeyFieldNames(index);
List<IAType> keyFieldTypes = findKeyTypes(index);
boolean allUsed = true;
int lastFieldMatched = -1;
matchedExpressions.clear();
// Used to keep track of matched expressions (added for prefix search)
int numMatchedKeys = 0;
for (int i = 0; i < keyFieldNames.size(); i++) {
List<String> keyField = keyFieldNames.get(i);
final IAType keyType = keyFieldTypes.get(i);
boolean foundKeyField = false;
Iterator<Pair<Integer, Integer>> exprsAndVarIter = indexExprAndVarEntry.getValue().iterator();
while (exprsAndVarIter.hasNext()) {
final Pair<Integer, Integer> exprAndVarIdx = exprsAndVarIter.next();
final IOptimizableFuncExpr optFuncExpr = analysisCtx.getMatchedFuncExpr(exprAndVarIdx.first);
// If expr is not optimizable by concrete index then remove
// expr and continue.
if (!accessMethod.exprIsOptimizable(index, optFuncExpr, checkApplicableOnly)) {
exprsAndVarIter.remove();
continue;
}
boolean typeMatch = true;
//Prune indexes based on field types
List<IAType> matchedTypes = new ArrayList<>();
//retrieve types of expressions joined/selected with an indexed field
for (int j = 0; j < optFuncExpr.getNumLogicalVars(); j++) {
if (j != exprAndVarIdx.second) {
matchedTypes.add(optFuncExpr.getFieldType(j));
}
}
if (matchedTypes.size() < 2 && optFuncExpr.getNumLogicalVars() == 1) {
matchedTypes
.add((IAType) ExpressionTypeComputer.INSTANCE.getType(optFuncExpr.getConstantExpr(0),
context.getMetadataProvider(), typeEnvironment));
}
//infer type of logicalExpr based on index keyType
matchedTypes.add((IAType) ExpressionTypeComputer.INSTANCE.getType(
optFuncExpr.getLogicalExpr(exprAndVarIdx.second), null, new IVariableTypeEnvironment() {
@Override
public Object getVarType(LogicalVariable var) throws AlgebricksException {
if (var.equals(optFuncExpr.getSourceVar(exprAndVarIdx.second))) {
return keyType;
}
throw new IllegalArgumentException();
}
@Override
public Object getVarType(LogicalVariable var,
List<LogicalVariable> nonMissableVariables,
List<List<LogicalVariable>> correlatedMissableVariableLists,
List<LogicalVariable> nonNullableVariables,
List<List<LogicalVariable>> correlatedNullableVariableLists)
throws AlgebricksException {
if (var.equals(optFuncExpr.getSourceVar(exprAndVarIdx.second))) {
return keyType;
}
throw new IllegalArgumentException();
}
@Override
public void setVarType(LogicalVariable var, Object type) {
throw new IllegalArgumentException();
}
@Override
public Object getType(ILogicalExpression expr) throws AlgebricksException {
return ExpressionTypeComputer.INSTANCE.getType(expr, null, this);
}
@Override
public boolean substituteProducedVariable(LogicalVariable v1, LogicalVariable v2)
throws AlgebricksException {
throw new IllegalArgumentException();
}
}));
//for the case when jaccard similarity is measured between ordered & unordered lists
boolean jaccardSimilarity = optFuncExpr.getFuncExpr().getFunctionIdentifier().getName()
.startsWith("similarity-jaccard-check");
// Full-text search consideration: an (un)ordered list of string type can be compatible with string
// type. i.e. an (un)ordered list can be provided as arguments to a string type field index.
List<IAType> elementTypes = matchedTypes;
if (optFuncExpr.getFuncExpr().getFunctionIdentifier() == BuiltinFunctions.FULLTEXT_CONTAINS
|| optFuncExpr.getFuncExpr()
.getFunctionIdentifier() == BuiltinFunctions.FULLTEXT_CONTAINS_WO_OPTION) {
for (int j = 0; j < matchedTypes.size(); j++) {
if (matchedTypes.get(j).getTypeTag() == ATypeTag.ARRAY
|| matchedTypes.get(j).getTypeTag() == ATypeTag.MULTISET) {
elementTypes.set(j, ((AbstractCollectionType) matchedTypes.get(j)).getItemType());
}
}
}
for (int j = 0; j < matchedTypes.size(); j++) {
for (int k = j + 1; k < matchedTypes.size(); k++) {
if (!ATypeTag.ANY.equals(elementTypes.get(k).getTypeTag())) {
typeMatch &= isMatched(elementTypes.get(j), elementTypes.get(k), jaccardSimilarity);
}
}
}
// Check if any field name in the optFuncExpr matches.
if (typeMatch && optFuncExpr.findFieldName(keyField) != -1
&& optFuncExpr.getOperatorSubTree(exprAndVarIdx.second).hasDataSourceScan()) {
foundKeyField = true;
matchedExpressions.add(exprAndVarIdx.first);
hasIndexPreferences =
hasIndexPreferences || accessMethod.getSecondaryIndexAnnotation(optFuncExpr) != null;
}
}
if (foundKeyField) {
numMatchedKeys++;
if (lastFieldMatched == i - 1) {
lastFieldMatched = i;
}
} else {
allUsed = false;
// if any expression was matched, remove the non-matched expressions, otherwise the index is unusable
if (lastFieldMatched >= 0) {
exprsAndVarIter = indexExprAndVarEntry.getValue().iterator();
while (exprsAndVarIter.hasNext()) {
if (!matchedExpressions.contains(exprsAndVarIter.next().first)) {
exprsAndVarIter.remove();
}
}
}
break;
}
}
// If the access method requires all exprs to be matched but they
// are not, remove this candidate.
if (!allUsed && accessMethod.matchAllIndexExprs(index)) {
indexExprAndVarIt.remove();
continue;
}
// A prefix of the index exprs may have been matched.
if (accessMethod.matchPrefixIndexExprs(index)) {
if (lastFieldMatched < 0) {
indexExprAndVarIt.remove();
continue;
} else if (Index.IndexCategory.of(indexType).equals(Index.IndexCategory.ARRAY)) {
// For array indexes, we cannot make the decision to apply the prefix until we see a conjunct
// conditioning on an array. We should improve using array indexes for queries that don't involve
// the array component in the future.
Index.ArrayIndexDetails arrayIndexDetails = (Index.ArrayIndexDetails) index.getIndexDetails();
int indexOfFirstArrayField = 0;
for (Index.ArrayIndexElement e : arrayIndexDetails.getElementList()) {
if (!e.getUnnestList().isEmpty()) {
break;
}
for (List<String> ignored : e.getProjectList()) {
indexOfFirstArrayField++;
}
}
if (lastFieldMatched < indexOfFirstArrayField) {
indexExprAndVarIt.remove();
continue;
}
}
}
analysisCtx.putNumberOfMatchedKeys(index, numMatchedKeys);
}
if (hasIndexPreferences) {
Map<IOptimizableFuncExpr, Set<Index>> exprAndApplicableIndexes =
createExprAndApplicableIndexesMap(analysisCtx);
// First validate the index preference hints. Warn and remove any inapplicable hints.
boolean annotationRemoved =
warnAndRemoveInapplicableSecondaryIndexHints(exprAndApplicableIndexes, accessMethod, context);
Collection<String> preferredSecondaryIndexNames =
fetchPreferredSecondaryIndexNames(exprAndApplicableIndexes, accessMethod);
if (preferredSecondaryIndexNames != null) {
// If we have preferred indexes then remove all non-preferred indexes.
// Non preferred indexes are (applicableIndexes - preferredIndexes).
removeNonPreferredSecondaryIndexes(analysisCtx, preferredSecondaryIndexNames);
} else if (annotationRemoved && (this instanceof IntroduceJoinAccessMethodRule)) {
// ONE OR MORE INAPPLICABLE ANNOTATIONS HAS BEEN REMOVED AND THERE ARE NO preferredIndexes LEFT.
// IT IS AS IF NO HINT WAS SPECIFIED BY THE USER.
// THIS CODE WILL ONLY BE TRIGGERED FOR JOINS AND NOT FOR SCANS
// TO PRESERVE CURRENT FUNCTIONALITY IN THE ABSENCE OF HINTS.
//
// In the case of joins, we want to REMOVE all applicable indexes from consideration,
// so that the indexnl join will not be applicable.
// RBO will not pick an indexnl join and default to a hash join.
// CBO will enumerate all possible join methods and pick the cheapest one.
//
// In the case of scans, we DO NOT want to REMOVE any applicable indexes from consideration.
// RBO will default to an intersection of all applicable indexes.
// CBO will make a cost based selection and intersection of the chosen indexes.
// We pass in an empty list for preferredIndexNames,
// which means that all applicable indexes are non-preferred and will be removed from consideration.
removeNonPreferredSecondaryIndexes(analysisCtx, Collections.EMPTY_LIST);
}
}
}