All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oracle.kv.impl.query.compiler.OptRulePushIndexPreds Maven / Gradle / Ivy

/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.query.compiler;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;

import oracle.kv.impl.api.table.IndexImpl;
import oracle.kv.impl.api.table.TableImpl;
import oracle.kv.impl.query.QueryException;
import oracle.kv.impl.query.compiler.Expr.ExprIter;
import oracle.kv.impl.query.compiler.Expr.ExprKind;
import oracle.kv.impl.query.compiler.ExprBaseTable.IndexHint;
import oracle.kv.impl.query.compiler.ExprSFW.FromClause;
import oracle.kv.table.Index;

/**
 * The goal of this optimization rule is convert WHERE predicates into index
 * scan conditions in order to avoing a full table scan.
 *
 * The rule analyses the predicates in a WHERE clause to find, for each index
 * associated with a given table (including the table's primary index), (a) a
 * starting and/or ending key that could be applied to a scan over that index,
 * and (b) predicates that can be evaluated during the index scan from the
 * index columns only, thus filtering the retrieved index keys further.
 *
 * The rule assumes the WHERE-clause expr is in CNF. For each index, it first
 * collects all CNF factors that are "index predicates", i.e., they can be
 * evaluated fully from the index columns only. For example, if the current
 * index is the primary-key index and C1, C2, C3 are the primary-key columns,
 * {@literal "C1 > 10" and "C2 = 3 or C3 < 20"} are primary index preds. Then,
 * for each index column, in the order that these columns appear in the index
 * (or primary key) declaration, the rule looks-for and processes index preds
 * that are comparison preds {@literal (eg, "C1 > 10" is a comparison pred, but
 * "C2 = 3 or C3 < 20" is not)}. The possible outomes of processing an index
 * pred w.r.t. an index column are listed in the PredicateStatus enum
 * below. The rule stops processing the current index as soon as it finds an
 * index column for which there is no equality pred to be pushed to the index.
 *
 * After the rule has analyzed all indexes, it chooses the "best" index to
 * use among the indexes that had something pushed down to them.
 *
 * TODO: need a "good" heuristic to choose the "best" index, as well as
 * a compiler hint or USE INDEX clause to let the user decide.
 */
class OptRulePushIndexPreds {

    // TODO move this to the Optimizer obj, when we have one
    private RuntimeException theException = null;

    private ArrayList theAnalyzers;

    private boolean theCompletePrimaryKey;

    RuntimeException getException() {
        return theException;
    }

    void apply(Expr expr) {

        try {
            applyInternal(expr);
        } catch (RuntimeException e) {
            theException = e;
        }
    }

    void applyInternal(Expr expr) {

        if (expr.getKind() == ExprKind.SFW) {
            boolean applied = applyOnSFW((ExprSFW)expr);
            if (applied) {
                return;
            }
        }

        ExprIter children = expr.getChildren();

        while (children.hasNext()) {
            Expr child = children.next();
            applyInternal(child);
        }
        children.reset();

        if (expr.getKind() == ExprKind.UPDATE_ROW) {

            if (!theCompletePrimaryKey) {
                throw new QueryException(
                    "Multi-row update is not supported. A complete and " +
                    "exact primary key must be specified in the WHERE clause.");
            }

            return;
        }
    }

