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;
}
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;
}
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);
}
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;
}
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();
}
}
Aggregations