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

io.questdb.griffin.model.QueryModel Maven / Gradle / Ivy

/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2022 QuestDB
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

package io.questdb.griffin.model;

import io.questdb.cairo.sql.Function;
import io.questdb.griffin.OrderByMnemonic;
import io.questdb.griffin.SqlException;
import io.questdb.std.*;
import io.questdb.std.str.CharSink;

import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.Objects;

import static io.questdb.griffin.SqlKeywords.isAndKeyword;

/**
 * Important note: Make sure to update clear, equals and hashCode methods, as well as
 * the unit tests, when you're adding a new field to this class. Instances of QueryModel
 * are reused across query compilation, so making sure that we reset all fields correctly
 * is important.
 */
public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sinkable {
    public static final QueryModelFactory FACTORY = new QueryModelFactory();
    public static final int ORDER_DIRECTION_ASCENDING = 0;
    public static final int ORDER_DIRECTION_DESCENDING = 1;
    public static final String NO_ROWID_MARKER = "*!*";

    public static final int JOIN_INNER = 1;
    public static final int JOIN_OUTER = 2;
    public static final int JOIN_CROSS = 3;
    public static final int JOIN_ASOF = 4;
    public static final int JOIN_SPLICE = 5;
    public static final int JOIN_LT = 6;
    public static final int JOIN_ONE = 7;
    public static final int JOIN_MAX = JOIN_ONE;

    public static final String SUB_QUERY_ALIAS_PREFIX = "_xQdbA";

    public static final int SELECT_MODEL_NONE = 0;
    public static final int SELECT_MODEL_CHOOSE = 1;
    public static final int SELECT_MODEL_VIRTUAL = 2;
    public static final int SELECT_MODEL_ANALYTIC = 3;
    public static final int SELECT_MODEL_GROUP_BY = 4;
    public static final int SELECT_MODEL_DISTINCT = 5;
    public static final int SELECT_MODEL_CURSOR = 6;

    //types of set operations between this and union model  
    public static final int SET_OPERATION_UNION_ALL = 0;
    public static final int SET_OPERATION_UNION = 1;
    public static final int SET_OPERATION_EXCEPT = 2;
    public static final int SET_OPERATION_INTERSECT = 3;

    public static final int LATEST_BY_NONE = 0;
    public static final int LATEST_BY_DEPRECATED = 1;
    public static final int LATEST_BY_NEW = 2;

    private static final ObjList modelTypeName = new ObjList<>();
    private final ObjList bottomUpColumns = new ObjList<>();
    private final LowerCaseCharSequenceHashSet topDownNameSet = new LowerCaseCharSequenceHashSet();
    private final ObjList topDownColumns = new ObjList<>();
    private final LowerCaseCharSequenceObjHashMap aliasToColumnNameMap = new LowerCaseCharSequenceObjHashMap<>();
    private final LowerCaseCharSequenceObjHashMap columnNameToAliasMap = new LowerCaseCharSequenceObjHashMap<>();
    private final LowerCaseCharSequenceObjHashMap aliasToColumnMap = new LowerCaseCharSequenceObjHashMap<>();
    private final ObjList bottomUpColumnNames = new ObjList<>();
    private final ObjList joinModels = new ObjList<>();
    private final ObjList orderBy = new ObjList<>();
    //position of the order by clause token
    private int orderByPosition;
    private final ObjList groupBy = new ObjList<>();
    private final IntList orderByDirection = new IntList();
    private final IntHashSet dependencies = new IntHashSet();
    private final IntList orderedJoinModels1 = new IntList();
    private final IntList orderedJoinModels2 = new IntList();
    private final LowerCaseCharSequenceIntHashMap columnAliasIndexes = new LowerCaseCharSequenceIntHashMap();
    private final LowerCaseCharSequenceIntHashMap modelAliasIndexes = new LowerCaseCharSequenceIntHashMap();
    private final ObjList expressionModels = new ObjList<>();

    // collect frequency of column names from each join model
    // and check if any of columns with frequency > 0 are selected
    // column name frequency of 1 corresponds to map value 0
    // column name frequency of 0 corresponds to map value -1
    // list of "and" concatenated expressions
    private final ObjList parsedWhere = new ObjList<>();
    private final IntHashSet parsedWhereConstants = new IntHashSet();
    private final ArrayDeque sqlNodeStack = new ArrayDeque<>();
    private final LowerCaseCharSequenceIntHashMap orderHash = new LowerCaseCharSequenceIntHashMap(4, 0.5, -1);
    private final ObjList joinColumns = new ObjList<>(4);
    private final ObjList sampleByFill = new ObjList<>();
    private final ObjList latestBy = new ObjList<>();
    private final ObjList orderByAdvice = new ObjList<>();
    private final IntList orderByDirectionAdvice = new IntList();
    private final LowerCaseCharSequenceObjHashMap withClauseModel = new LowerCaseCharSequenceObjHashMap<>();
    private final ObjList updateSetColumns = new ObjList<>();
    private final IntList updateTableColumnTypes = new IntList();
    private final ObjList updateTableColumnNames = new ObjList<>();
    private ExpressionNode sampleByTimezoneName = null;
    private ExpressionNode sampleByOffset = null;
    private int latestByType = LATEST_BY_NONE;
    private ExpressionNode whereClause;
    // Used to store a deep copy of the whereClause field
    // since whereClause can be changed during optimization/generation stage.
    private ExpressionNode backupWhereClause;
    private ExpressionNode postJoinWhereClause;
    private ExpressionNode constWhereClause;
    private QueryModel nestedModel;
    private ExpressionNode tableName;
    private long tableVersion = -1;
    private Function tableNameFunction;
    private ExpressionNode alias;
    private ExpressionNode timestamp;
    private ExpressionNode sampleBy;
    private ExpressionNode sampleByUnit;
    private JoinContext context;
    private ExpressionNode joinCriteria;
    private int joinType = JOIN_INNER;
    private int joinKeywordPosition;
    private IntList orderedJoinModels = orderedJoinModels2;
    private ExpressionNode limitLo;
    private ExpressionNode limitHi;
    //position of the limit clause token
    private int limitPosition;
    private ExpressionNode limitAdviceLo;
    private ExpressionNode limitAdviceHi;
    //simple flag to mark when limit x,y in current model (part of query) is already taken care of by existing factories e.g. LimitedSizeSortedLightRecordCursorFactory
    //and doesn't need to be enforced by LimitRecordCursor. We need it to detect whether current factory implements limit from this or inner query .
    private boolean isLimitImplemented;
    // A flag to mark intermediate SELECT translation models. Such models do not contain the full list of selected
    // columns (e.g. they lack virtual columns), so they should be skipped when rewriting positional ORDER BY.
    private boolean isSelectTranslation = false;
    private int selectModelType = SELECT_MODEL_NONE;
    private boolean nestedModelIsSubQuery = false;
    private boolean distinct = false;
    private QueryModel unionModel;
    private int setOperationType;
    private int modelPosition = 0;
    private int orderByAdviceMnemonic = OrderByMnemonic.ORDER_BY_UNKNOWN;
    private int tableId = -1;
    private boolean isUpdateModel;
    private int modelType = ExecutionModel.QUERY;
    private QueryModel updateTableModel;
    private String updateTableName;
    private boolean artificialStar;

    private QueryModel() {
        joinModels.add(this);
    }

    // Recursively clones the current value of whereClause for the model and its sub-models into the backupWhereClause field.
    public static void backupWhereClause(final ObjectPool pool, final QueryModel model) {
        QueryModel current = model;
        while (current != null) {
            if (current.unionModel != null) {
                backupWhereClause(pool, current.unionModel);
            }
            if (current.updateTableModel != null) {
                backupWhereClause(pool, current.updateTableModel);
            }
            for (int i = 0, n = current.joinModels.size(); i < n; i++) {
                final QueryModel m = current.joinModels.get(i);
                if (m != null && current != m) {
                    backupWhereClause(pool, m);
                }
            }
            current.backupWhereClause = ExpressionNode.deepClone(pool, current.whereClause);
            current = current.nestedModel;
        }
    }

    // Recursively restores the whereClause field from backupWhereClause for the model and its sub-models.
    public static void restoreWhereClause(final ObjectPool pool, final QueryModel model) {
        QueryModel current = model;
        while (current != null) {
            if (current.unionModel != null) {
                restoreWhereClause(pool, current.unionModel);
            }
            if (current.updateTableModel != null) {
                restoreWhereClause(pool, current.updateTableModel);
            }
            for (int i = 0, n = current.joinModels.size(); i < n; i++) {
                final QueryModel m = current.joinModels.get(i);
                if (m != null && current != m) {
                    restoreWhereClause(pool, m);
                }
            }
            current.whereClause = ExpressionNode.deepClone(pool, current.backupWhereClause);
            current = current.nestedModel;
        }
    }

    public void addBottomUpColumn(QueryColumn column) throws SqlException {
        addBottomUpColumn(0, column, false, null);
    }

    public void addBottomUpColumn(QueryColumn column, boolean allowDuplicates) throws SqlException {
        addBottomUpColumn(0, column, allowDuplicates, null);
    }

    public void addBottomUpColumn(int position, QueryColumn column, boolean allowDuplicates) throws SqlException {
        addBottomUpColumn(position, column, allowDuplicates, null);
    }

    public void addBottomUpColumn(int position, QueryColumn column, boolean allowDuplicates, CharSequence additionalMessage) throws SqlException {
        if (!allowDuplicates && aliasToColumnMap.contains(column.getName())) {
            throw SqlException.duplicateColumn(position, column.getName(), additionalMessage);
        }
        bottomUpColumns.add(column);
        addField(column);
    }

    public void addDependency(int index) {
        dependencies.add(index);
    }

    public void addExpressionModel(ExpressionNode node) {
        assert node.queryModel != null;
        expressionModels.add(node);
    }

    public void addField(QueryColumn column) {
        final CharSequence alias = column.getAlias();
        final ExpressionNode ast = column.getAst();
        assert alias != null;
        aliasToColumnNameMap.put(alias, ast.token);
        columnNameToAliasMap.put(ast.token, alias);
        bottomUpColumnNames.add(alias);
        aliasToColumnMap.put(alias, column);
        columnAliasIndexes.put(alias, bottomUpColumnNames.size() - 1);
    }

    public void addGroupBy(ExpressionNode node) {
        groupBy.add(node);
    }

    public void addJoinColumn(ExpressionNode node) {
        joinColumns.add(node);
    }

    public void addJoinModel(QueryModel model) {
        joinModels.add(model);
    }

    public void addLatestBy(ExpressionNode latestBy) {
        this.latestBy.add(latestBy);
    }

    public boolean addModelAliasIndex(ExpressionNode node, int index) {
        return modelAliasIndexes.put(node.token, index);
    }

    public void addOrderBy(ExpressionNode node, int direction) {
        orderBy.add(node);
        orderByDirection.add(direction);
    }

    public void addParsedWhereNode(ExpressionNode node, boolean innerPredicate) {
        node.innerPredicate = innerPredicate;
        parsedWhere.add(node);
    }

    public void addSampleByFill(ExpressionNode sampleByFill) {
        this.sampleByFill.add(sampleByFill);
    }

    public void addTopDownColumn(QueryColumn column, CharSequence alias) {
        if (topDownNameSet.add(alias)) {
            topDownColumns.add(column);
        }
    }

    public void addUpdateTableColumnMetadata(int columnType, String columnName) {
        updateTableColumnTypes.add(columnType);
        updateTableColumnNames.add(columnName);
    }

    /* Determines whether this model allows pushing columns from parent model(s).
     * If this is a UNION, EXCEPT or INTERSECT or contains a select distinct then it can't be done safely. */
    public boolean allowsColumnsChange() {
        QueryModel union = this;
        while (union != null) {
            if (union.getSetOperationType() != QueryModel.SET_OPERATION_UNION_ALL ||
                    union.getSelectModelType() == QueryModel.SELECT_MODEL_DISTINCT) {
                return false;
            }

            union = union.getUnionModel();
        }

        return true;
    }

    @Override
    public void clear() {
        bottomUpColumns.clear();
        aliasToColumnNameMap.clear();
        joinModels.clear();
        joinModels.add(this);
        clearSampleBy();
        orderBy.clear();
        orderByDirection.clear();
        orderByAdvice.clear();
        orderByDirectionAdvice.clear();
        orderByPosition = 0;
        orderByAdviceMnemonic = OrderByMnemonic.ORDER_BY_UNKNOWN;
        isSelectTranslation = false;
        groupBy.clear();
        dependencies.clear();
        parsedWhere.clear();
        whereClause = null;
        constWhereClause = null;
        nestedModel = null;
        tableName = null;
        alias = null;
        latestByType = LATEST_BY_NONE;
        latestBy.clear();
        joinCriteria = null;
        joinType = JOIN_INNER;
        joinKeywordPosition = 0;
        orderedJoinModels1.clear();
        orderedJoinModels2.clear();
        parsedWhereConstants.clear();
        columnAliasIndexes.clear();
        modelAliasIndexes.clear();
        postJoinWhereClause = null;
        context = null;
        orderedJoinModels = orderedJoinModels2;
        limitHi = null;
        limitLo = null;
        limitAdviceHi = null;
        limitAdviceLo = null;
        limitPosition = 0;
        isLimitImplemented = false;
        timestamp = null;
        sqlNodeStack.clear();
        joinColumns.clear();
        withClauseModel.clear();
        selectModelType = SELECT_MODEL_NONE;
        columnNameToAliasMap.clear();
        tableNameFunction = null;
        tableId = -1;
        tableVersion = -1;
        bottomUpColumnNames.clear();
        expressionModels.clear();
        distinct = false;
        nestedModelIsSubQuery = false;
        unionModel = null;
        orderHash.clear();
        modelPosition = 0;
        topDownColumns.clear();
        topDownNameSet.clear();
        aliasToColumnMap.clear();
        isUpdateModel = false;
        modelType = ExecutionModel.QUERY;
        updateSetColumns.clear();
        updateTableColumnTypes.clear();
        updateTableColumnNames.clear();
        updateTableModel = null;
        updateTableName = null;
        setOperationType = SET_OPERATION_UNION_ALL;
        artificialStar = false;
    }

    public void clearColumnMapStructs() {
        this.aliasToColumnNameMap.clear();
        this.bottomUpColumnNames.clear();
        this.aliasToColumnMap.clear();
    }

    public void clearOrderBy() {
        orderBy.clear();
        orderByDirection.clear();
    }

    public void clearSampleBy() {
        sampleBy = null;
        sampleByUnit = null;
        sampleByFill.clear();
        sampleByTimezoneName = null;
        sampleByOffset = null;
    }

    public void copyBottomToTopColumns() {
        topDownColumns.clear();
        topDownNameSet.clear();
        for (int i = 0, n = bottomUpColumns.size(); i < n; i++) {
            QueryColumn column = bottomUpColumns.getQuick(i);
            addTopDownColumn(column, column.getAlias());
        }
    }

    public void copyColumnsFrom(
            QueryModel other,
            ObjectPool queryColumnPool,
            ObjectPool expressionNodePool
    ) {
        clearColumnMapStructs();

        // copy only literal columns and convert functions to literal while copying
        final ObjList aliases = other.aliasToColumnMap.keys();
        for (int i = 0, n = aliases.size(); i < n; i++) {
            final CharSequence alias = aliases.getQuick(i);
            QueryColumn qc = other.aliasToColumnMap.get(alias);
            if (qc.getAst().type != ExpressionNode.LITERAL) {
                qc = queryColumnPool.next().of(
                        alias,
                        expressionNodePool.next().of(
                                ExpressionNode.LITERAL,
                                alias,
                                0,
                                qc.getAst().position
                        ),
                        qc.isIncludeIntoWildcard()
                );
            }
            this.aliasToColumnMap.put(alias, qc);
        }
        ObjList columnNames = other.bottomUpColumnNames;
        this.bottomUpColumnNames.addAll(columnNames);
        for (int i = 0, n = columnNames.size(); i < n; i++) {
            final CharSequence name = columnNames.getQuick(i);
            this.aliasToColumnNameMap.put(name, name);
        }
    }

    public void copyOrderByAdvice(ObjList orderByAdvice) {
        this.orderByAdvice.clear();
        this.orderByAdvice.addAll(orderByAdvice);
    }

    public void copyOrderByDirectionAdvice(IntList orderByDirection) {
        this.orderByDirectionAdvice.clear();
        this.orderByDirectionAdvice.addAll(orderByDirection);
    }

    public void copyUpdateTableMetadata(QueryModel updateTableModel) {
        this.updateTableModel = updateTableModel;
        this.tableId = updateTableModel.tableId;
        this.tableVersion = updateTableModel.tableVersion;
    }

    public QueryColumn findBottomUpColumnByAst(ExpressionNode node) {
        for (int i = 0, n = bottomUpColumns.size(); i < n; i++) {
            QueryColumn qc = bottomUpColumns.getQuick(i);
            if (ExpressionNode.compareNodesExact(node, qc.getAst())) {
                return qc;
            }
        }
        return null;
    }

    public ExpressionNode getAlias() {
        return alias;
    }

    public int getLimitPosition() {
        return limitPosition;
    }

    public int getOrderByPosition() {
        return orderByPosition;
    }

    public void setAlias(ExpressionNode alias) {
        this.alias = alias;
    }

    public LowerCaseCharSequenceObjHashMap getAliasToColumnMap() {
        return aliasToColumnMap;
    }

    public LowerCaseCharSequenceObjHashMap getAliasToColumnNameMap() {
        return aliasToColumnNameMap;
    }

    public ObjList getBottomUpColumnNames() {
        return bottomUpColumnNames;
    }

    public ObjList getBottomUpColumns() {
        return bottomUpColumns;
    }

    public int getColumnAliasIndex(CharSequence alias) {
        return columnAliasIndexes.get(alias);
    }

    public LowerCaseCharSequenceObjHashMap getColumnNameToAliasMap() {
        return columnNameToAliasMap;
    }

    public ObjList getColumns() {
        return topDownColumns.size() > 0 ? topDownColumns : bottomUpColumns;
    }

    public ExpressionNode getConstWhereClause() {
        return constWhereClause;
    }

    public void setConstWhereClause(ExpressionNode constWhereClause) {
        this.constWhereClause = constWhereClause;
    }

    public JoinContext getContext() {
        return context;
    }

    public void setContext(JoinContext context) {
        this.context = context;
    }

    public IntHashSet getDependencies() {
        return dependencies;
    }

    public ObjList getExpressionModels() {
        return expressionModels;
    }

    public ObjList getGroupBy() {
        return groupBy;
    }

    public ObjList getJoinColumns() {
        return joinColumns;
    }

    public ExpressionNode getJoinCriteria() {
        return joinCriteria;
    }

    public void setJoinCriteria(ExpressionNode joinCriteria) {
        this.joinCriteria = joinCriteria;
    }

    public int getJoinKeywordPosition() {
        return joinKeywordPosition;
    }

    public void setJoinKeywordPosition(int position) {
        this.joinKeywordPosition = position;
    }

    public ObjList getJoinModels() {
        return joinModels;
    }

    public int getJoinType() {
        return joinType;
    }

    public void setJoinType(int joinType) {
        this.joinType = joinType;
    }

    public ObjList getLatestBy() {
        return latestBy;
    }

    public int getLatestByType() {
        return latestByType;
    }

    public void setLatestByType(int latestByType) {
        this.latestByType = latestByType;
    }

    public ExpressionNode getLimitAdviceHi() {
        return limitAdviceHi;
    }

    public ExpressionNode getLimitAdviceLo() {
        return limitAdviceLo;
    }

    public ExpressionNode getLimitHi() {
        return limitHi;
    }

    public ExpressionNode getLimitLo() {
        return limitLo;
    }

    public int getModelAliasIndex(CharSequence column, int start, int end) {
        int index = modelAliasIndexes.keyIndex(column, start, end);
        if (index < 0) {
            return modelAliasIndexes.valueAt(index);
        }
        return -1;
    }

    public LowerCaseCharSequenceIntHashMap getModelAliasIndexes() {
        return modelAliasIndexes;
    }

    public int getModelPosition() {
        return modelPosition;
    }

    public void setLimitPosition(int limitPosition) {
        this.limitPosition = limitPosition;
    }

    public void setModelPosition(int modelPosition) {
        this.modelPosition = modelPosition;
    }

    @Override
    public int getModelType() {
        return modelType;
    }

    public void setModelType(int modelType) {
        this.modelType = modelType;
    }

    public CharSequence getName() {
        if (alias != null) {
            return alias.token;
        }

        if (tableName != null) {
            return tableName.token;
        }

        return null;
    }

    public QueryModel getNestedModel() {
        return nestedModel;
    }

    public void setNestedModel(QueryModel nestedModel) {
        this.nestedModel = nestedModel;
    }

    public ObjList getOrderBy() {
        return orderBy;
    }

    public ObjList getOrderByAdvice() {
        return orderByAdvice;
    }

    public int getOrderByAdviceMnemonic() {
        return orderByAdviceMnemonic;
    }

    public void setOrderByAdviceMnemonic(int orderByAdviceMnemonic) {
        this.orderByAdviceMnemonic = orderByAdviceMnemonic;
    }

    public IntList getOrderByDirection() {
        return orderByDirection;
    }

    public IntList getOrderByDirectionAdvice() {
        return orderByDirectionAdvice;
    }

    public LowerCaseCharSequenceIntHashMap getOrderHash() {
        return orderHash;
    }

    public IntList getOrderedJoinModels() {
        return orderedJoinModels;
    }

    public void setOrderByPosition(int orderByPosition) {
        this.orderByPosition = orderByPosition;
    }

    public void setOrderedJoinModels(IntList that) {
        assert that == orderedJoinModels1 || that == orderedJoinModels2;
        this.orderedJoinModels = that;
    }

    public ObjList getParsedWhere() {
        return parsedWhere;
    }

    public ExpressionNode getPostJoinWhereClause() {
        return postJoinWhereClause;
    }

    public void setPostJoinWhereClause(ExpressionNode postJoinWhereClause) {
        this.postJoinWhereClause = postJoinWhereClause;
    }

    public ExpressionNode getSampleBy() {
        return sampleBy;
    }

    public void setSampleBy(ExpressionNode sampleBy) {
        this.sampleBy = sampleBy;
    }

    public ObjList getSampleByFill() {
        return sampleByFill;
    }

    public ExpressionNode getSampleByOffset() {
        return sampleByOffset;
    }

    public void setSampleByOffset(ExpressionNode sampleByOffset) {
        this.sampleByOffset = sampleByOffset;
    }

    public ExpressionNode getSampleByTimezoneName() {
        return sampleByTimezoneName;
    }

    public void setSampleByTimezoneName(ExpressionNode sampleByTimezoneName) {
        this.sampleByTimezoneName = sampleByTimezoneName;
    }

    public ExpressionNode getSampleByUnit() {
        return sampleByUnit;
    }

    public int getSelectModelType() {
        return selectModelType;
    }

    public void setSelectModelType(int selectModelType) {
        this.selectModelType = selectModelType;
    }

    public int getSetOperationType() {
        return setOperationType;
    }

    public void setSetOperationType(int setOperationType) {
        this.setOperationType = setOperationType;
    }

    public int getTableId() {
        return tableId;
    }

    public void setTableId(int id) {
        this.tableId = id;
    }

    public ExpressionNode getTableName() {
        return tableName;
    }

    public void setTableName(ExpressionNode tableName) {
        this.tableName = tableName;
    }

    public Function getTableNameFunction() {
        return tableNameFunction;
    }

    public void setTableNameFunction(Function function) {
        this.tableNameFunction = function;
    }

    public long getTableVersion() {
        return tableVersion;
    }

    public void setTableVersion(long tableVersion) {
        this.tableVersion = tableVersion;
    }

    public ExpressionNode getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(ExpressionNode timestamp) {
        this.timestamp = timestamp;
    }

    public ObjList getTopDownColumns() {
        return topDownColumns;
    }

    public QueryModel getUnionModel() {
        return unionModel;
    }

    public void setUnionModel(QueryModel unionModel) {
        this.unionModel = unionModel;
    }

    public ObjList getUpdateExpressions() {
        return updateSetColumns;
    }

    public ObjList getUpdateTableColumnNames() {
        return this.updateTableModel != null ? this.updateTableModel.getUpdateTableColumnNames() : updateTableColumnNames;
    }

    public IntList getUpdateTableColumnTypes() {
        return this.updateTableModel != null ? this.updateTableModel.getUpdateTableColumnTypes() : updateTableColumnTypes;
    }

    public String getUpdateTableName() {
        return updateTableName;
    }

    public void setUpdateTableName(String tableName) {
        this.updateTableName = tableName;
    }

    public ExpressionNode getWhereClause() {
        return whereClause;
    }

    public void setWhereClause(ExpressionNode whereClause) {
        this.whereClause = whereClause;
    }

    public LowerCaseCharSequenceObjHashMap getWithClauses() {
        return withClauseModel;
    }

    public boolean isArtificialStar() {
        return artificialStar;
    }

    public void setArtificialStar(boolean artificialStar) {
        this.artificialStar = artificialStar;
    }

    public boolean isDistinct() {
        return distinct;
    }

    public void setDistinct(boolean distinct) {
        this.distinct = distinct;
    }

    public boolean isLimitImplemented() {
        return isLimitImplemented;
    }

    public void setLimitImplemented(boolean limitImplemented) {
        isLimitImplemented = limitImplemented;
    }

    public boolean isNestedModelIsSubQuery() {
        return nestedModelIsSubQuery;
    }

    public void setNestedModelIsSubQuery(boolean nestedModelIsSubQuery) {
        this.nestedModelIsSubQuery = nestedModelIsSubQuery;
    }

    public boolean isSelectTranslation() {
        return isSelectTranslation;
    }

    public void setSelectTranslation(boolean isSelectTranslation) {
        this.isSelectTranslation = isSelectTranslation;
    }

    public boolean isTopDownNameMissing(CharSequence columnName) {
        return topDownNameSet.excludes(columnName);
    }

    public boolean isUpdate() {
        return isUpdateModel;
    }

    public void moveGroupByFrom(QueryModel model) {
        this.groupBy.addAll(model.groupBy);
        // clear the source
        model.groupBy.clear();
    }

    public void moveJoinAliasFrom(QueryModel that) {
        final ExpressionNode alias = that.alias;
        if (alias != null && !Chars.startsWith(alias.token, SUB_QUERY_ALIAS_PREFIX)) {
            setAlias(alias);
            addModelAliasIndex(alias, 0);
        }
    }

    public void moveLimitFrom(QueryModel baseModel) {
        this.limitLo = baseModel.getLimitLo();
        this.limitHi = baseModel.getLimitHi();
        baseModel.setLimit(null, null);
    }

    public void moveSampleByFrom(QueryModel model) {
        this.sampleBy = model.sampleBy;
        this.sampleByUnit = model.sampleByUnit;
        this.sampleByFill.clear();
        this.sampleByFill.addAll(model.sampleByFill);
        this.sampleByTimezoneName = model.sampleByTimezoneName;
        this.sampleByOffset = model.sampleByOffset;

        // clear the source
        model.clearSampleBy();
    }

    /**
     * Optimiser may be attempting to order join clauses several times.
     * Every time ordering takes place optimiser will keep at most two lists:
     * one is last known order the other is new order. If new order cost is better
     * optimiser will replace last known order with new one.
     * 

* To facilitate this behaviour the function will always return non-current list. * * @return non current order list. */ public IntList nextOrderedJoinModels() { IntList ordered = orderedJoinModels == orderedJoinModels1 ? orderedJoinModels2 : orderedJoinModels1; ordered.clear(); return ordered; } /* * Splits "where" clauses into "and" chunks */ public ObjList parseWhereClause() { ExpressionNode n = getWhereClause(); // pre-order traversal sqlNodeStack.clear(); while (!sqlNodeStack.isEmpty() || n != null) { if (n != null && n.token != null) { if (isAndKeyword(n.token)) { if (n.rhs != null) { sqlNodeStack.push(n.rhs); } n = n.lhs; } else { addParsedWhereNode(n, false); n = null; } } else { n = sqlNodeStack.poll(); } } return getParsedWhere(); } public void removeDependency(int index) { dependencies.remove(index); } public void replaceJoinModel(int pos, QueryModel model) { joinModels.setQuick(pos, model); } public void setIsUpdate(boolean isUpdate) { this.isUpdateModel = isUpdate; } public void setLimit(ExpressionNode lo, ExpressionNode hi) { this.limitLo = lo; this.limitHi = hi; } public void setLimitAdvice(ExpressionNode lo, ExpressionNode hi) { this.limitAdviceLo = lo; this.limitAdviceHi = hi; } public void setSampleBy(ExpressionNode sampleBy, ExpressionNode sampleByUnit) { this.sampleBy = sampleBy; this.sampleByUnit = sampleByUnit; } @Override public void toSink(CharSink sink) { if (modelType == ExecutionModel.QUERY) { toSink0(sink, false, false); } else if (modelType == ExecutionModel.UPDATE) { updateToSink(sink); } } @Override public CharSequence translateAlias(CharSequence column) { return aliasToColumnNameMap.get(column); } private static void aliasToSink(CharSequence alias, CharSink sink) { sink.put(' '); boolean quote = Chars.indexOf(alias, ' ') != -1; if (quote) { sink.put('\'').put(alias).put('\''); } else { sink.put(alias); } } private String getSelectModelTypeText() { return modelTypeName.get(selectModelType); } private void sinkColumns(CharSink sink, ObjList columns) { for (int i = 0, n = columns.size(); i < n; i++) { if (i > 0) { sink.put(", "); } QueryColumn column = columns.getQuick(i); CharSequence name = column.getName(); CharSequence alias = column.getAlias(); ExpressionNode ast = column.getAst(); ast.toSink(sink); if (column instanceof AnalyticColumn || name == null) { if (alias != null) { aliasToSink(alias, sink); } // this can only be analytic column if (name != null) { AnalyticColumn ac = (AnalyticColumn) column; sink.put(" over ("); final ObjList partitionBy = ac.getPartitionBy(); if (partitionBy.size() > 0) { sink.put("partition by "); for (int k = 0, z = partitionBy.size(); k < z; k++) { if (k > 0) { sink.put(", "); } partitionBy.getQuick(k).toSink(sink); } } final ObjList orderBy = ac.getOrderBy(); if (orderBy.size() > 0) { if (partitionBy.size() > 0) { sink.put(' '); } sink.put("order by "); for (int k = 0, z = orderBy.size(); k < z; k++) { if (k > 0) { sink.put(", "); } orderBy.getQuick(k).toSink(sink); if (ac.getOrderByDirection().getQuick(k) == 1) { sink.put(" desc"); } } } sink.put(')'); } } else { // do not repeat alias when it is the same as AST token, provided AST is a literal if (alias != null && (ast.type != ExpressionNode.LITERAL || !ast.token.equals(alias))) { aliasToSink(alias, sink); } } } } //returns textual description of this model, e.g. select-choose [top-down-columns] bottom-up-columns from X ... private void toSink0(CharSink sink, boolean joinSlave, boolean showOrderBy) { final boolean hasColumns = this.topDownColumns.size() > 0 || this.bottomUpColumns.size() > 0; if (hasColumns) { sink.put(getSelectModelTypeText()); if (this.topDownColumns.size() > 0) { sink.put(' '); sink.put('['); sinkColumns(sink, this.topDownColumns); sink.put(']'); } if (this.bottomUpColumns.size() > 0) { sink.put(' '); sinkColumns(sink, this.bottomUpColumns); } sink.put(" from "); } if (tableName != null) { tableName.toSink(sink); } else { sink.put('('); nestedModel.toSink0(sink, false, showOrderBy); sink.put(')'); } if (alias != null) { aliasToSink(alias.token, sink); } if (getLatestByType() != LATEST_BY_NEW && timestamp != null) { sink.put(" timestamp ("); timestamp.toSink(sink); sink.put(')'); } if (getLatestByType() == LATEST_BY_DEPRECATED && getLatestBy().size() > 0) { sink.put(" latest by "); for (int i = 0, n = getLatestBy().size(); i < n; i++) { if (i > 0) { sink.put(','); } getLatestBy().getQuick(i).toSink(sink); } } if (orderedJoinModels.size() > 1) { for (int i = 0, n = orderedJoinModels.size(); i < n; i++) { QueryModel model = joinModels.getQuick(orderedJoinModels.getQuick(i)); if (model != this) { switch (model.getJoinType()) { case JOIN_OUTER: sink.put(" outer join "); break; case JOIN_ASOF: sink.put(" asof join "); break; case JOIN_SPLICE: sink.put(" splice join "); break; case JOIN_CROSS: sink.put(" cross join "); break; case JOIN_LT: sink.put(" lt join "); break; default: sink.put(" join "); break; } if (model.getWhereClause() != null) { sink.put('('); model.toSink0(sink, true, showOrderBy); sink.put(')'); if (model.getAlias() != null) { aliasToSink(model.getAlias().token, sink); } else if (model.getTableName() != null) { aliasToSink(model.getTableName().token, sink); } } else { model.toSink0(sink, true, showOrderBy); } JoinContext jc = model.getContext(); if (jc != null && jc.aIndexes.size() > 0) { // join clause sink.put(" on "); for (int k = 0, z = jc.aIndexes.size(); k < z; k++) { if (k > 0) { sink.put(" and "); } jc.aNodes.getQuick(k).toSink(sink); sink.put(" = "); jc.bNodes.getQuick(k).toSink(sink); } } if (model.getPostJoinWhereClause() != null) { sink.put(" post-join-where "); model.getPostJoinWhereClause().toSink(sink); } } } } if (getWhereClause() != null) { sink.put(" where "); whereClause.toSink(sink); } if (constWhereClause != null) { sink.put(" const-where "); constWhereClause.toSink(sink); } if (!joinSlave && postJoinWhereClause != null) { sink.put(" post-join-where "); postJoinWhereClause.toSink(sink); } if (getLatestByType() == LATEST_BY_NEW && getLatestBy().size() > 0) { sink.put(" latest on "); timestamp.toSink(sink); sink.put(" partition by "); for (int i = 0, n = getLatestBy().size(); i < n; i++) { if (i > 0) { sink.put(','); } getLatestBy().getQuick(i).toSink(sink); } } if (sampleBy != null) { sink.put(" sample by "); sampleBy.toSink(sink); final int fillCount = sampleByFill.size(); if (fillCount > 0) { sink.put(" fill("); sink.put(sampleByFill.getQuick(0)); if (fillCount > 1) { for (int i = 1; i < fillCount; i++) { sink.put(','); sink.put(sampleByFill.getQuick(i)); } } sink.put(')'); } if (sampleByTimezoneName != null || sampleByOffset != null) { sink.put(" align to calendar"); if (sampleByTimezoneName != null) { sink.put(" time zone "); sink.put(sampleByTimezoneName); } if (sampleByOffset != null) { sink.put(" with offset "); sink.put(sampleByOffset); } } } if (showOrderBy && orderBy.size() > 0) { sink.put(" order by "); for (int i = 0, n = orderBy.size(); i < n; i++) { if (i > 0) { sink.put(", "); } sink.put(orderBy.get(i)); if (orderByDirection.get(i) == 1) { sink.put(" desc"); } } } else if (orderHash.size() > 0 && orderBy.size() > 0) { sink.put(" order by "); ObjList columnNames = orderHash.keys(); for (int i = 0, n = columnNames.size(); i < n; i++) { if (i > 0) { sink.put(", "); } CharSequence key = columnNames.getQuick(i); sink.put(key); if (orderHash.get(key) == 1) { sink.put(" desc"); } } } if (getLimitLo() != null || getLimitHi() != null) { sink.put(" limit "); if (getLimitLo() != null) { getLimitLo().toSink(sink); } if (getLimitHi() != null) { sink.put(','); getLimitHi().toSink(sink); } } if (unionModel != null) { if (setOperationType == QueryModel.SET_OPERATION_INTERSECT) { sink.put(" intersect "); } else if (setOperationType == QueryModel.SET_OPERATION_EXCEPT) { sink.put(" except "); } else { sink.put(" union "); if (setOperationType == QueryModel.SET_OPERATION_UNION_ALL) { sink.put("all "); } } unionModel.toSink0(sink, false, showOrderBy); } } private void updateToSink(CharSink sink) { sink.put("update "); tableName.toSink(sink); if (alias != null) { sink.put(" as"); aliasToSink(alias.token, sink); } sink.put(" set "); for (int i = 0, n = getUpdateExpressions().size(); i < n; i++) { if (i > 0) { sink.put(','); } CharSequence columnExpr = getUpdateExpressions().get(i).token; sink.put(columnExpr); sink.put(" = "); QueryColumn setColumn = getNestedModel().getColumns().getQuick(i); setColumn.getAst().toSink(sink); } if (getNestedModel() != null) { sink.put(" from ("); getNestedModel().toSink(sink); sink.put(")"); } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; QueryModel that = (QueryModel) o; // joinModels always contain this as the first element, so we need to compare them manually. if (joinModels.size() != that.joinModels.size()) { return false; } for (int i = 1, n = joinModels.size(); i < n; i++) { if (!joinModels.getQuick(i).equals(that.joinModels.getQuick(i))) { return false; } } // ArrayDeque doesn't implement equals and hashCode, so we deal with sqlNodeStack separately. if (sqlNodeStack.size() != that.sqlNodeStack.size()) { return false; } Iterator i1 = sqlNodeStack.iterator(); Iterator i2 = that.sqlNodeStack.iterator(); while (i1.hasNext() && i2.hasNext()) { ExpressionNode n1 = i1.next(); ExpressionNode n2 = i2.next(); if (!Objects.equals(n1, n2)) { return false; } } return orderByPosition == that.orderByPosition && latestByType == that.latestByType && tableVersion == that.tableVersion && joinType == that.joinType && joinKeywordPosition == that.joinKeywordPosition && limitPosition == that.limitPosition && isLimitImplemented == that.isLimitImplemented && isSelectTranslation == that.isSelectTranslation && selectModelType == that.selectModelType && nestedModelIsSubQuery == that.nestedModelIsSubQuery && distinct == that.distinct && setOperationType == that.setOperationType && modelPosition == that.modelPosition && orderByAdviceMnemonic == that.orderByAdviceMnemonic && tableId == that.tableId && isUpdateModel == that.isUpdateModel && modelType == that.modelType && artificialStar == that.artificialStar && Objects.equals(bottomUpColumns, that.bottomUpColumns) && Objects.equals(topDownNameSet, that.topDownNameSet) && Objects.equals(topDownColumns, that.topDownColumns) && Objects.equals(aliasToColumnNameMap, that.aliasToColumnNameMap) && Objects.equals(columnNameToAliasMap, that.columnNameToAliasMap) && Objects.equals(aliasToColumnMap, that.aliasToColumnMap) && Objects.equals(bottomUpColumnNames, that.bottomUpColumnNames) && Objects.equals(orderBy, that.orderBy) && Objects.equals(groupBy, that.groupBy) && Objects.equals(orderByDirection, that.orderByDirection) && Objects.equals(dependencies, that.dependencies) && Objects.equals(orderedJoinModels1, that.orderedJoinModels1) && Objects.equals(orderedJoinModels2, that.orderedJoinModels2) && Objects.equals(columnAliasIndexes, that.columnAliasIndexes) && Objects.equals(modelAliasIndexes, that.modelAliasIndexes) && Objects.equals(expressionModels, that.expressionModels) && Objects.equals(parsedWhere, that.parsedWhere) && Objects.equals(parsedWhereConstants, that.parsedWhereConstants) && Objects.equals(orderHash, that.orderHash) && Objects.equals(joinColumns, that.joinColumns) && Objects.equals(sampleByFill, that.sampleByFill) && Objects.equals(latestBy, that.latestBy) && Objects.equals(orderByAdvice, that.orderByAdvice) && Objects.equals(orderByDirectionAdvice, that.orderByDirectionAdvice) && Objects.equals(withClauseModel, that.withClauseModel) && Objects.equals(updateSetColumns, that.updateSetColumns) && Objects.equals(updateTableColumnTypes, that.updateTableColumnTypes) && Objects.equals(updateTableColumnNames, that.updateTableColumnNames) && Objects.equals(sampleByTimezoneName, that.sampleByTimezoneName) && Objects.equals(sampleByOffset, that.sampleByOffset) && Objects.equals(whereClause, that.whereClause) && Objects.equals(backupWhereClause, that.backupWhereClause) && Objects.equals(postJoinWhereClause, that.postJoinWhereClause) && Objects.equals(constWhereClause, that.constWhereClause) && Objects.equals(nestedModel, that.nestedModel) && Objects.equals(tableName, that.tableName) && Objects.equals(tableNameFunction, that.tableNameFunction) && Objects.equals(alias, that.alias) && Objects.equals(timestamp, that.timestamp) && Objects.equals(sampleBy, that.sampleBy) && Objects.equals(sampleByUnit, that.sampleByUnit) && Objects.equals(context, that.context) && Objects.equals(joinCriteria, that.joinCriteria) && Objects.equals(orderedJoinModels, that.orderedJoinModels) && Objects.equals(limitLo, that.limitLo) && Objects.equals(limitHi, that.limitHi) && Objects.equals(limitAdviceLo, that.limitAdviceLo) && Objects.equals(limitAdviceHi, that.limitAdviceHi) && Objects.equals(unionModel, that.unionModel) && Objects.equals(updateTableModel, that.updateTableModel) && Objects.equals(updateTableName, that.updateTableName); } @Override public int hashCode() { int hash = super.hashCode(); // joinModels always contain this as the first element, so we need to hash them manually. for (int i = 1, n = joinModels.size(); i < n; i++) { hash = 31 * hash + Objects.hash(joinModels.getQuick(i)); } // ArrayDeque doesn't implement equals and hashCode, so we deal with sqlNodeStack separately. for (ExpressionNode node : sqlNodeStack) { hash = 31 * hash + Objects.hash(node); } return 31 * hash + Objects.hash( bottomUpColumns, topDownNameSet, topDownColumns, aliasToColumnNameMap, columnNameToAliasMap, aliasToColumnMap, bottomUpColumnNames, orderBy, orderByPosition, groupBy, orderByDirection, dependencies, orderedJoinModels1, orderedJoinModels2, columnAliasIndexes, modelAliasIndexes, expressionModels, parsedWhere, parsedWhereConstants, orderHash, joinColumns, sampleByFill, latestBy, orderByAdvice, orderByDirectionAdvice, withClauseModel, updateSetColumns, updateTableColumnTypes, updateTableColumnNames, sampleByTimezoneName, sampleByOffset, latestByType, whereClause, backupWhereClause, postJoinWhereClause, constWhereClause, nestedModel, tableName, tableVersion, tableNameFunction, alias, timestamp, sampleBy, sampleByUnit, context, joinCriteria, joinType, joinKeywordPosition, orderedJoinModels, limitLo, limitHi, limitPosition, limitAdviceLo, limitAdviceHi, isLimitImplemented, isSelectTranslation, selectModelType, nestedModelIsSubQuery, distinct, unionModel, setOperationType, modelPosition, orderByAdviceMnemonic, tableId, isUpdateModel, modelType, updateTableModel, updateTableName, artificialStar ); } public static final class QueryModelFactory implements ObjectFactory { @Override public QueryModel newInstance() { return new QueryModel(); } } static { modelTypeName.extendAndSet(SELECT_MODEL_NONE, "select"); modelTypeName.extendAndSet(SELECT_MODEL_CHOOSE, "select-choose"); modelTypeName.extendAndSet(SELECT_MODEL_VIRTUAL, "select-virtual"); modelTypeName.extendAndSet(SELECT_MODEL_ANALYTIC, "select-analytic"); modelTypeName.extendAndSet(SELECT_MODEL_GROUP_BY, "select-group-by"); modelTypeName.extendAndSet(SELECT_MODEL_DISTINCT, "select-distinct"); modelTypeName.extendAndSet(SELECT_MODEL_CURSOR, "select-cursor"); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy