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

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

Go to download

NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.

The newest version!
/*-
 * 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.List;
import java.util.Map;

import oracle.kv.Direction;
import oracle.kv.impl.api.table.FieldDefFactory;
import oracle.kv.impl.api.table.FieldDefImpl;
import oracle.kv.impl.api.table.FieldMap;
import oracle.kv.impl.api.table.FieldValueImpl;
import oracle.kv.impl.api.table.IndexImpl;
import oracle.kv.impl.api.table.IndexImpl.IndexField;
import oracle.kv.impl.api.table.RecordDefImpl;
import oracle.kv.impl.api.table.TableImpl;
import oracle.kv.impl.query.QueryException;
import oracle.kv.impl.query.QueryStateException;
import oracle.kv.impl.query.compiler.ExprVar.VarKind;
import oracle.kv.impl.query.compiler.FunctionLib.FuncCode;
import oracle.kv.impl.query.types.ExprType;
import oracle.kv.impl.query.types.ExprType.Quantifier;
import oracle.kv.impl.query.types.TypeManager;
import oracle.kv.table.Index;

/**
 * Represents a SELECT-FROM-WHERE query block.
 *
 * Implementation of GroubBy:
 *
 * In general, a SFW that includes grouping is implemented with 2 SFW exprs.
 * The lower/inner SFW applies the WHERE clause and does the grouping. Its
 * SELECT lists consists of the grouping exprs, followed by the aggregate
 * functions that appear in the original SELECT list (the SELECT list in the
 * user's query). The upper/outer SFW computes the original SELECT list.
 * For example the query:
 *
 * select a + b, sum(c+d) / count(*)
 * from foo
 * where e > 10
 * group by a, b
 *
 * is rewritten as:
 *
 * select gb-0 + gb-1, aggr-2 / aggr-3
 * from (select a as gb-0, b as gb-1, sum(c+d) as aggr-2, count(*) as aggr-3
 *       from foo
 *       where e > 10
 *       group by a, b)
 *
 * If the original SELECT list is identical to the SELECT list of the inner
 * SFW, then no outer SFW is added.
 *
 * The above rewrite is done during translation time. During distribution time
 * another SFW is added if the query is ALL_PARTITIONS or ALL_SHARDS. This SFW
 * regroups the partial groups that arrive from the RNs. So, the above example
 * will actually look like this:
 *
 * select gb-0 + gb-1, aggr-2 / aggr-3
 * from (select gb-0, gb-1, sum(aggr-2) as aggr-2, sum(aggr-3) as aggr-3
 *       from RCV(select a as gb-0,
 *                       b as gb-1,
 *                       sum(c+d) as aggr-2,
 *                       count(*) as aggr-3
 *                from foo
 *                where e > 10
 *                group by a, b))
 *
 * theFromClauses:
 * There is a FromClause for each table expression appearing in the FROM clause
 * of the actual query block. For now, the first of these table exprs is always
 * the name of a KVS table or a NESTED TABLES clause (see ExprBaseTable). Any
 * exprs following the first one cannot reference any table names (i.e., no
 * joins among tables in different table hierarchies are supported yet)
 *
 * theWhereExpr:
 *
 * theFieldNames:
 *
 * theFieldExprs:
 *
 * theConstructsRecord
 * True if the SELECT clause constructs a record. Normally it is true, but
 * will be false if the SELECT clause contains a single expr with no associated
 * AS clause.
 *
 * theDoNullOnEmpty:
 * Normally, if a SELECT expr returns nothing, NULL is used as the result of
 * that expr. This creates a problem for order-by queries that are run on
 * multiple partitions/shards: Sort exprs are added to the SELECT list (if
 * not there already) of the SFW that produces the query results at each RN.
 * What if a sort expr returns nothing? For sorting, EMPTY and NULL are
 * considered distinct values and EMPTY sorts before NULL. So, we should not
 * convert EMPTY to NULL in this case, because a merge-sort must be done at
 * the client side and this merge sort should see the EMPTY. To support this
 * case, theDoNullOnEmpty is normally true, but is set to false for the SFWs
 * under the RCV, if the query does sorting. The EMPTY values sent by such
 * SFWs are then converted back to NULL by the RCV iter.
 *
 * theNumGroupByExprs:
 * If theNumGroupByExprs is {@literal >=} 0, the SFW is a grouping one. As
 * mentioned above, in this case the grouping exprs appear first in the SELECT
 * list, followed by the aggregate functions. theNumGroupByExprs is the number
 * of the grouping exprs. Because of this canonical form of the grouping SFW,
 * there is no need to implement a class to represent the group-by clause and
 * store the grouping exprs twice.
 *
 * theNeedOuterSFWForGroupBy:
 * A transient member used during translation to determine if an outer SFW is
 * needed. It acts as a global var for the rewriteSelectExprForGroupBy() method
 * and it is set to true by that method, if any of the exprs in the original
 * SELECT list does not match with the corresponding expr in the SELECT list of
 * the inner SFW.
 *
 * theSortExprs:
 *
 * theSortSpecs:
 *
 * theUsePrimaryIndexForSort:
 *
 * theSortingIndexes:
 *
 * theNearPred:
 *
 * theNearPred:
 *
 * theHasNearPred:
 *
 * theOffsetExpr:
 *
 * theLimitExpr:
 */
class ExprSFW extends Expr {

    /**
     * FromClause does not represent the full FROM clause of an SFW expr.
     * Instead, it represents one of the top-level, comma-separated exprs
     * appearing in the actual FROM clause. A FromClause evaluates its
     * associated expr (called the "domain expr") and binds one or more
     * vars to the values produced by the domain expr.
     */
    class FromClause
    {
        /*
         * The "domain expr" of this FromClause. If there is one var associated
         * with the FromClause, the var iterates over the items generated by
         * this expr.
         */
        private Expr theDomainExpr;

        /*
         * The variables defined by this FromClause. Currently, the array
         * will contain more than one variables only if the domain expr is an
         * ExprBaseTable representing a NESTED TABLES clause. In this case, the
         * vars represent the table aliases used in the NESTED TABLES clause and
         * they are listed in the same order as their corresponding aliases in
         * the ExprBaseTable.
         */
        private final ArrayList theVars = new ArrayList();

        /*
         * If, for a table T, the query uses a secondary index I and I is
         * covering or any filtering predictes have been pushed to I, then
         * an "index variable" is created to range over the entries of index
         * I. The filtering preds, if any, are rewritten to access this index
         * var instead of the corresponding table variable. If I is covering,
         * every (sub)expression E which accesses T columns is also rewritten.
         *
         * Currently, only the target table of the query may use a secondary
         * index, so there can be at most one index var. Nevertheless,
         * theIndexVars array mirrors theVars array for convenience and future
         * extensions.
         */
        private final ArrayList theIndexVars =
            new ArrayList();

        FromClause(Expr domExpr, String varName, TableImpl table) {

            theDomainExpr = domExpr;
            theDomainExpr.addParent(ExprSFW.this);

            theVars.add(new ExprVar(theQCB, theSctx, domExpr.getLocation(),
                                    varName, table, this));
            theIndexVars.add(null);
        }

        /*
         * Creates and adds a var after theDomainExpr has already been set.
         * This is the case when theDomainExpr is a NESTED TABLES clause.
         */
        ExprVar addVar(String varName, TableImpl table) {

            assert(table != null);

            ExprVar var = new ExprVar(theQCB, theSctx,
                                      theDomainExpr.getLocation(),
                                      varName, table, this);
            theVars.add(var);
            theIndexVars.add(null);
            return var;
        }

        Expr getDomainExpr() {
            return theDomainExpr;
        }

        int getNumVars() {
            return theVars.size();
        }

        ArrayList getVars() {
            return theVars;
        }

        ExprVar getVar(int i) {
            return theVars.get(i);
        }

        void setIndexVar(int i, ExprVar var) {
            theIndexVars.set(i, var);
        }

        ExprVar getIndexVar(int i) {
            return theIndexVars.get(i);
        }

        ExprVar getVar() {

            if (theVars.size() > 1) {
                throw new QueryStateException(
                    "Method called on FromClause with more than one " +
                    "variables. domain expression:\n" + theDomainExpr.display());
            }

            return theVars.get(0);
        }

        ArrayList getTables() {

            if (theDomainExpr.getKind() == ExprKind.BASE_TABLE) {
                return ((ExprBaseTable)theDomainExpr).getTables();
            }

            return null;
        }

        ExprBaseTable getTableExpr() {
            if (theDomainExpr.getKind() == ExprKind.BASE_TABLE) {
                return (ExprBaseTable)theDomainExpr;
            }

            return null;
        }

        TableImpl getTargetTable() {

            if (theDomainExpr.getKind() == ExprKind.BASE_TABLE) {
                return ((ExprBaseTable)theDomainExpr).getTargetTable();
            }

            return null;
        }

        ExprVar getTargetTableVar() {

            if (theDomainExpr.getKind() == ExprKind.BASE_TABLE) {
                ExprBaseTable te = (ExprBaseTable)theDomainExpr;
                return theVars.get(te.getNumAncestors());
            }

            if (theDomainExpr.getKind() == ExprKind.UPDATE_ROW ||
                theDomainExpr.getKind() == ExprKind.INSERT_ROW ||
                theDomainExpr.getKind() == ExprKind.DELETE_ROW) {
                return theVars.get(0);
            }

            return null;
        }

        ExprVar getTargetTableIndexVar() {

            if (theDomainExpr.getKind() == ExprKind.BASE_TABLE) {
                ExprBaseTable te = (ExprBaseTable)theDomainExpr;
                return theIndexVars.get(te.getNumAncestors());
            }

            return null;
        }
    }

    private int theNumChildren;

    private ArrayList theFromClauses;

    private Expr theWhereExpr;

    private ArrayList theFieldNames;

    private ArrayList theFieldExprs;

    private boolean theConstructsRecord = true;

    private boolean theDoNullOnEmpty = true;

    private int theNumGroupByExprs = -1;

    private boolean theNeedOuterSFWForGroupBy;

    private ArrayList theSortExprs;

    private ArrayList theSortSpecs;

    private boolean theUsePrimaryIndexForSort = false;

    private ArrayList theSortingIndexes = null;

    private ExprFuncCall theNearPred;

    private boolean theHasNearPred;

    private Expr theOffsetExpr;

    private Expr theLimitExpr;

    private boolean theGroupByExprCompleteShardKey = false;

    ExprSFW(
        QueryControlBlock qcb,
        StaticContext sctx,
        QueryException.Location location) {

        super(qcb, sctx, ExprKind.SFW, location);
        theFromClauses = new ArrayList(8);
    }

    ExprVar createFromVar(Expr domainExpr, String varName) {
        return createFrom(domainExpr, varName, null);
    }

    ExprVar createTableVar(
        Expr domExpr,
        TableImpl table,
        String varName) {

        assert(table != null &&
               (domExpr.getKind() == ExprKind.BASE_TABLE ||
                domExpr.getKind() == ExprKind.UPDATE_ROW ||
                domExpr.getKind() == ExprKind.INSERT_ROW ||
                domExpr.getKind() == ExprKind.DELETE_ROW));

        for (FromClause fc : theFromClauses) {
            if (fc.getDomainExpr() == domExpr) {
                return fc.addVar(varName, table);
            }
        }

        return createFrom(domExpr, varName, table);
    }

    private ExprVar createFrom(
        Expr domainExpr,
        String varName,
        TableImpl table) {

        FromClause fc = this.new FromClause(domainExpr, varName, table);
        theFromClauses.add(fc);
        ++theNumChildren;
        return fc.getVar(0);
    }

    void removeFromClause(int i, boolean destroy) {

        FromClause fc = theFromClauses.get(i);

        for (ExprVar var : fc.getVars()) {
            assert(!var.hasParents());
        }

        theFromClauses.remove(i);
        --theNumChildren;
        fc.getDomainExpr().removeParent(this, destroy);
    }

    FromClause getFromClause(int i) {
        return theFromClauses.get(i);
    }

    FromClause getFirstFrom() {
        return getFromClause(0);
    }

    int getNumFroms() {
        return theFromClauses.size();
    }

    Expr getDomainExpr(int i) {
        return theFromClauses.get(i).getDomainExpr();
    }

    void setDomainExpr(int i, Expr newExpr, boolean destroy) {
        FromClause fc = theFromClauses.get(i);
        fc.theDomainExpr.removeParent(this, destroy);
        fc.theDomainExpr = newExpr;
        newExpr.addParent(this);
    }

    /*
     * Check whether the given expr is the domain expr for a FromClause defined
     * by this SFW expr. If so, return the variables of that FromClause.
     */
    ArrayList findVarsForExpr(Expr expr) {

        for (int i = 0; i < theFromClauses.size(); ++i) {
            FromClause fc = theFromClauses.get(i);
            if (fc.getDomainExpr() == expr) {
                return fc.getVars();
            }
        }

        return null;
    }

    void removeUnusedVars() {

        for (int i = theFromClauses.size() - 1; i >= 0; --i) {
            FromClause fc = theFromClauses.get(i);
            if (fc.getDomainExpr().isScalar() &&
                fc.getVar(0).getNumParents() == 0) {
                assert(fc.getNumVars() == 1);
                removeFromClause(i, true);
            }
        }
    }

    ExprVar addIndexVar(TableImpl table, IndexImpl index) {

        for (FromClause fc : theFromClauses) {

            ArrayList tables = fc.getTables();

            if (tables == null) {
                continue;
            }

            int tablePos = -1;
            for (int i = 0; i < tables.size(); ++i) {
                if (tables.get(i).getId() == table.getId()) {
                    tablePos = i;
                    break;
                }
            }

            if (tablePos < 0) {
                continue;
            }

            ExprVar var = fc.getVar(tablePos);
            String idxVarName = var.createIndexVarName();

            ExprVar idxVar = new ExprVar(theQCB, theSctx, var.theLocation,
                                         idxVarName, table, fc);

            RecordDefImpl indexEntryDef;
            if (index != null) {
                indexEntryDef = index.getIndexEntryDef();
            } else {
                /*
                 * We use getRowDef() instead of getPrimKeyDef(), because during
                 * runtime, we always construct a full table row, even if we may
                 * fill just the prim-key columns in that row.
                 */
                indexEntryDef = table.getRowDef();
            }

            ExprType idxVarType = TypeManager.createType(indexEntryDef,
                                                           Quantifier.ONE);
            idxVar.setIndex(index, idxVarType);

            fc.setIndexVar(tablePos, idxVar);

            return idxVar;
        }

        return null;
    }

    void addWhereClause(Expr condExpr) {

        assert(theWhereExpr == null);

        theWhereExpr = ExprPromote.create(
            null, condExpr, TypeManager.BOOLEAN_QSTN());

        theWhereExpr.addParent(ExprSFW.this);
        ++theNumChildren;
    }

    Expr getWhereExpr() {
        return theWhereExpr;
    }

    void setWhereExpr(Expr newExpr, boolean destroy) {
        theWhereExpr.removeParent(this, destroy);
        theWhereExpr = null;
        addWhereClause(newExpr);
    }

    void removeWhereExpr(boolean destroy) {
        theWhereExpr.removeParent(this, destroy);
        theWhereExpr = null;
        --theNumChildren;
    }

    void addSelectClause(
        ArrayList fieldNames,
        ArrayList fieldExprs) {

        assert(fieldNames.size() == fieldExprs.size());
        theFieldNames = fieldNames;
        theFieldExprs = fieldExprs;

        for (int i = 0; i < fieldExprs.size(); ++i) {
            Expr expr = fieldExprs.get(i);

            if (expr.isMultiValued()) {
                ArrayList args = new ArrayList(1);
                args.add(expr);
                expr = new ExprArrayConstr(theQCB, theSctx, expr.getLocation(),
                                           args, true/*conditional*/);
            }

            expr.addParent(this);
            theFieldExprs.set(i, expr);
        }

        theNumChildren += fieldExprs.size();
    }

    boolean getConstructsRecord() {
        return theConstructsRecord;
    }

    void setConstructsRecord(boolean v) {

        if (v != theConstructsRecord) {
            theConstructsRecord = v;
            theType = computeType();
        }
    }

    Expr getFieldExpr(int i) {
        return theFieldExprs.get(i);
    }

    void setFieldExpr(int i, Expr newExpr, boolean destroy) {

        theFieldExprs.get(i).removeParent(this, destroy);

        if (newExpr.isMultiValued()) {
            ArrayList args = new ArrayList(1);
            args.add(newExpr);
            newExpr = new ExprArrayConstr(theQCB, theSctx, newExpr.theLocation,
                                          args, true/*conditional*/);
        }

        theFieldExprs.set(i, newExpr);
        newExpr.addParent(this);
    }

    void removeField(int i, boolean destroy) {
        theFieldExprs.get(i).removeParent(this, destroy);
        theFieldExprs.remove(i);
        theFieldNames.remove(i);
        theType = computeType();
        --theNumChildren;
    }

    void addField(String name, Expr expr) {

        if (theFieldExprs == null) {
            theFieldExprs = new ArrayList();
            theFieldNames = new ArrayList();
        }

        expr = ExprPromote.create(null, expr, TypeManager.ANY_QSTN());
        theFieldExprs.add(expr);
        theFieldNames.add(name);
        expr.addParent(this);

        if (theFieldExprs.size() > 1) {
            theConstructsRecord = true;
        }

        theType = computeType();
        ++theNumChildren;
    }

    String getFieldName(int i) {
        return theFieldNames.get(i);
    }

    int getNumFields() {
        return (theFieldExprs == null ? 0 : theFieldExprs.size());
    }

    ArrayList getFieldNames() {
        return theFieldNames;
    }

    void setFieldNames(ArrayList fieldNames) {
        theFieldNames = fieldNames;
        theType = computeType();
    }

    String[] getFieldNamesArray() {
        String[] arr = new String[theFieldNames.size()];
        return theFieldNames.toArray(arr);
    }

    boolean doNullOnEmpty() {
        return theDoNullOnEmpty;
    }

    void setDoNullOnEmpty(boolean v) {
        theDoNullOnEmpty = v;
    }

    int getNumGroupByExprs() {
        return theNumGroupByExprs;
    }

    void setNumGroupByExprs(int v) {
        if (theNumGroupByExprs < v) {
            theNumGroupByExprs = v;
        }
    }

    boolean hasGroupBy() {
        return theNumGroupByExprs >= 0;
    }

    boolean needOuterSFWForGroupBy() {
        return theNeedOuterSFWForGroupBy;
    }

    boolean isGroupingField(int i) {
        return (theNumGroupByExprs > 0 && i < theNumGroupByExprs);
    }

    void addGroupByClause(ArrayList gbExprs) {

        theFieldNames = new ArrayList(gbExprs.size());

        for (int i = 0; i < gbExprs.size(); ++i) {
            Expr expr = gbExprs.get(i);
            expr = ExprPromote.create(null, expr, TypeManager.ANY_ATOMIC_QSTN());
            expr.addParent(this);
            gbExprs.set(i, expr);
            theFieldNames.add("gb-" + i);
        }

        /*
         * The grouping exprs become the SELECT list of this inner SFW.
         * The grouping functions will be added later, when we poccess the
         * SELECT list of the overall SFW.
         */
        theFieldExprs = gbExprs;
        theNumGroupByExprs = gbExprs.size();

        theNumChildren += theNumGroupByExprs;
    }