    boolean applyOnSFW(ExprSFW sfw) {

        FromClause fc = sfw.getFirstFrom();
        ExprBaseTable tableExpr = fc.getTableExpr();

        if (tableExpr == null) {
            return false;
        }

        boolean hasSort = (sfw.hasSort() ||
                           (sfw.hasGroupBy() && sfw.getNumGroupByExprs() > 0));
        String sortingOp = (sfw.hasSort() ? "order-by" : "group-by");

        IndexHint forceIndexHint = tableExpr.getForceIndexHint();

        TableImpl table = tableExpr.getTargetTable();
        int tablePos = tableExpr.getTargetTablePos();
        Map indexes = table.getIndexes();

        theAnalyzers = new ArrayList(1+indexes.size());
        IndexAnalyzer primaryAnalyzer = null;

        /*
         * Try to push predicates in the primary index. Do this even
         * if the query has order-by and the primary index is not the
         * sorting one, because we need to discover if the query has
         * a complete shard key.
         */
        primaryAnalyzer = new IndexAnalyzer(sfw, tableExpr, tablePos,
                                            null/*index*/);
        primaryAnalyzer.analyze();

        theCompletePrimaryKey =
            (primaryAnalyzer.getPrimaryKeys() != null &&
             primaryAnalyzer.getPrimaryKeys().get(0).isComplete());

        /* No reason to continue if the WHERE expr is always false */
        if (primaryAnalyzer.theSFW == null) {
            return true;
        }

        if (theCompletePrimaryKey) {
            sfw.removeSort();
        }

        if (!hasSort ||
            sfw.hasNearPred() ||
            sfw.hasPrimaryIndexBasedSort()) {
            theAnalyzers.add(primaryAnalyzer);
        }

        if (forceIndexHint != null) {

            IndexImpl forcedIndex = forceIndexHint.theIndex;

            if (hasSort &&
                ((sfw.hasPrimaryIndexBasedSort() && forcedIndex != null) ||
                 (sfw.getSortingIndexes() != null &&
                  !sfw.getSortingIndexes().contains(forcedIndex)))) {

                String hintIndex = (forcedIndex == null ?
                                    "primary" :
                                    forcedIndex.getName());

                throw new QueryException(
                    "Cannot perform " + sortingOp + " because the index " +
                    "forced via a hint is not one of the sorting indexes.\n" +
                    "Hint index    : " + hintIndex + "\n",
                    sfw.getLocation());
            }

            IndexAnalyzer analyzer =
                (forcedIndex == null ?
                 primaryAnalyzer :
                 new IndexAnalyzer(sfw, tableExpr, tablePos, forcedIndex));

            if (analyzer != primaryAnalyzer) {
                analyzer.analyze();
            }

            if (analyzer.theSFW == null) {
                return true;
            }

            analyzer.apply();
            return true;
        }

        /*
         * If the query specifies a complete primary key, use the primary
         * index to execute it and remove any order-by.
         */
        if (theCompletePrimaryKey) {
            primaryAnalyzer.apply();
            return true;
        }

        /*
         * If the query specifies a complete shard key, use the primary
         * index to execute it. In this case, if the query has order-by
         * as well and the sorting index is not the primary one, an error
         * is raised.
         */
        if (primaryAnalyzer.hasShardKey()) {

            if (hasSort &&
                !sfw.hasPrimaryIndexBasedSort()) {
                throw new QueryException(
                    "Cannot perform " + sortingOp + " because the query " +
                    "specifies a complete shard key, but the sort cannot " +
                    "be done via the primary index",
                    sfw.getLocation());
            }

            primaryAnalyzer.apply();
            return true;
        }

        /*
         * If the SFW has sorting, scan the table using the index that
         * sorts the rows in the desired order.
         */
        if (sfw.hasPrimaryIndexBasedSort()) {
            primaryAnalyzer.apply();
            return true;
        }

        boolean alwaysFalse = false;

        if (sfw.hasSecondaryIndexBasedSort()) {

            for (IndexImpl index : sfw.getSortingIndexes()) {

                IndexAnalyzer analyzer =
                    new IndexAnalyzer(sfw, tableExpr, tablePos, index);

                theAnalyzers.add(analyzer);
                analyzer.analyze();

                if (analyzer.theSFW == null) {
                    alwaysFalse = true;
                    break;
                }
            }
        } else {
            for (Map.Entry entry : indexes.entrySet()) {

                IndexImpl index = (IndexImpl)entry.getValue();

                IndexAnalyzer analyzer =
                    new IndexAnalyzer(sfw, tableExpr, tablePos, index);

                theAnalyzers.add(analyzer);
                analyzer.analyze();

                if (analyzer.theSFW == null) {
                    alwaysFalse = true;
                    break;
                }
            }
        }

        /*
         * Choose the "best" of the applicable indexes.
         */
        if (!alwaysFalse) {
            chooseIndex();
        }

        return true;
    }

    /**
     * Choose and apply the "best" index among the applicable ones.
     */
    void chooseIndex() {
        IndexAnalyzer bestIndex = Collections.min(theAnalyzers);
        bestIndex.apply();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy