private List orderPlansBestToWorst()

in phoenix-core-client/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java [616:754]


    private List<QueryPlan> orderPlansBestToWorst(SelectStatement select, List<QueryPlan> plans, boolean stopAtBestPlan) {
        final QueryPlan dataPlan = plans.get(0);
        if (plans.size() == 1) {
            return plans;
        }

        if (this.costBased) {
            Collections.sort(plans, new Comparator<QueryPlan>() {
                @Override
                public int compare(QueryPlan plan1, QueryPlan plan2) {
                    return plan1.getCost().compareTo(plan2.getCost());
                }
            });
            // Return ordered list based on cost if stats are available; otherwise fall
            // back to static ordering.
            if (!plans.get(0).getCost().isUnknown()) {
                return stopAtBestPlan ? plans.subList(0, 1) : plans;
            }
        }

        /**
         * If we have a plan(s) that are just point lookups (i.e. fully qualified row
         * keys), then favor those first.
         */
        List<QueryPlan> candidates = Lists.newArrayListWithExpectedSize(plans.size());
        if (stopAtBestPlan) { // If we're stopping at the best plan, only consider point lookups if there are any
            for (QueryPlan plan : plans) {
                if (plan.getContext().getScanRanges().isPointLookup()) {
                    candidates.add(plan);
                }
            }
        } else {
            candidates.addAll(plans);
        }
        /**
         * If we have a plan(s) that removes the order by, choose from among these,
         * as this is typically the most expensive operation. Once we have stats, if
         * there's a limit on the query, we might choose a different plan. For example
         * if the limit was a very large number and the combination of applying other 
         * filters on the row key are estimated to choose fewer rows, we'd choose that
         * one.
         */
        List<QueryPlan> stillCandidates = plans;
        List<QueryPlan> bestCandidates = candidates;
        if (!candidates.isEmpty()) {
            stillCandidates = candidates;
            bestCandidates = Lists.<QueryPlan>newArrayListWithExpectedSize(candidates.size());
        }
        for (QueryPlan plan : stillCandidates) {
            // If ORDER BY optimized out (or not present at all)
            if (plan.getOrderBy().getOrderByExpressions().isEmpty()) {
                bestCandidates.add(plan);
            }
        }
        if (bestCandidates.isEmpty()) {
            bestCandidates.addAll(stillCandidates);
        }
        
        int nViewConstants = 0;
        PTable dataTable = dataPlan.getTableRef().getTable();
        if (dataTable.getType() == PTableType.VIEW) {
            for (PColumn column : dataTable.getColumns()) {
                if (column.getViewConstant() != null) {
                    nViewConstants++;
                }
            }
        }
        final int boundRanges = nViewConstants;
        final boolean useDataOverIndexHint = select.getHint().hasHint(Hint.USE_DATA_OVER_INDEX_TABLE);
        final int comparisonOfDataVersusIndexTable = useDataOverIndexHint ? -1 : 1;
        Collections.sort(bestCandidates, new Comparator<QueryPlan>() {

            @Override
            public int compare(QueryPlan plan1, QueryPlan plan2) {
                PTable table1 = plan1.getTableRef().getTable();
                PTable table2 = plan2.getTableRef().getTable();
                int boundCount1 = plan1.getContext().getScanRanges().getBoundPkColumnCount();
                int boundCount2 = plan2.getContext().getScanRanges().getBoundPkColumnCount();
                // For shared indexes (i.e. indexes on views and local indexes),
                // a) add back any view constants as these won't be in the index, and
                // b) ignore the viewIndexId which will be part of the row key columns.
                boundCount1 += table1.getViewIndexId() == null ? 0 : (boundRanges - 1);
                boundCount2 += table2.getViewIndexId() == null ? 0 : (boundRanges - 1);
                // Adjust for salting. Salting adds a bound range for each salt bucket.
                // (but the sum of buckets cover the entire table)
                boundCount1 -= plan1.getContext().getScanRanges().isSalted() ? 1 : 0;
                boundCount2 -= plan2.getContext().getScanRanges().isSalted() ? 1 : 0;
                int c = boundCount2 - boundCount1;
                if (c != 0) return c;
                if (plan1.getGroupBy() != null && plan2.getGroupBy() != null) {
                    if (plan1.getGroupBy().isOrderPreserving() != plan2.getGroupBy().isOrderPreserving()) {
                        return plan1.getGroupBy().isOrderPreserving() ? -1 : 1;
                    }
                }

                // Partial secondary index is preferred
                if (table1.getIndexWhere() != null && table2.getIndexWhere() == null) {
                    return -1;
                }
                if (table1.getIndexWhere() == null && table2.getIndexWhere() != null) {
                    return 1;
                }
                // Use the plan that has fewer "dataColumns" (columns that need to be merged in)
                c = plan1.getContext().getDataColumns().size() - plan2.getContext().getDataColumns().size();
                if (c != 0) return c;

                // Use smaller table (table with fewest kv columns)
                if (!useDataOverIndexHint || (table1.getType() == PTableType.INDEX && table2.getType() == PTableType.INDEX)) {
                    c = (table1.getColumns().size() - table1.getPKColumns().size()) - (table2.getColumns().size() - table2.getPKColumns().size());
                    if (c != 0) return c;
                }

                // If all things are equal, don't choose local index as it forces scan
                // on every region (unless there's no start/stop key)

                if (table1.getIndexType() == IndexType.LOCAL && table2.getIndexType() !=
                        IndexType.LOCAL) {
                    return plan1.getContext().getScanRanges().getRanges().isEmpty() ? -1 : 1;
                }
                if (table2.getIndexType() == IndexType.LOCAL && table1.getIndexType() !=
                        IndexType.LOCAL) {
                    return plan2.getContext().getScanRanges().getRanges().isEmpty() ? 1 : -1;
                }

                // All things being equal, just use the table based on the Hint.USE_DATA_OVER_INDEX_TABLE

                if (table1.getType() == PTableType.INDEX && table2.getType() != PTableType.INDEX) {
                    return -comparisonOfDataVersusIndexTable;
                }
                if (table2.getType() == PTableType.INDEX && table1.getType() != PTableType.INDEX) {
                    return comparisonOfDataVersusIndexTable;
                }
                return 0;
            }
            
        });

        return stopAtBestPlan ? bestCandidates.subList(0, 1) : bestCandidates;
    }