/**
* Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com)
*
* 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.linkedin.pinot.pql.parsers;
import com.linkedin.pinot.common.request.AggregationInfo;
import com.linkedin.pinot.common.request.BrokerRequest;
import com.linkedin.pinot.pql.parsers.pql2.ast.AstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.BetweenPredicateAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.BinaryMathOpAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.BooleanOperatorAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.ComparisonPredicateAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.ExpressionParenthesisGroupAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.FloatingPointLiteralAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.FunctionCallAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.GroupByAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.HavingAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.IdentifierAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.InPredicateAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.IntegerLiteralAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.IsPredicateAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.LimitAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.OrderByAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.OrderByExpressionAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.OutputColumnAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.OutputColumnListAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.PredicateListAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.PredicateParenthesisGroupAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.RegexpLikePredicateAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.SelectAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.StarColumnListAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.StarExpressionAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.StringLiteralAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.TableNameAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.TopAstNode;
import com.linkedin.pinot.pql.parsers.pql2.ast.WhereAstNode;
import java.util.Stack;
import org.antlr.v4.runtime.misc.NotNull;
/**
* PQL 2 parse tree listener that generates an abstract syntax tree.
*/
public class Pql2AstListener extends PQL2BaseListener {
Stack<AstNode> _nodeStack = new Stack<>();
AstNode _rootNode = null;
private String _expression;
public Pql2AstListener(String expression) {
_expression = expression; // Original expression being parsed.
}
private void pushNode(AstNode node) {
if (_rootNode == null) {
_rootNode = node;
}
AstNode parentNode = null;
if (!_nodeStack.isEmpty()) {
parentNode = _nodeStack.peek();
}
if (parentNode != null) {
parentNode.addChild(node);
}
node.setParent(parentNode);
_nodeStack.push(node);
}
private void popNode() {
AstNode topNode = _nodeStack.pop();
topNode.doneProcessingChildren();
}
public AstNode getRootNode() {
return _rootNode;
}
@Override
public void enterSelect(@NotNull PQL2Parser.SelectContext ctx) {
pushNode(new SelectAstNode());
}
@Override
public void exitSelect(@NotNull PQL2Parser.SelectContext ctx) {
popNode();
}
@Override
public void enterTableName(@NotNull PQL2Parser.TableNameContext ctx) {
pushNode(new TableNameAstNode(ctx.getText()));
}
@Override
public void exitTableName(@NotNull PQL2Parser.TableNameContext ctx) {
popNode();
}
@Override
public void enterStarColumnList(@NotNull PQL2Parser.StarColumnListContext ctx) {
pushNode(new StarColumnListAstNode());
}
@Override
public void exitStarColumnList(@NotNull PQL2Parser.StarColumnListContext ctx) {
popNode();
}
@Override
public void enterOutputColumnList(@NotNull PQL2Parser.OutputColumnListContext ctx) {
pushNode(new OutputColumnListAstNode());
}
@Override
public void exitOutputColumnList(@NotNull PQL2Parser.OutputColumnListContext ctx) {
popNode();
}
@Override
public void enterIsPredicate(@NotNull PQL2Parser.IsPredicateContext ctx) {
pushNode(new IsPredicateAstNode());
}
@Override
public void exitIsPredicate(@NotNull PQL2Parser.IsPredicateContext ctx) {
popNode();
}
@Override
public void enterPredicateParenthesisGroup(@NotNull PQL2Parser.PredicateParenthesisGroupContext ctx) {
pushNode(new PredicateParenthesisGroupAstNode());
}
@Override
public void exitPredicateParenthesisGroup(@NotNull PQL2Parser.PredicateParenthesisGroupContext ctx) {
popNode();
}
@Override
public void enterComparisonPredicate(@NotNull PQL2Parser.ComparisonPredicateContext ctx) {
pushNode(new ComparisonPredicateAstNode(ctx.getChild(0).getChild(1).getText()));
}
@Override
public void exitComparisonPredicate(@NotNull PQL2Parser.ComparisonPredicateContext ctx) {
popNode();
}
@Override
public void enterExpressionParenthesisGroup(@NotNull PQL2Parser.ExpressionParenthesisGroupContext ctx) {
pushNode(new ExpressionParenthesisGroupAstNode());
}
@Override
public void exitExpressionParenthesisGroup(@NotNull PQL2Parser.ExpressionParenthesisGroupContext ctx) {
popNode();
}
@Override
public void enterOutputColumn(@NotNull PQL2Parser.OutputColumnContext ctx) {
pushNode(new OutputColumnAstNode());
}
@Override
public void exitOutputColumn(@NotNull PQL2Parser.OutputColumnContext ctx) {
popNode();
}
@Override
public void enterIdentifier(@NotNull PQL2Parser.IdentifierContext ctx) {
pushNode(new IdentifierAstNode(ctx.getText()));
}
@Override
public void exitIdentifier(@NotNull PQL2Parser.IdentifierContext ctx) {
popNode();
}
@Override
public void enterStarExpression(@NotNull PQL2Parser.StarExpressionContext ctx) {
pushNode(new StarExpressionAstNode());
}
@Override
public void exitStarExpression(@NotNull PQL2Parser.StarExpressionContext ctx) {
popNode();
}
@Override
public void enterFunctionCall(@NotNull PQL2Parser.FunctionCallContext ctx) {
String expression = _expression.substring(ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex() + 1);
pushNode(new FunctionCallAstNode(ctx.getChild(0).getText(), expression));
}
@Override
public void exitFunctionCall(@NotNull PQL2Parser.FunctionCallContext ctx) {
popNode();
}
@Override
public void enterIntegerLiteral(@NotNull PQL2Parser.IntegerLiteralContext ctx) {
pushNode(new IntegerLiteralAstNode(Long.parseLong(ctx.getText())));
}
@Override
public void exitIntegerLiteral(@NotNull PQL2Parser.IntegerLiteralContext ctx) {
popNode();
}
@Override
public void enterOrderBy(@NotNull PQL2Parser.OrderByContext ctx) {
pushNode(new OrderByAstNode());
}
@Override
public void exitOrderBy(@NotNull PQL2Parser.OrderByContext ctx) {
popNode();
}
@Override
public void enterGroupBy(@NotNull PQL2Parser.GroupByContext ctx) {
pushNode(new GroupByAstNode());
}
@Override
public void exitGroupBy(@NotNull PQL2Parser.GroupByContext ctx) {
popNode();
}
@Override
public void enterBetweenPredicate(@NotNull PQL2Parser.BetweenPredicateContext ctx) {
pushNode(new BetweenPredicateAstNode());
}
@Override
public void exitBetweenPredicate(@NotNull PQL2Parser.BetweenPredicateContext ctx) {
popNode();
}
@Override
public void enterBinaryMathOp(@NotNull PQL2Parser.BinaryMathOpContext ctx) {
pushNode(new BinaryMathOpAstNode(ctx.getChild(1).getText()));
}
@Override
public void exitBinaryMathOp(@NotNull PQL2Parser.BinaryMathOpContext ctx) {
popNode();
}
@Override
public void enterInPredicate(@NotNull PQL2Parser.InPredicateContext ctx) {
boolean isNotInClause = false;
if ("not".equalsIgnoreCase(ctx.getChild(0).getChild(1).getText())) {
isNotInClause = true;
}
pushNode(new InPredicateAstNode(isNotInClause));
}
@Override
public void exitInPredicate(@NotNull PQL2Parser.InPredicateContext ctx) {
popNode();
}
@Override
public void enterRegexpLikePredicate(@NotNull PQL2Parser.RegexpLikePredicateContext ctx) {
pushNode(new RegexpLikePredicateAstNode());
}
@Override
public void exitRegexpLikePredicate(@NotNull PQL2Parser.RegexpLikePredicateContext ctx) {
popNode();
}
@Override
public void enterHaving(@NotNull PQL2Parser.HavingContext ctx) {
pushNode(new HavingAstNode());
}
@Override
public void exitHaving(@NotNull PQL2Parser.HavingContext ctx) {
popNode();
}
@Override
public void enterStringLiteral(@NotNull PQL2Parser.StringLiteralContext ctx) {
String text = ctx.getText();
int textLength = text.length();
// String literals can be either 'foo' or "bar". We support quoting by doubling the beginning character, so that
// users can write 'Martha''s Vineyard' or """foo"""
if (text.charAt(0) == '\'') {
pushNode(new StringLiteralAstNode(text.substring(1, textLength - 1).replaceAll("''", "'")));
} else if (text.charAt(0) == '"') {
pushNode(new StringLiteralAstNode(text.substring(1, textLength - 1).replaceAll("\"\"", "\"")));
} else {
throw new Pql2CompilationException("String literal does not start with either ' or \"");
}
}
@Override
public void exitStringLiteral(@NotNull PQL2Parser.StringLiteralContext ctx) {
popNode();
}
@Override
public void enterFloatingPointLiteral(@NotNull PQL2Parser.FloatingPointLiteralContext ctx) {
pushNode(new FloatingPointLiteralAstNode(Double.valueOf(ctx.getText())));
}
@Override
public void exitFloatingPointLiteral(@NotNull PQL2Parser.FloatingPointLiteralContext ctx) {
popNode();
}
@Override
public void enterLimit(@NotNull PQL2Parser.LimitContext ctx) {
// Can either be LIMIT <maxRows> or LIMIT <offset>, <maxRows> (the second is a MySQL syntax extension)
if (ctx.getChild(0).getChildCount() == 2)
pushNode(new LimitAstNode(Integer.parseInt(ctx.getChild(0).getChild(1).getText())));
else
pushNode(new LimitAstNode(
Integer.parseInt(ctx.getChild(0).getChild(3).getText()),
Integer.parseInt(ctx.getChild(0).getChild(1).getText())
));
}
@Override
public void exitLimit(@NotNull PQL2Parser.LimitContext ctx) {
popNode();
}
@Override
public void enterWhere(@NotNull PQL2Parser.WhereContext ctx) {
pushNode(new WhereAstNode());
}
@Override
public void exitWhere(@NotNull PQL2Parser.WhereContext ctx) {
popNode();
}
@Override
public void enterTopClause(@NotNull PQL2Parser.TopClauseContext ctx) {
pushNode(new TopAstNode(Integer.parseInt(ctx.getChild(1).getText())));
}
@Override
public void exitTopClause(@NotNull PQL2Parser.TopClauseContext ctx) {
popNode();
}
@Override
public void enterOrderByExpression(@NotNull PQL2Parser.OrderByExpressionContext ctx) {
if (ctx.getChildCount() == 1) {
pushNode(new OrderByExpressionAstNode(ctx.getChild(0).getText(), "asc"));
} else {
pushNode(new OrderByExpressionAstNode(ctx.getChild(0).getText(), ctx.getChild(1).getText()));
}
}
@Override
public void exitOrderByExpression(@NotNull PQL2Parser.OrderByExpressionContext ctx) {
popNode();
}
@Override
public void enterPredicateList(@NotNull PQL2Parser.PredicateListContext ctx) {
pushNode(new PredicateListAstNode());
}
@Override
public void exitPredicateList(@NotNull PQL2Parser.PredicateListContext ctx) {
popNode();
}
@Override
public void enterBooleanOperator(@NotNull PQL2Parser.BooleanOperatorContext ctx) {
pushNode(BooleanOperatorAstNode.valueOf(ctx.getText().toUpperCase()));
}
@Override
public void exitBooleanOperator(@NotNull PQL2Parser.BooleanOperatorContext ctx) {
popNode();
}
}