Search in sources :

Example 6 with ExprNode

use of com.questdb.griffin.common.ExprNode in project questdb by bluestreak01.

the class SqlLexerOptimiser method parseJoin.

private QueryModel parseJoin(CharSequence tok, int joinType, QueryModel parent) throws ParserException {
    QueryModel joinModel = queryModelPool.next();
    joinModel.setJoinType(joinType);
    if (!Chars.equals(tok, "join")) {
        expectTok("join");
    }
    tok = expectTableNameOrSubQuery();
    if (Chars.equals(tok, '(')) {
        joinModel.setNestedModel(parseSubQuery());
        expectTok(')');
    } else {
        parseSelectFrom(joinModel, tok, parent);
    }
    tok = optTok();
    if (tok != null && tableAliasStop.excludes(tok)) {
        lexer.unparse();
        joinModel.setAlias(expr());
    } else {
        lexer.unparse();
    }
    tok = optTok();
    if (joinType == QueryModel.JOIN_CROSS && tok != null && Chars.equals(tok, "on")) {
        throw ParserException.$(lexer.position(), "Cross joins cannot have join clauses");
    }
    switch(joinType) {
        case QueryModel.JOIN_ASOF:
            if (tok == null || !Chars.equals("on", tok)) {
                lexer.unparse();
                break;
            }
        // intentional fall through
        case QueryModel.JOIN_INNER:
        case QueryModel.JOIN_OUTER:
            expectTok(tok, "on");
            astBuilder.reset();
            exprParser.parseExpr(lexer, astBuilder);
            ExprNode expr;
            switch(astBuilder.size()) {
                case 0:
                    throw ParserException.$(lexer.position(), "Expression expected");
                case 1:
                    expr = astBuilder.poll();
                    if (expr.type == ExprNode.LITERAL) {
                        do {
                            joinModel.addJoinColumn(expr);
                        } while ((expr = astBuilder.poll()) != null);
                    } else {
                        joinModel.setJoinCriteria(expr);
                    }
                    break;
                default:
                    // this code handles "join on (a,b,c)", e.g. list of columns
                    while ((expr = astBuilder.poll()) != null) {
                        if (expr.type != ExprNode.LITERAL) {
                            throw ParserException.$(lexer.position(), "Column name expected");
                        }
                        joinModel.addJoinColumn(expr);
                    }
                    break;
            }
            break;
        default:
            lexer.unparse();
    }
    return joinModel;
}
Also used : ExprNode(com.questdb.griffin.common.ExprNode)

Example 7 with ExprNode

use of com.questdb.griffin.common.ExprNode in project questdb by bluestreak01.

the class SqlLexerOptimiser method rewriteSelectClause0.

@NotNull
private QueryModel rewriteSelectClause0(QueryModel model) throws ParserException {
    assert model.getNestedModel() != null;
    QueryModel groupByModel = queryModelPool.next();
    groupByModel.setSelectModelType(QueryModel.SELECT_MODEL_GROUP_BY);
    QueryModel outerModel = queryModelPool.next();
    outerModel.setSelectModelType(QueryModel.SELECT_MODEL_VIRTUAL);
    QueryModel innerModel = queryModelPool.next();
    innerModel.setSelectModelType(QueryModel.SELECT_MODEL_VIRTUAL);
    QueryModel analyticModel = queryModelPool.next();
    analyticModel.setSelectModelType(QueryModel.SELECT_MODEL_ANALYTIC);
    QueryModel translatingModel = queryModelPool.next();
    translatingModel.setSelectModelType(QueryModel.SELECT_MODEL_CHOOSE);
    boolean useInnerModel = false;
    boolean useAnalyticModel = false;
    boolean useGroupByModel = false;
    boolean useOuterModel = false;
    final ObjList<QueryColumn> columns = model.getColumns();
    final QueryModel baseModel = model.getNestedModel();
    final boolean hasJoins = baseModel.getJoinModels().size() > 1;
    // sample by clause should be promoted to all of the models as well as validated
    final ExprNode sampleBy = baseModel.getSampleBy();
    if (sampleBy != null) {
        final ExprNode timestamp = baseModel.getTimestamp();
        if (timestamp == null) {
            throw ParserException.$(sampleBy.position, "TIMESTAMP column is not defined");
        }
        groupByModel.setSampleBy(sampleBy);
        groupByModel.setTimestamp(timestamp);
        baseModel.setSampleBy(null);
        baseModel.setTimestamp(null);
        createSelectColumn(timestamp.token, timestamp, baseModel, translatingModel, innerModel, groupByModel, analyticModel, outerModel);
    }
    // create virtual columns from select list
    for (int i = 0, k = columns.size(); i < k; i++) {
        final QueryColumn qc = columns.getQuick(i);
        final boolean analytic = qc instanceof AnalyticColumn;
        // fail-fast if this is an arithmetic expression where we expect analytic function
        if (analytic && qc.getAst().type != ExprNode.FUNCTION) {
            throw ParserException.$(qc.getAst().position, "Analytic function expected");
        }
        if (qc.getAst().type == ExprNode.LITERAL) {
            // select a.f, b.f from ....
            if (Chars.endsWith(qc.getAst().token, '*')) {
                createSelectColumnsForWildcard(qc, baseModel, translatingModel, innerModel, groupByModel, analyticModel, outerModel, hasJoins);
            } else {
                createSelectColumn(qc.getAlias(), qc.getAst(), baseModel, translatingModel, innerModel, groupByModel, analyticModel, outerModel);
            }
        } else {
            // we can add it to group-by model right away
            if (qc.getAst().type == ExprNode.FUNCTION) {
                if (analytic) {
                    analyticModel.addColumn(qc);
                    // ensure literals referenced by analytic column are present in nested models
                    emitLiterals(qc.getAst(), translatingModel, innerModel, baseModel);
                    useAnalyticModel = true;
                    continue;
                } else if (FunctionFactories.isAggregate(qc.getAst().token)) {
                    groupByModel.addColumn(qc);
                    // group-by column references might be needed when we have
                    // outer model supporting arithmetic such as:
                    // select sum(a)+sum(b) ....
                    outerModel.addColumn(nextColumn(qc.getAlias()));
                    // pull out literals
                    emitLiterals(qc.getAst(), translatingModel, innerModel, baseModel);
                    useGroupByModel = true;
                    continue;
                }
            }
            // this is not a direct call to aggregation function, in which case
            // we emit aggregation function into group-by model and leave the
            // rest in outer model
            int beforeSplit = groupByModel.getColumns().size();
            emitAggregates(qc.getAst(), groupByModel);
            if (beforeSplit < groupByModel.getColumns().size()) {
                outerModel.addColumn(qc);
                // pull literals from newly created group-by columns into both of underlying models
                for (int j = beforeSplit, n = groupByModel.getColumns().size(); j < n; j++) {
                    emitLiterals(groupByModel.getColumns().getQuick(i).getAst(), translatingModel, innerModel, baseModel);
                }
                useGroupByModel = true;
                useOuterModel = true;
            } else {
                // there were no aggregation functions emitted therefore
                // this is just a function that goes into virtual model
                innerModel.addColumn(qc);
                useInnerModel = true;
                // we also create column that references this inner layer from outer layer,
                // for example when we have:
                // select a, b+c ...
                // it should translate to:
                // select a, x from (select a, b+c x from (select a,b,c ...))
                final QueryColumn innerColumn = nextColumn(qc.getAlias());
                // pull literals only into translating model
                emitLiterals(qc.getAst(), translatingModel, null, baseModel);
                groupByModel.addColumn(innerColumn);
                analyticModel.addColumn(innerColumn);
                outerModel.addColumn(innerColumn);
            }
        }
    }
    // fail if we have both analytic and group-by models
    if (useAnalyticModel && useGroupByModel) {
        throw ParserException.$(0, "Analytic function is not allowed in context of aggregation. Use sub-query.");
    }
    // check if translating model is redundant, e.g.
    // that it neither chooses between tables nor renames columns
    boolean translationIsRedundant = useInnerModel || useGroupByModel || useAnalyticModel;
    if (translationIsRedundant) {
        for (int i = 0, n = translatingModel.getColumns().size(); i < n; i++) {
            QueryColumn column = translatingModel.getColumns().getQuick(i);
            if (!column.getAst().token.equals(column.getAlias())) {
                translationIsRedundant = false;
            }
        }
    }
    QueryModel root;
    if (translationIsRedundant) {
        root = baseModel;
    } else {
        root = translatingModel;
        translatingModel.setNestedModel(baseModel);
    }
    if (useInnerModel) {
        innerModel.setNestedModel(root);
        root = innerModel;
    }
    if (useAnalyticModel) {
        analyticModel.setNestedModel(root);
        root = analyticModel;
    } else if (useGroupByModel) {
        groupByModel.setNestedModel(root);
        root = groupByModel;
        if (useOuterModel) {
            outerModel.setNestedModel(root);
            root = outerModel;
        }
    }
    if (!useGroupByModel && groupByModel.getSampleBy() != null) {
        throw ParserException.$(groupByModel.getSampleBy().position, "at least one aggregation function must be present in 'select' clause");
    }
    return root;
}
Also used : ExprNode(com.questdb.griffin.common.ExprNode) NotNull(org.jetbrains.annotations.NotNull)

