/*
* Copyright (C) 2014 Indeed Inc.
*
* 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.
*/
/*****************************************************************************
* Copyright (C) Codehaus.org *
* ------------------------------------------------------------------------- *
* 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 com.indeed.imhotep.sql.parser;
import static com.indeed.imhotep.sql.parser.TerminalParser.term;
import static com.indeed.imhotep.sql.parser.TerminalParser.phrase;
import com.indeed.imhotep.sql.ast.*;
import org.codehaus.jparsec.OperatorTable;
import org.codehaus.jparsec.Parser;
import org.codehaus.jparsec.Parsers;
import org.codehaus.jparsec.Parser.Reference;
import org.codehaus.jparsec.functors.Binary;
import org.codehaus.jparsec.functors.Map2;
import org.codehaus.jparsec.functors.Unary;
import org.codehaus.jparsec.misc.Mapper;
/**
* Parser for expressions.
*
* @author Ben Yu
*/
public final class ExpressionParser {
static final Parser<Expression> NUMBER =
curry(NumberExpression.class).sequence(TerminalParser.NUMBER);
static final Parser<Expression> SIGNED_NUMBER =
Parsers.sequence(term("-").retn("-").optional(), TerminalParser.NUMBER, new Map2<String, String, Expression>() {
@Override
public Expression map(String o, String s) {
final String val;
if(o != null) {
val = "-"+s;
} else {
val = s;
}
return new NumberExpression(val);
}
});
static final Parser<Expression> STRING =
curry(StringExpression.class).sequence(TerminalParser.STRING);
static final Parser<Expression> UNQUOTED_STRING =
curry(StringExpression.class).sequence(Parsers.or(TerminalParser.STRING, TerminalParser.NUMBER, TerminalParser.NAME).many1().source());
static final Parser<Expression> NAME =
curry(NameExpression.class).sequence(TerminalParser.NAME);
public static Expression parseExpression(String str) {
return TerminalParser.parse(expression(), str);
}
static Parser<Expression> functionCall(Parser<Expression> param) {
return curry(FunctionExpression.class)
.sequence(TerminalParser.NAME,
term("("), param.sepBy(TerminalParser.term(",")), term(")")).label("function call");
}
static Parser<Expression> tuple(Parser<Expression> expr) {
return curry(TupleExpression.class)
.sequence(term("("), expr.sepBy(term(",")), term(")"));
}
static <T> Parser<T> paren(Parser<T> parser) {
return parser.between(term("("), term(")"));
}
static Parser<Expression> arithmetic(Parser<Expression> atom) {
Reference<Expression> reference = Parser.newReference();
Parser<Expression> operand =
Parsers.or(paren(reference.lazy()), functionCall(reference.lazy()), atom);
Parser<Expression> parser = new OperatorTable<Expression>()
.infixl(binary("/", Op.AGG_DIV), 5)
.infixl(binary("<", Op.LESS), 7)
.infixl(binary("<=", Op.LESS_EQ), 7)
.infixl(binary("=", Op.EQ), 7)
.infixl(binary("!=", Op.NOT_EQ), 7)
.infixl(binary(">", Op.GREATER), 7)
.infixl(binary(">=", Op.GREATER_EQ), 7)
.infixl(binary("+", Op.PLUS), 10)
.infixl(binary("-", Op.MINUS), 10)
.infixl(binary("*", Op.MUL), 20)
.infixl(binary("\\", Op.DIV), 20)
.infixl(binary("%", Op.MOD), 20)
.prefix(unary("-", Op.NEG), 30)
.build(operand);
reference.set(parser);
return parser;
}
static Parser<Expression> atomWhere() {
// can't have unquoted strings at the top level of where expressions as the spaces there are meaningful
return Parsers.or(SIGNED_NUMBER, STRING, NAME);
}
static Parser<Expression> atom() {
return Parsers.longest(SIGNED_NUMBER, STRING, NAME, UNQUOTED_STRING);
}
static Parser<Expression> groupByExpression() {
return Parsers.longest(NAME,
functionCall(expression()),
bracketExpression(),
explodeExpression(),
groupByIn()).label("group by expression");
}
static Parser<Expression> groupByIn() {
return binaryExpression(Op.IN).sequence(NAME, term("in"), tuple(atom())).label("IN grouping");
}
static Parser<Expression> explodeExpression() {
return unaryExpression(Op.EXPLODE).sequence(NAME, term("*")).label("explode grouping");
}
static Parser<Expression> expression() {
return arithmetic(atom()).label("expression");
}
private static Parser<BracketsExpression> bracketExpression() {
return Mapper.curry(BracketsExpression.class).sequence(
TerminalParser.NAME,
TerminalParser.notTerm("]", Parsers.ANY_TOKEN).many().source().between(term("["), term("]")).label("bracket expression")
);
}
/************************** where expressions ****************************/
public static Expression parseWhereExpression(String str) {
return TerminalParser.parse(whereExpression(), str);
}
static Parser<Expression> whereExpression() {
Parser<Expression> singleFilter = filter();
return new OperatorTable<Expression>()
.prefix(unary("-", Op.NEG), 30)
.infixl(binaryOptional("and", Op.AND), 20)
.build(singleFilter).label("where expression");
}
static Parser<Expression> filter() {
// each filter is one of: 1) simple field equality 2) metric inequality/comparison 3) IN operation 4) function call with any expressions as params
// TODO: should be able to do a metric comparison involving functions e.g. floatscale(yearlysalary,1,0) != 0
return Parsers.or(
comparison(NAME, atomWhere()),
inCondition(),
functionCall(arithmetic(atom())), // function parameters are away from top level so we use non-where version of atom
arithmetic(atomWhere()) // TODO: fix: using this instead of the line below lets through some input that will fail in IQLTranslator
// metricComparison(arithmetic(atomWhere()), SIGNED_NUMBER)
);
}
static Parser<Expression> comparison(Parser<Expression> opLeft, Parser<Expression> opRight) {
return Parsers.or(
compare(opLeft, "=", Op.EQ, opRight),
compare(opLeft, ":", Op.EQ, opRight),
compare(opLeft, "!=", Op.NOT_EQ, opRight),
compare(opLeft, "=~", Op.REGEX_EQ, opRight),
compare(opLeft, "!=~", Op.REGEX_NOT_EQ, opRight)).label("comparison");
}
static Parser<Expression> metricComparison(Parser<Expression> opLeft, Parser<Expression> opRight) {
return Parsers.or(
compare(opLeft, "=", Op.EQ, opRight),
compare(opLeft, "!=", Op.NOT_EQ, opRight),
compare(opLeft, ">=", Op.GREATER_EQ, opRight),
compare(opLeft, ">", Op.GREATER, opRight),
compare(opLeft, "<=", Op.LESS_EQ, opRight),
compare(opLeft, "<", Op.LESS, opRight)).label("metric comparison");
}
static Parser<Expression> inCondition() {
return binaryExpression(Op.IN).sequence(NAME, term("in"), tuple(atom())).label("IN condition").or(
binaryExpression(Op.NOT_IN).sequence(NAME, phrase("not in"), tuple(atom())).label("NOT IN condition")
);
}
/************************** utility methods ****************************/
private static Parser<Expression> compare(
Parser<Expression> leftOperand, String name, Op op, Parser<Expression> rightOperand) {
return curry(BinaryExpression.class).sequence(leftOperand, term(name).retn(op), rightOperand);
}
private static Parser<Binary<Expression>> binary(String name, Op op) {
return term(name).next(binaryExpression(op).binary());
}
private static Parser<Binary<Expression>> binaryOptional(String name, Op op) {
return term(name).optional().next(binaryExpression(op).binary());
}
private static Parser<Unary<Expression>> unary(String name, Op op) {
return term(name).next(unaryExpression(op).unary());
}
private static Mapper<Expression> binaryExpression(Op op) {
return curry(BinaryExpression.class, op);
}
private static Mapper<Expression> unaryExpression(Op op) {
return curry(UnaryExpression.class, op);
}
private static Mapper<Expression> curry(Class<? extends Expression> clazz, Object... args) {
return Mapper.curry(clazz, args);
}
}