    /*
     * Called by the Translator on the inner SFW to create, in the outer
     * SELECT list, the expr that corresponds to a given expr from the
     * original SELECT list. The expr to create will reference the grouping
     * exprs and/or the aggragate function that appear in the inner SELECT
     * list.
     *
     * - fieldPos : the position in the SELECT list of the expr to map in the
     *   the outer SELECT list
     * - fieldExpr : theExpr to map
     * - fieldName : the associate field name
     * - fieldSubExpr : On the initial invocation, it is the same as the
     *   fieldExpr. Method traverses the fieldExpr subtree looking for sub
     *   exprs that are either aggregate functions or match the grouping exprs.
     * - outerSFW : the outer SFW
     * - outerFromVar : from FROM var of the outer SFW.
     */
    Expr rewriteSelectExprForGroupBy(
        int fieldPos,
        Expr fieldExpr,
        String fieldName,
        Expr fieldSubExpr,
        ExprSFW outerSFW,
        ExprVar outerFromVar) {

        if (fieldSubExpr.isStepExpr()) {

            int i;
            for (i = 0; i < theNumGroupByExprs; ++i) {

                if (ExprUtils.matchExprs(fieldSubExpr, getFieldExpr(i))) {

                    String gbName = "gb-" + i;
                    Expr fieldRef = new ExprFieldStep(theQCB, theSctx,
                                                      theLocation,
                                                      outerFromVar,
                                                      gbName);

                    if (i != fieldPos || fieldSubExpr != fieldExpr) {
                        outerSFW.theNeedOuterSFWForGroupBy = true;
                    }

                    if (fieldSubExpr == fieldExpr) {
                        return fieldRef;
                    }

                    fieldSubExpr.replace(fieldRef, false/*destroy*/);
                    return fieldExpr;
                }
            }

        } else if (fieldSubExpr.getFunction(null) != null &&
                   fieldSubExpr.getFunction(null).isAggregate()) {

            int numFields = getNumFields();
            int i;
            for (i = theNumGroupByExprs; i < numFields; ++i) {

                if (ExprUtils.matchExprs(fieldSubExpr, getFieldExpr(i))) {
                    break;
                }
            }

            QueryException.Location loc = fieldSubExpr.getLocation();
            String aggrName = "aggr-" + i;
            Expr fieldRef;

            if (fieldSubExpr.getFunction(FuncCode.FN_AVG) != null) {

                /* convert avg to sum/count */
                assert(i == numFields);
                outerSFW.theNeedOuterSFWForGroupBy = true;

                Expr inExpr = fieldSubExpr.getInput();

                Expr sumExpr = ExprFuncCall.create(theQCB, theSctx, loc,
                                                   FuncCode.FN_SUM, inExpr);

                Expr cntExpr = ExprFuncCall.create(theQCB, theSctx, loc,
                                                   FuncCode.FN_COUNT_NUMBERS,
                                                   inExpr);

                addField(aggrName, sumExpr);
                String aggrName2 = "aggr-" + (i + 1);
                addField(aggrName2, cntExpr);
                numFields = getNumFields();
                outerFromVar.computeType(false);

                Expr sumRef = new ExprFieldStep(theQCB, theSctx, loc,
                                                outerFromVar, aggrName);

                Expr cntRef = new ExprFieldStep(theQCB, theSctx, loc,
                                                outerFromVar, aggrName2);

                cntRef = ExprCast.create(theQCB, theSctx, loc, cntRef,
                                          FieldDefImpl.doubleDef,
                                          Quantifier.ONE);


                ArrayList args = new ArrayList(3);
                args.add(sumRef);
                args.add(cntRef);
                FieldValueImpl ops = FieldDefImpl.stringDef.createString("*/");
                args.add(new ExprConst(theQCB, theSctx, loc, ops));

                fieldRef = ExprFuncCall.create(theQCB, theSctx, loc,
                                               FuncCode.OP_MULT_DIV, args);
            } else {

                if (i == numFields) {
                    addField(aggrName, fieldSubExpr);
                    outerFromVar.computeType(false);
                }

                fieldRef = new ExprFieldStep(theQCB, theSctx, loc,
                                             outerFromVar, aggrName);
            }


            if (i != fieldPos || fieldSubExpr != fieldExpr) {
                outerSFW.theNeedOuterSFWForGroupBy = true;
            }

            if (fieldSubExpr == fieldExpr) {
                return fieldRef;
            }

            fieldSubExpr.replace(fieldRef, false/*destroy*/);

            /*
             * we must reset the i-th field expr because the replace() call
             * above replaces with fieldRef.
             */
            setFieldExpr(i, fieldSubExpr, false);
            return fieldExpr;

        } else if (fieldSubExpr.getKind() == ExprKind.VAR) {

            ExprVar var = (ExprVar)fieldSubExpr;

            if (var.getVarKind() != VarKind.EXTERNAL) {
                throw new QueryException(
                    "Invalid expression in the SELECT clause. When a " +
                    "select-from-where expression includes grouping, " +
                    "its SELECT expressions must reference grouping " +
                    "columns and aggregate functions",
                    fieldSubExpr.getLocation());
            }

            return fieldExpr;
        }

        outerSFW.theNeedOuterSFWForGroupBy = true;

        ExprIter children = fieldSubExpr.getChildren();
        while (children.hasNext()) {
            Expr child = children.next();
            rewriteSelectExprForGroupBy(fieldPos,
                                        fieldExpr,
                                        fieldName,
                                        child,
                                        outerSFW,
                                        outerFromVar);
        }
        children.reset();
        return fieldExpr;
    }

    void addSortClause(
        ArrayList sortExprs,
        ArrayList sortSpecs) {

        if (theNumGroupByExprs > 0) {
            throw new QueryException(
                "select-from-where expression cannot have both order by " +
                "and group by clauses");
        }

        for (int i = 0; i < sortExprs.size(); ++i) {
            // TODO: allow arrays as well
            Expr expr = sortExprs.get(i);
            expr = ExprPromote.create(null, expr, TypeManager.ANY_ATOMIC_QSTN());
            expr.addParent(this);
            sortExprs.set(i, expr);
        }

        theSortExprs = sortExprs;
        theSortSpecs = sortSpecs;

        theNumChildren += theSortExprs.size();
    }

    void removeSort() {

        if (!hasSort()) {
            return;
        }

        while (!theSortExprs.isEmpty()) {
            removeSortExpr(0, true);
        }

        theSortExprs = null;
        theSortSpecs = null;
        theSortingIndexes = null;
        theUsePrimaryIndexForSort = false;
    }

    boolean hasSort() {
        return (theSortExprs != null && !theSortExprs.isEmpty());
    }

    boolean hasPrimaryIndexBasedSort() {
        return theUsePrimaryIndexForSort;
    }

    boolean hasSecondaryIndexBasedSort() {
        return theSortingIndexes != null && !theSortingIndexes.isEmpty();
    }

    boolean getGroupByExprCompleteShardKey() {
        return theGroupByExprCompleteShardKey;
    }

