in oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java [1078:1229]
private SelectorExecutionPlan getBestSelectorExecutionPlan(
NodeState rootState, FilterImpl filter,
QueryIndexProvider indexProvider, boolean traversalEnabled) {
QueryIndex bestIndex = null;
if (LOG.isDebugEnabled()) {
logDebug("cost using filter " + filter);
}
double bestCost = Double.POSITIVE_INFINITY;
IndexPlan bestPlan = null;
// track similar costs
QueryIndex almostBestIndex = null;
double almostBestCost = Double.POSITIVE_INFINITY;
IndexPlan almostBestPlan = null;
long maxEntryCount = saturatedAdd(offset.orElse(0L), limit.orElse(Long.MAX_VALUE));
// Sort the indexes according to their minimum cost to be able to skip the remaining indexes if the cost of the
// current index is below the minimum cost of the next index.
List<? extends QueryIndex> queryIndexes = MINIMAL_COST_ORDERING
.sortedCopy(indexProvider.getQueryIndexes(rootState));
List<OrderEntry> sortOrder = getSortOrder(filter);
for (int i = 0; i < queryIndexes.size(); i++) {
QueryIndex index = queryIndexes.get(i);
double minCost = index.getMinimumCost();
if (minCost > bestCost) {
if (Math.abs(minCost - bestIndex.getMinimumCost()) < .00001) {
// Continue with cost evaluation if minimum cost of both indexes are same i.e both indexes are on par.
LOG.debug("minCost: {} of index :{} > best Cost: {} from index: {}, but both indexes have same minimum cost - cost evaluation will continue", minCost, index.getIndexName(), bestCost, bestIndex.getIndexName());
} else {
// Stop looking if the minimum cost is higher than the current best cost
LOG.debug("minCost: {} of index :{} < best Cost: {} from index: {}. Further index evaluation will be skipped", minCost, index.getIndexName(), bestCost, bestIndex.getIndexName());
break;
}
}
double cost;
String indexName = index.getIndexName();
IndexPlan indexPlan = null;
if (index instanceof AdvancedQueryIndex) {
AdvancedQueryIndex advIndex = (AdvancedQueryIndex) index;
List<IndexPlan> ipList = advIndex.getPlans(
filter, sortOrder, rootState);
cost = Double.POSITIVE_INFINITY;
for (IndexPlan p : ipList) {
long entryCount = p.getEstimatedEntryCount();
if (p.getSupportsPathRestriction()) {
entryCount = scaleEntryCount(rootState, filter, entryCount);
}
if (sortOrder == null || p.getSortOrder() != null) {
// if the query is unordered, or
// if the query contains "order by" and the index can sort on that,
// then we don't need to read all entries from the index
entryCount = Math.min(maxEntryCount, entryCount);
}
double c = p.getCostPerExecution() + entryCount * p.getCostPerEntry();
if (LOG.isDebugEnabled()) {
String plan = advIndex.getPlanDescription(p, rootState);
String msg = String.format("cost for [%s] of type (%s) with plan [%s] is %1.2f", p.getPlanName(), indexName, plan, c);
logDebug(msg);
}
if (c < bestCost) {
almostBestCost = bestCost;
almostBestIndex = bestIndex;
almostBestPlan = bestPlan;
bestCost = c;
bestIndex = index;
bestPlan = p;
} else if (c - bestCost <= 0.1) {
almostBestCost = c;
almostBestIndex = index;
almostBestPlan = p;
}
}
if (indexPlan != null && indexPlan.getPlanName() != null) {
indexName += "[" + indexPlan.getPlanName() + "]";
}
} else {
cost = index.getCost(filter, rootState);
}
if (LOG.isDebugEnabled()) {
logDebug("cost for " + indexName + " is " + cost);
}
if (cost < 0) {
LOG.error("cost below 0 for " + indexName + " is " + cost);
}
if (cost < bestCost) {
almostBestCost = bestCost;
almostBestIndex = bestIndex;
bestCost = cost;
bestIndex = index;
bestPlan = indexPlan;
} else if (cost - bestCost <= 0.1) {
almostBestCost = cost;
almostBestIndex = index;
}
}
if (LOG.isDebugEnabled() && Math.abs(bestCost - almostBestCost) <= 0.1) {
String msg = (bestPlan != null && almostBestPlan != null) ? String.format("selected index %s with plan %s and %s with plan %s have similar costs %s and %s for query %s - " +
"check query explanation / index definitions",
bestIndex, bestPlan.getPlanName(), almostBestIndex, almostBestPlan.getPlanName(), bestCost, almostBestCost, filter.toString())
:String.format("selected index %s and %s have similar costs %s and %s for query %s - check query explanation / index definitions",
bestIndex, almostBestIndex, bestCost, almostBestCost, filter.toString());
LOG.debug(msg);
}
potentiallySlowTraversalQuery = bestIndex == null;
if (traversalEnabled) {
TraversingIndex traversal = new TraversingIndex();
double cost = traversal.getCost(filter, rootState);
if (LOG.isDebugEnabled()) {
logDebug("cost for " + traversal.getIndexName() + " is " + cost);
}
if (cost < bestCost || bestCost == Double.POSITIVE_INFINITY) {
bestCost = cost;
bestPlan = null;
bestIndex = traversal;
if (potentiallySlowTraversalQuery) {
potentiallySlowTraversalQuery = traversal.isPotentiallySlow(filter, rootState);
}
}
}
if (potentiallySlowTraversalQuery || bestIndex == null) {
// Log warning for fulltext queries without index, since these cannot return results
if(!filter.getFulltextConditions().isEmpty()) {
LOG.warn("Fulltext query without index for filter {}; no results will be returned", filter);
} else {
LOG.debug("no proper index was found for filter {}", filter);
}
StatisticsProvider statisticsProvider = getSettings().getStatisticsProvider();
if (statisticsProvider != null) {
HistogramStats histogram = statisticsProvider.getHistogram(INDEX_UNAVAILABLE, StatsOptions.METRICS_ONLY);
if (histogram != null) {
histogram.update(1);
}
}
}
return new SelectorExecutionPlan(filter.getSelector(), bestIndex,
bestPlan, bestCost);
}