Example 8 with ExprNode

use of com.questdb.griffin.common.ExprNode in project questdb by bluestreak01.

the class SqlLexerOptimiser method parseWithClauses.

private void parseWithClauses(QueryModel model) throws ParserException {
    do {
        ExprNode name = expectLiteral();
        if (model.getWithClause(name.token) != null) {
            throw ParserException.$(name.position, "duplicate name");
        }
        expectTok("as");
        expectTok('(');
        int lo, hi;
        lo = lexer.position();
        QueryModel m = parseSubQuery();
        hi = lexer.position();
        WithClauseModel wcm = withClauseModelPool.next();
        wcm.of(lo + 1, hi, m);
        expectTok(')');
        model.addWithClause(name.token, wcm);
        CharSequence tok = optTok();
        if (tok == null || !Chars.equals(tok, ',')) {
            lexer.unparse();
            break;
        }
    } while (true);
}
Also used : ExprNode(com.questdb.griffin.common.ExprNode) FlyweightCharSequence(com.questdb.std.str.FlyweightCharSequence)

Example 9 with ExprNode

use of com.questdb.griffin.common.ExprNode in project questdb by bluestreak01.

the class SqlLexerOptimiser method rewriteOrderBy.

/**
 * Rewrites order by clause to achieve simple column resolution for model parser.
 * Order by must never reference column that doesn't exist in its own select list.
 * <p>
 * Because order by clause logically executes after "select" it must be able to
 * reference results of arithmetic expression, aggregation function results, arithmetic with
 * aggregation results and analytic functions. Somewhat contradictory to this order by must
 * also be able to reference columns of table or sub-query that are not even in select clause.
 *
 * @param model inbound model
 * @return outbound model
 * @throws ParserException when column names are ambiguos or not found at all.
 */
private QueryModel rewriteOrderBy(QueryModel model) throws ParserException {
    // find base model and check if there is "group-by" model in between
    // when we are dealing with "group by" model some of the implicit "order by" columns have to be dropped,
    // for example:
    // select a, sum(b) from T order by c
    // 
    // above is valid but sorting on "c" would be redundant. However in the following example
    // 
    // select a, b from T order by c
    // 
    // ordering is does affect query result
    QueryModel result = model;
    QueryModel base = model;
    QueryModel baseParent = model;
    QueryModel wrapper = null;
    final int modelColumnCount = model.getColumns().size();
    boolean groupBy = false;
    while (base.getColumns().size() > 0) {
        baseParent = base;
        base = base.getNestedModel();
        groupBy = groupBy || baseParent.getSelectModelType() == QueryModel.SELECT_MODEL_GROUP_BY;
    }
    // find out how "order by" columns are referenced
    ObjList<ExprNode> orderByNodes = base.getOrderBy();
    int sz = orderByNodes.size();
    if (sz > 0) {
        boolean ascendColumns = true;
        // for each order by column check how deep we need to go between "model" and "base"
        for (int i = 0; i < sz; i++) {
            final ExprNode orderBy = orderByNodes.getQuick(i);
            final CharSequence column = orderBy.token;
            final int dot = Chars.indexOf(column, '.');
            // is this a table reference?
            if (dot > -1 || model.getColumnNameTypeMap().excludes(column)) {
                // validate column
                getIndexOfTableForColumn(base, column, dot, orderBy.position);
                // this condition is to ignore order by columns that are not in select and behind group by
                if (ascendColumns && base != model) {
                    // check if column is aliased as either
                    // "x y" or "tab.x y" or "t.x y", where "t" is alias of table "tab"
                    final CharSequenceObjHashMap<CharSequence> map = baseParent.getColumnToAliasMap();
                    int index = map.keyIndex(column);
                    if (index > -1 && dot > -1) {
                        // we have the following that are true:
                        // 1. column does have table alias, e.g. tab.x
                        // 2. column definitely exists
                        // 3. column is _not_ referenced as select tab.x from tab
                        // 
                        // lets check if column is referenced as select x from tab
                        // this will determine is column is referenced by select at all
                        index = map.keyIndex(column, dot + 1, column.length());
                    }
                    if (index < 0) {
                        // we have found alias, rewrite order by column
                        orderBy.token = map.valueAt(index);
                    } else {
                        // when we have group by model, ascent is not possible
                        if (groupBy) {
                            ascendColumns = false;
                        } else {
                            if (baseParent.getSelectModelType() != QueryModel.SELECT_MODEL_CHOOSE) {
                                QueryModel synthetic = queryModelPool.next();
                                synthetic.setSelectModelType(QueryModel.SELECT_MODEL_CHOOSE);
                                for (int j = 0, z = baseParent.getColumns().size(); j < z; j++) {
                                    synthetic.addColumn(baseParent.getColumns().getQuick(j));
                                }
                                synthetic.setNestedModel(base);
                                baseParent.setNestedModel(synthetic);
                                baseParent = synthetic;
                            }
                            // if base parent model is already "choose" type, use that and ascend alias all the way up
                            CharSequence alias = createColumnAlias(column, dot, baseParent.getColumnNameTypeMap());
                            baseParent.addColumn(nextColumn(alias, column));
                            // do we have more than one parent model?
                            if (model != baseParent) {
                                QueryModel m = model;
                                do {
                                    m.addColumn(nextColumn(alias));
                                    m = m.getNestedModel();
                                } while (m != baseParent);
                            }
                            orderBy.token = alias;
                            if (wrapper == null) {
                                wrapper = queryModelPool.next();
                                wrapper.setSelectModelType(QueryModel.SELECT_MODEL_CHOOSE);
                                for (int j = 0; j < modelColumnCount; j++) {
                                    wrapper.addColumn(nextColumn(model.getColumns().getQuick(j).getAlias()));
                                }
                                result = wrapper;
                                wrapper.setNestedModel(model);
                            }
                        }
                    }
                }
            }
            if (ascendColumns && base != model) {
                model.addOrderBy(orderBy, base.getOrderByDirection().getQuick(i));
            }
        }
        if (base != model) {
            base.clearOrderBy();
        }
    }
    QueryModel nested = base.getNestedModel();
    if (nested != null) {
        QueryModel rewritten = rewriteOrderBy(nested);
        if (rewritten != nested) {
            base.setNestedModel(rewritten);
        }
    }
    ObjList<QueryModel> joinModels = base.getJoinModels();
    for (int i = 1, n = joinModels.size(); i < n; i++) {
        // we can ignore result of order by rewrite for because
        // 1. when join model is not a sub-query it will always have all the fields, so order by wouldn't
        // introduce synthetic model (no column needs to be hidden)
        // 2. when join model is a sub-query it will have nested model, which can be rewritten. Parent model
        // would remain the same again.
        rewriteOrderBy(joinModels.getQuick(i));
    }
    return result;
}
Also used : ExprNode(com.questdb.griffin.common.ExprNode) FlyweightCharSequence(com.questdb.std.str.FlyweightCharSequence)

Example 10 with ExprNode

use of com.questdb.griffin.common.ExprNode in project questdb by bluestreak01.

the class SqlLexerOptimiser method parseFromClause.

private void parseFromClause(QueryModel model, QueryModel masterModel) throws ParserException {
    CharSequence tok = expectTableNameOrSubQuery();
    if (Chars.equals(tok, '(')) {
        model.setNestedModel(parseSubQuery());
        // expect closing bracket
        expectTok(')');
        tok = optTok();
        if (tok != null && tableAliasStop.excludes(tok)) {
            model.setAlias(literal(tok));
            tok = optTok();
        }
        // expect [timestamp(column)]
        ExprNode timestamp = parseTimestamp(tok);
        if (timestamp != null) {
            model.setTimestamp(timestamp);
            tok = optTok();
        }
    } else {
        parseSelectFrom(model, tok, masterModel);
        tok = optTok();
        if (tok != null && tableAliasStop.excludes(tok)) {
            model.setAlias(literal(tok));
            tok = optTok();
        }
        // expect [timestamp(column)]
        ExprNode timestamp = parseTimestamp(tok);
        if (timestamp != null) {
            model.setTimestamp(timestamp);
            tok = optTok();
        }
        if (Chars.equalsNc("latest", tok)) {
            parseLatestBy(model);
            tok = optTok();
        }
    }
    // expect multiple [[inner | outer | cross] join]
    int joinType;
    while (tok != null && (joinType = joinStartSet.get(tok)) != -1) {
        model.addJoinModel(parseJoin(tok, joinType, masterModel));
        tok = optTok();
    }
    if (tok != null && Chars.equals(tok, "where")) {
        model.setWhereClause(expr());
        tok = optTok();
    }
    if (tok != null && Chars.equals(tok, "sample")) {
        expectTok("by");
        model.setSampleBy(expectLiteral());
        tok = optTok();
    }
    if (tok != null && Chars.equals(tok, "order")) {
        expectTok("by");
        do {
            ExprNode n = expectLiteral();
            tok = optTok();
            if (tok != null && Chars.equalsIgnoreCase(tok, "desc")) {
                model.addOrderBy(n, QueryModel.ORDER_DIRECTION_DESCENDING);
                tok = optTok();
            } else {
                model.addOrderBy(n, QueryModel.ORDER_DIRECTION_ASCENDING);
                if (tok != null && Chars.equalsIgnoreCase(tok, "asc")) {
                    tok = optTok();
                }
            }
            if (model.getOrderBy().size() >= MAX_ORDER_BY_COLUMNS) {
                throw err("Too many columns");
            }
        } while (tok != null && Chars.equals(tok, ','));
    }
    // expect [limit]
    if (tok != null && Chars.equals(tok, "limit")) {
        ExprNode lo = expr();
        ExprNode hi = null;
        tok = optTok();
        if (tok != null && Chars.equals(tok, ',')) {
            hi = expr();
        } else {
            lexer.unparse();
        }
        model.setLimit(lo, hi);
    } else {
        lexer.unparse();
    }
}
Also used : ExprNode(com.questdb.griffin.common.ExprNode) FlyweightCharSequence(com.questdb.std.str.FlyweightCharSequence)

Aggregations

ExprNode (com.questdb.griffin.common.ExprNode)40 FlyweightCharSequence (com.questdb.std.str.FlyweightCharSequence)14 NumericException (com.questdb.common.NumericException)2 CairoException (com.questdb.cairo.CairoException)1 TableReader (com.questdb.cairo.TableReader)1 EntryLockedException (com.questdb.cairo.pool.ex.EntryLockedException)1 RecordColumnMetadata (com.questdb.common.RecordColumnMetadata)1 RecordMetadata (com.questdb.common.RecordMetadata)1 IntrinsicModel (com.questdb.griffin.lexer.model.IntrinsicModel)1 NotNull (org.jetbrains.annotations.NotNull)1