private SelectorExecutionPlan getBestSelectorExecutionPlan()

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);
    }