    ArrayList getSortingIndexes() {
        return theSortingIndexes;
    }

    int getNumSortExprs() {
        return (theSortExprs == null ? 0 : theSortExprs.size());
    }

    Expr getSortExpr(int i) {
        return theSortExprs.get(i);
    }

    void setSortExpr(int i, Expr newExpr, boolean destroy) {
        theSortExprs.get(i).removeParent(this, destroy);
        theSortExprs.set(i, newExpr);
        newExpr.addParent(this);
    }

    void removeSortExpr(int i, boolean destroy) {
        Expr sortExpr = theSortExprs.remove(i);
        sortExpr.removeParent(this, destroy);
        theSortSpecs.remove(i);
        --theNumChildren;
    }

    SortSpec[] getSortSpecs() {
        SortSpec[] arr = new SortSpec[theSortSpecs.size()];
        return theSortSpecs.toArray(arr);
    }

    /*
     * Method to find the index to use for the sort or group-by and
     * determine the direction, or throw error if no applicable index.
     */
    void analyseOrderOrGroupBy(boolean orderby) {

        String opKind = (orderby ? "order-by " : "group-by ");

        FromClause fc = getFirstFrom();
        TableImpl table = fc.getTargetTable();

        if (table == null) {
            throw new QueryException(
                opKind + "cannot be performed because the " + opKind +
                "expressions are not consecutive columns of any index",
                getLocation());
        }

        ExprBaseTable tableExpr = fc.getTableExpr();

        IndexField ipath = null;
        int i = 0;
        boolean desc = false;
        boolean nullsLast = false;
        Direction direction;

        /*
         * Determine the sort direction and store it in the BaseTableExpr
         */
        if (orderby) {
            SortSpec spec = theSortSpecs.get(0);
            desc = spec.theIsDesc;
            nullsLast = !spec.theNullsFirst;
            direction = (desc ? Direction.REVERSE : Direction.FORWARD);

            for (i = 1; i < theSortSpecs.size(); ++i) {
                spec = theSortSpecs.get(i);
                if (desc != spec.theIsDesc || nullsLast != (!spec.theNullsFirst)) {
                    throw new QueryException(
                        "In the current implementation, all order-by specs " +
                        "must have the same ordering direction and the same " +
                        "relative order for NULLs",
                        getSortExpr(i).getLocation());
                }
            }

            if ((desc && nullsLast) || (!desc && !nullsLast)) {
                throw new QueryException(
                    "NULLs ordering is not compatible with the way " +
                    "NULLs are ordered in indexes.");
            }

            tableExpr.setDirection(direction);
        }

        /*
         * Check whether the sort/group exprs are a prefix of the primary
         * key columns.
         */
        int numPkCols = table.getPrimaryKeySize();

        int numExprs = (orderby ? theSortExprs.size() : theNumGroupByExprs);

        int numShardKeys = table.getShardKeySize();

        for (i = 0; i < numPkCols && i < numExprs; ++i) {

            Expr expr = (orderby ? getSortExpr(i) : getFieldExpr(i));

            if (!ExprUtils.isPrimKeyColumnRef(table, i, expr)) {
                break;
            }

            if (!orderby && i == numShardKeys - 1) {
                theGroupByExprCompleteShardKey = true;
            }
        }

        if (i == numExprs) {

            theUsePrimaryIndexForSort = true;

            if (orderby) {
                if (i > numShardKeys) {
                    while (numExprs > numShardKeys) {
                        removeSortExpr(numExprs - 1, true/*destroy*/);
                        --numExprs;
                    }
                }

                if (desc && tableExpr.getNumDescendants() > 0) {
                    throw new QueryException(
                        "In the current implementation, it is not possible " +
                        "to order by primary key columns in descending order " +
                        "when the NESTED TABLES clause contains descendants");
                }
            }

            return;
        }

        /*
         * Check whether the sort exprs are a prefix of the columns of some
         * secondary index.
         */
        theSortingIndexes = new ArrayList();
        Map indexes = table.getIndexes();

        for (Map.Entry entry : indexes.entrySet()) {

            IndexImpl index = (IndexImpl)entry.getValue();
            List indexPaths = index.getIndexFields();

            /*
             * Don't use multikey index for group-by because duplicate
             * elimination may be required. It's hard to implement duplicate
             * elimination together with group by, and it would result in no
             * actual grouping done at the RNs (because the prim key columns
             * would have to be added in the group-by).
             */
            if (hasGroupBy() && index.isMultiKey()) {
                continue;
            }

            for (i = 0;
                 i < indexPaths.size() && i < numExprs; ++i) {

                ipath = indexPaths.get(i);
                Expr expr = (orderby ? getSortExpr(i) : getFieldExpr(i));

                if (ipath.isMultiKey() || ipath.isGeometry()) {
                    break;
                }

                IndexExpr iexpr = expr.getIndexExpr();

                if (iexpr == null ||
                    !iexpr.matchesIndex(index, ipath.getPosition())) {
                    break;
                }
            }

            if (i == numExprs) {
                theSortingIndexes.add(index);
                continue;
            }

            /*
             * Check if the remaining sort exprs are primary-key columns
             * (which exist in the index as well).
             */
            if (i == indexPaths.size()) {

                for (int j = 0;
                     j < numPkCols && i < numExprs;
                     ++i, ++j) {

                    Expr expr = (orderby ? getSortExpr(i) : getFieldExpr(i));

                    if (!ExprUtils.isPrimKeyColumnRef(table, j, expr)) {
                        break;
                    }
                }

                if (i == numExprs) {
                    theSortingIndexes.add(index);
                    continue;
                }
            }
        }

        if (theSortingIndexes.isEmpty()) {
            throw new QueryException(
                opKind + "cannot be performed because there is no index " +
                "that orders the table rows in the desired order (in the " +
                "current implementation sorting and grouping are possible " +
                "only if the ORDER/GROUP BY clause contains N expressions " +
                "that match the " +
                "first N fields on an index, and these fields are not " +
                "multi-key", getLocation());
        }
    }

    /*
     * Method to add the sort expr in the SELECT clause, if not there already.
     * The method is called from the Distributer, when a receive expr is pulled
     * above a SFW expr that has sort. The method returns the positions of the
     * sort exprs in the SELECT list. The positions are stored in the receive
     * expr.
     */
    int[] addSortExprsToSelect() {

        int numFieldExprs = theFieldExprs.size();
        int numSortExprs = theSortExprs.size();

        int[] sortPositions = new int[numSortExprs];

        for (int i = 0; i < numSortExprs; ++i) {

            Expr sortExpr = theSortExprs.get(i);

            int j;
            for (j = 0; j < numFieldExprs; ++j) {

                Expr fieldExpr = theFieldExprs.get(j);

                if (ExprUtils.matchExprs(sortExpr, fieldExpr)) {
                    break;
                }
            }

            if (j == numFieldExprs) {
                theFieldExprs.add(sortExpr);
                theFieldNames.add(theQCB.generateFieldName("sort"));
                sortExpr.addParent(this);
                theConstructsRecord = true;
                sortPositions[i] = theFieldExprs.size() - 1;
                ++theNumChildren;
            } else {
                sortPositions[i] = j;
                sortExpr.removeParent(this, true/*destroy*/);
            }
        }

        theNumChildren -= theSortExprs.size();
        theSortExprs = null;
        theType = computeType();

        return sortPositions;
    }

    boolean hasNearPred() {
        return theHasNearPred;
    }

    void setNearPred(ExprFuncCall e) {
        theNearPred = e;
        theHasNearPred = true;
    }

    void rewriteNearPred() {

        if (theNearPred == null) {
            return;
        }

        FunctionLib fnlib = CompilerAPI.getFuncLib();

        theNearPred.setFunction(fnlib.getFunc(FuncCode.FN_GEO_WITHIN_DISTANCE));

        if (hasSort() || hasGroupBy()) {
            return;
        }

        Expr geopath = theNearPred.getArg(0);
        Expr geopoint = theNearPred.getArg(1);
        Expr distanceExpr = ExprFuncCall.create(theQCB, theSctx,
                                                geopath.getLocation(),
                                                FuncCode.FN_GEO_DISTANCE,
                                                geopath, geopoint);

        ArrayList sortExprs = new ArrayList(1);
        ArrayList sortSpecs = new ArrayList(1);
        sortExprs.add(distanceExpr);
        sortSpecs.add(new SortSpec(false, false));

        addSortClause(sortExprs, sortSpecs);
    }

    void addOffsetLimit(Expr offset, Expr limit) {

        if (offset != null) {
            addOffset(offset);
        }

        if (limit != null) {
            addLimit(limit);
        }
    }

    Expr getOffset() {
        return theOffsetExpr;
    }

    void addOffset(Expr expr) {

        assert(theOffsetExpr == null);

        if (!ConstKind.isConst(expr)) {
            throw new QueryException("Offset expression is not constant");
        }

        if (expr.getKind() == ExprKind.CONST) {
            FieldValueImpl val = ((ExprConst)expr).getValue();
            if (val.getLong() == 0) {
                return;
            }
        }

        theOffsetExpr = ExprPromote.create(null, expr, TypeManager.LONG_ONE());

        theOffsetExpr.addParent(this);
        ++theNumChildren;
    }

    void removeOffset(boolean destroy) {
        theOffsetExpr.removeParent(this, destroy);
        theOffsetExpr = null;
        --theNumChildren;
    }

    void setOffset(Expr newExpr, boolean destroy) {
        theOffsetExpr.removeParent(this, destroy);
        theOffsetExpr = null;
        --theNumChildren;
        addOffset(newExpr);
    }

    Expr getLimit() {
        return theLimitExpr;
    }

    void addLimit(Expr expr) {

        assert(theLimitExpr == null);

        if (!ConstKind.isConst(expr)) {
            throw new QueryException("Limit expression is not constant");
        }

        theLimitExpr = ExprPromote.create(null, expr, TypeManager.LONG_ONE());
        theLimitExpr.addParent(this);
        ++theNumChildren;
    }

    void removeLimit(boolean destroy) {
        theLimitExpr.removeParent(this, destroy);
        theLimitExpr = null;
        --theNumChildren;
    }

    void setLimit(Expr newExpr, boolean destroy) {
        theLimitExpr.removeParent(this, destroy);
        theLimitExpr = null;
        --theNumChildren;
        addLimit(newExpr);
    }

    @Override
    int getNumChildren() {
        return theNumChildren;
    }

    int computeNumChildren() {
        return
            theFromClauses.size() +
            (theWhereExpr != null ? 1 : 0) +
            theFieldExprs.size() +
            (theSortExprs != null ? theSortExprs.size() : 0) +
            (theOffsetExpr != null ? 1 : 0) +
            (theLimitExpr != null ? 1 : 0);
    }

    /**
     * Add all prim-key columns to the SELECT list. A prim-key column is added
     * if not already there. This is needed when the use of a multi-key index
     * requires duplicate elimination to be performed, based on the prim-key of
     * the result rows.
     */
    int[] addPrimKeyToSelect() {

        FromClause fc = theFromClauses.get(0);

        TableImpl table = fc.getTargetTable();

        int numPrimKeyCols = table.getPrimaryKeySize();

        if (theFieldExprs == null) {
            theFieldExprs = new ArrayList(numPrimKeyCols);
            theFieldNames = new ArrayList(numPrimKeyCols);
        }

        int numFieldExprs = theFieldExprs.size();
        int[] pkPositionsInSelect = new int[numPrimKeyCols];

        for (int i = 0; i < numPrimKeyCols; ++i) {

            Expr fieldExpr = null;
            int j;
            for (j = 0; j < numFieldExprs; ++j) {

                fieldExpr = theFieldExprs.get(j);

                if (ExprUtils.isPrimKeyColumnRef(table, i, fieldExpr)) {
                    break;
                }
            }

            if (j == numFieldExprs) {
                String pkColName = table.getPrimaryKeyColumnName(i);
                int pkColPos;
                ExprVar rowVar;
                ExprVar idxVar = fc.getTargetTableIndexVar();

                if (idxVar != null) {
                    rowVar = idxVar;
                    pkColPos = idxVar.getIndex().numFields() + i;
                } else {
                    rowVar = fc.getTargetTableVar();
                    pkColPos = table.getPrimKeyPos(i);
                }

                Expr primKeyExpr = new ExprFieldStep(getQCB(),
                                                     getSctx(),
                                                     getLocation(),
                                                     rowVar,
                                                     pkColPos);

                theFieldExprs.add(primKeyExpr);
                primKeyExpr.addParent(this);
                theFieldNames.add(theQCB.generateFieldName(pkColName));
                pkPositionsInSelect[i] = theFieldExprs.size() - 1;
                theConstructsRecord = true;
                ++theNumChildren;
            } else {
                pkPositionsInSelect[i] = j;
            }
        }

        theType = computeType();

        return pkPositionsInSelect;
    }

    @Override
    ExprType computeType() {

        if (theFieldExprs == null) {
            return null;
        }

        Quantifier q = getDomainExpr(0).getType().getQuantifier();

        for (int i = 1; i < theFromClauses.size(); ++i) {

            Quantifier q1 = getDomainExpr(i).getType().getQuantifier();

            q = TypeManager.getUnionQuant(q, q1);

            if (q == Quantifier.STAR) {
                break;
            }
        }

        if (theWhereExpr != null) {
            q = TypeManager.getUnionQuant(q, Quantifier.QSTN);
        }

        if (!getConstructsRecord()) {
            ExprType type = TypeManager.createType(getFieldExpr(0).getType(), q);
            if (type.isAnyJson()) {
                theQCB.theHaveJsonConstructors = true;
            }

            return type;
        }

        FieldMap fieldMap = new FieldMap();

        for (int i = 0; i < theFieldNames.size(); ++i) {

            FieldDefImpl fieldDef = theFieldExprs.get(i).getType().getDef();

            if (fieldDef.isJson()) {
                theQCB.theHaveJsonConstructors = true;
            }
            /*
             * TODO: what about nullability? If a field is not nullable, it
             * must have a default value, so we need a method to create a
             * default default value.
             */
            fieldMap.put(theFieldNames.get(i),
                         fieldDef,
                         true/*nullable*/,
                         null/*defaultValue*/);
        }

        RecordDefImpl recDef = FieldDefFactory.createRecordDef(fieldMap,
                                                               null/*descr*/);
        return TypeManager.createType(recDef, q);
    }

    @Override
    public boolean mayReturnNULL() {

        if (getConstructsRecord()) {
            return false;
        }

        return theFieldExprs.get(0).mayReturnNULL();
    }

    @Override
    void displayContent(StringBuilder sb, QueryFormatter formatter) {

        formatter.indent(sb);
        for (int i = 0; i < theFromClauses.size(); ++i) {
            FromClause fc = theFromClauses.get(i);
            sb.append("FROM-" + i + " :\n");
            fc.getDomainExpr().display(sb, formatter);
            sb.append(" as ");
            List vars = fc.getVars();
            for (ExprVar var : vars) {
                sb.append(var.getName() + "  ");
            }
            sb.append("\n\n");
        }

        if (theWhereExpr != null) {
            formatter.indent(sb);
            sb.append("WHERE:\n");
            theWhereExpr.display(sb, formatter);
            sb.append("\n\n");
        }

        formatter.indent(sb);
        sb.append("GROUP BY:").append(theNumGroupByExprs);
        sb.append("\n\n");

        formatter.indent(sb);
        sb.append("SELECT:\n");

        for (int i = 0; i < theFieldExprs.size(); ++i) {
            formatter.indent(sb);
            sb.append(theFieldNames.get(i)).append(": \n");
            theFieldExprs.get(i).display(sb, formatter);
            if (i < theFieldExprs.size() - 1) {
                sb.append(",\n");
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy