http://git-wip-us.apache.org/repos/asf/tajo/blob/d6bc9118/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLAnalyzer.java ---------------------------------------------------------------------- diff --git a/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLAnalyzer.java b/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLAnalyzer.java new file mode 100644 index 0000000..b9b86ac --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLAnalyzer.java @@ -0,0 +1,1986 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.tajo.parser.sql; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.misc.NotNull; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.apache.tajo.SessionVars; +import org.apache.tajo.algebra.*; +import org.apache.tajo.algebra.Aggregation.GroupType; +import org.apache.tajo.algebra.CreateIndex.IndexMethodSpec; +import org.apache.tajo.algebra.DataTypeExpr.MapType; +import org.apache.tajo.algebra.LiteralValue.LiteralType; +import org.apache.tajo.algebra.Sort.SortSpec; +import org.apache.tajo.exception.SQLSyntaxError; +import org.apache.tajo.exception.TajoRuntimeException; +import org.apache.tajo.parser.sql.SQLParser.*; +import org.apache.tajo.storage.StorageConstants; +import org.apache.tajo.util.StringUtils; + +import java.util.*; + +import static org.apache.tajo.algebra.Aggregation.GroupElement; +import static org.apache.tajo.algebra.CreateTable.*; +import static org.apache.tajo.algebra.WindowSpec.WindowFrameEndBoundType; +import static org.apache.tajo.algebra.WindowSpec.WindowFrameStartBoundType; +import static org.apache.tajo.common.TajoDataTypes.Type; +import static org.apache.tajo.parser.sql.SQLParser.*; + +public class SQLAnalyzer extends SQLParserBaseVisitor<Expr> { + + public Expr parse(String sql) throws SQLSyntaxError { + ANTLRInputStream input = new ANTLRInputStream(sql); + SQLLexer lexer = new SQLLexer(input); + CommonTokenStream tokens = new CommonTokenStream(lexer); + SqlContext context; + try { + SQLParser parser = new SQLParser(tokens); + parser.setBuildParseTree(true); + parser.removeErrorListeners(); + + parser.setErrorHandler(new SQLErrorStrategy()); + parser.addErrorListener(new SQLErrorListener()); + context = parser.sql(); + } catch (SQLParseError e) { + throw new SQLSyntaxError(e.getMessage()); + } + return visitSql(context); + } + + private static boolean checkIfExist(Object obj) { + return obj != null; + } + + @Override + public Expr visitSql(SqlContext ctx) { + Expr statement = visit(ctx.statement()); + if (checkIfExist(ctx.explain_clause())) { + return new Explain(statement, checkIfExist(ctx.explain_clause().GLOBAL())); + } else { + return statement; + } + } + + public Expr visitSession_statement(@NotNull Session_statementContext ctx) { + + if (checkIfExist(ctx.CATALOG())) { + + return new SetSession(SessionVars.CURRENT_DATABASE.name(), ctx.dbname.getText()); + + + } else if (checkIfExist(ctx.name)) { + String value; + if (checkIfExist(ctx.boolean_literal())) { + value = ctx.boolean_literal().getText(); + } else if (checkIfExist(ctx.Character_String_Literal())) { + value = stripQuote(ctx.Character_String_Literal().getText()); + } else if (checkIfExist(ctx.signed_numerical_literal())) { + value = ctx.signed_numerical_literal().getText(); + } else { + value = null; + } + return new SetSession(ctx.name.getText(), value); + + + } else if (checkIfExist(ctx.TIME()) && checkIfExist(ctx.ZONE())) { + + String value; + if (checkIfExist(ctx.Character_String_Literal())) { + value = stripQuote(ctx.Character_String_Literal().getText()); + } else if (checkIfExist(ctx.signed_numerical_literal())) { + value = ctx.signed_numerical_literal().getText(); + } else { + value = null; + } + return new SetSession(SessionVars.TIMEZONE.name(), value); + + } else { + throw new TajoRuntimeException(new SQLSyntaxError("Unsupported session statement")); + } + } + + @Override + public Expr visitNon_join_query_expression(Non_join_query_expressionContext ctx) { + + Expr current = visitNon_join_query_term(ctx.non_join_query_term()); + if (ctx.getChildCount() == 1) { + return current; + } + + OpType operatorType; + Expr left; + for (int i = 1; i < ctx.getChildCount(); i++) { + int idx = i; + boolean distinct = true; + + if (ctx.getChild(idx) instanceof TerminalNode) { + if (((TerminalNode) ctx.getChild(idx)).getSymbol().getType() == UNION) { + operatorType = OpType.Union; + } else { + operatorType = OpType.Except; + } + + idx++; + + if (ctx.getChild(idx) instanceof TerminalNode) { + if (((TerminalNode) ctx.getChild(idx)).getSymbol().getType() == ALL) { + distinct = false; + } + + idx++; + } + + Query_termContext queryTermContext = + (Query_termContext) ctx.getChild(idx); + Expr right = visitQuery_term(queryTermContext); + + left = current; + current = new SetOperation(operatorType, left, right, distinct); + + i = idx; + } + } + + return current; + } + + @Override + public Expr visitNon_join_query_term(Non_join_query_termContext ctx) { + + Expr current = visitNon_join_query_primary(ctx.non_join_query_primary()); + Expr left; + + for (int i = 1; i < ctx.getChildCount(); ) { + int idx = i; + boolean distinct = true; + + if (ctx.getChild(idx) instanceof TerminalNode) { + if (((TerminalNode) ctx.getChild(idx)).getSymbol().getType() == INTERSECT) { + idx++; + } + + if (ctx.getChild(idx) instanceof TerminalNode) { + if (((TerminalNode) ctx.getChild(idx)).getSymbol().getType() == ALL) { + distinct = false; + idx++; + } + } + + Query_primaryContext queryPrimaryContext = (Query_primaryContext) ctx.getChild(idx); + Expr right = visitQuery_primary(queryPrimaryContext); + + left = current; + current = new SetOperation(OpType.Intersect, left, right, distinct); + + i += idx; + } + } + + return current; + + } + + @Override + public Expr visitNon_join_query_primary(Non_join_query_primaryContext ctx) { + if (ctx.simple_table() != null) { + return visitSimple_table(ctx.simple_table()); + } else if (ctx.non_join_query_expression() != null) { + return visitNon_join_query_expression(ctx.non_join_query_expression()); + } + return visitChildren(ctx); + } + + @Override + public Expr visitQuery_specification(Query_specificationContext ctx) { + Expr current = null; + if (ctx.table_expression() != null) { + current = visitFrom_clause(ctx.table_expression().from_clause()); + + if (ctx.table_expression().where_clause() != null) { + Selection selection = visitWhere_clause(ctx.table_expression().where_clause()); + selection.setChild(current); + current = selection; + } + + if (ctx.table_expression().groupby_clause() != null) { + Aggregation aggregation = visitGroupby_clause(ctx.table_expression().groupby_clause()); + aggregation.setChild(current); + current = aggregation; + + if (ctx.table_expression().having_clause() != null) { + Expr havingCondition = visitBoolean_value_expression( + ctx.table_expression().having_clause().boolean_value_expression()); + Having having = new Having(havingCondition); + having.setChild(current); + current = having; + } + } + + if (ctx.table_expression().orderby_clause() != null) { + Sort sort = visitOrderby_clause(ctx.table_expression().orderby_clause()); + sort.setChild(current); + current = sort; + } + + if (checkIfExist(ctx.table_expression().window_clause())) { + Window window = visitWindow_clause(ctx.table_expression().window_clause()); + window.setChild(current); + current = window; + } + + if (ctx.table_expression().limit_clause() != null) { + Limit limit = visitLimit_clause(ctx.table_expression().limit_clause()); + limit.setChild(current); + current = limit; + } + } + + Projection projection = visitSelect_list(ctx.select_list()); + + if (ctx.set_qualifier() != null && ctx.set_qualifier().DISTINCT() != null) { + projection.setDistinct(); + } + + if (current != null) { + projection.setChild(current); + } + + current = projection; + + return current; + } + + /** + * <pre> + * select_list + * : select_sublist (COMMA select_sublist)* + * ; + * </pre> + * + * @param ctx + * @return + */ + @Override + public Projection visitSelect_list(Select_listContext ctx) { + Projection projection = new Projection(); + NamedExpr[] targets = new NamedExpr[ctx.select_sublist().size()]; + for (int i = 0; i < targets.length; i++) { + targets[i] = visitSelect_sublist(ctx.select_sublist(i)); + } + projection.setNamedExprs(targets); + + return projection; + } + + /** + * <pre> + * select_sublist + * : derived_column + * | asterisked_qualifier + * ; + * </pre> + * + * @param ctx + * @return + */ + @Override + public NamedExpr visitSelect_sublist(Select_sublistContext ctx) { + if (ctx.qualified_asterisk() != null) { + return visitQualified_asterisk(ctx.qualified_asterisk()); + } else { + return visitDerived_column(ctx.derived_column()); + } + } + + @Override + public RelationList visitFrom_clause(From_clauseContext ctx) { + Expr[] relations = new Expr[ctx.table_reference_list().table_reference().size()]; + for (int i = 0; i < relations.length; i++) { + relations[i] = visitTable_reference(ctx.table_reference_list().table_reference(i)); + } + return new RelationList(relations); + } + + @Override + public Selection visitWhere_clause(Where_clauseContext ctx) { + return new Selection(visitSearch_condition(ctx.search_condition())); + } + + @Override + public Aggregation visitGroupby_clause(Groupby_clauseContext ctx) { + Aggregation clause = new Aggregation(); + + // If grouping group is not empty + if (ctx.grouping_element_list().grouping_element().get(0).empty_grouping_set() == null) { + int elementSize = ctx.grouping_element_list().grouping_element().size(); + ArrayList<GroupElement> groups = new ArrayList<GroupElement>(elementSize + 1); + ArrayList<Expr> ordinaryExprs = null; + int groupSize = 1; + groups.add(null); + + for (int i = 0; i < elementSize; i++) { + Grouping_elementContext element = + ctx.grouping_element_list().grouping_element().get(i); + if (element.ordinary_grouping_set() != null) { + if (ordinaryExprs == null) { + ordinaryExprs = new ArrayList<Expr>(); + } + Collections.addAll(ordinaryExprs, getRowValuePredicandsFromOrdinaryGroupingSet(element.ordinary_grouping_set())); + } else if (element.rollup_list() != null) { + groupSize++; + groups.add(new GroupElement(GroupType.Rollup, + getRowValuePredicandsFromOrdinaryGroupingSetList(element.rollup_list().c))); + } else if (element.cube_list() != null) { + groupSize++; + groups.add(new GroupElement(GroupType.Cube, + getRowValuePredicandsFromOrdinaryGroupingSetList(element.cube_list().c))); + } + } + + if (ordinaryExprs != null) { + groups.set(0, new GroupElement(GroupType.OrdinaryGroup, ordinaryExprs.toArray(new Expr[ordinaryExprs.size()]))); + clause.setGroups(groups.subList(0, groupSize).toArray(new GroupElement[groupSize])); + } else if (groupSize > 1) { + clause.setGroups(groups.subList(1, groupSize).toArray(new GroupElement[groupSize - 1])); + } + } + + return clause; + } + + @Override public WindowFunctionExpr visitWindow_function(@NotNull Window_functionContext context) { + WindowFunctionExpr windowFunction = null; + + Window_function_typeContext functionType = context.window_function_type(); + GeneralSetFunctionExpr functionBody; + if (checkIfExist(functionType.rank_function_type())) { + Rank_function_typeContext rankFunction = functionType.rank_function_type(); + if (checkIfExist(rankFunction.RANK())) { + functionBody = new GeneralSetFunctionExpr("rank", false, new Expr[] {}); + } else if (checkIfExist(rankFunction.DENSE_RANK())) { + functionBody = new GeneralSetFunctionExpr("dense_rank", false, new Expr[] {}); + } else if (checkIfExist(rankFunction.PERCENT_RANK())) { + functionBody = new GeneralSetFunctionExpr("percent_rank", false, new Expr[] {}); + } else { + functionBody = new GeneralSetFunctionExpr("cume_dist", false, new Expr[] {}); + } + } else if (checkIfExist(functionType.ROW_NUMBER())) { + functionBody = new GeneralSetFunctionExpr("row_number", false, new Expr[] {}); + } else if (checkIfExist(functionType.FIRST_VALUE())) { + functionBody = new GeneralSetFunctionExpr("first_value", false, new Expr[]{ visitColumn_reference(functionType.column_reference())}); + } else if (checkIfExist(functionType.LAST_VALUE())) { + functionBody = new GeneralSetFunctionExpr("last_value", false, new Expr[]{visitColumn_reference(functionType.column_reference())}); + } else if (checkIfExist(functionType.LAG())) { + if (checkIfExist(functionType.numeric_value_expression())) { + if (checkIfExist(functionType.common_value_expression())) { + functionBody = new GeneralSetFunctionExpr("lag", false, new Expr[]{visitColumn_reference(functionType.column_reference()), + visitNumeric_value_expression(functionType.numeric_value_expression()), + visitCommon_value_expression(functionType.common_value_expression())}); + } else { + functionBody = new GeneralSetFunctionExpr("lag", false, new Expr[]{visitColumn_reference(functionType.column_reference()), + visitNumeric_value_expression(functionType.numeric_value_expression())}); + } + } else { + functionBody = new GeneralSetFunctionExpr("lag", false, new Expr[]{visitColumn_reference(functionType.column_reference())}); + } + } else if (checkIfExist(functionType.LEAD())) { + if (checkIfExist(functionType.numeric_value_expression())) { + if (checkIfExist(functionType.common_value_expression())) { + functionBody = new GeneralSetFunctionExpr("lead", false, new Expr[]{visitColumn_reference(functionType.column_reference()), + visitNumeric_value_expression(functionType.numeric_value_expression()), + visitCommon_value_expression(functionType.common_value_expression())}); + } else { + functionBody = new GeneralSetFunctionExpr("lead", false, new Expr[]{visitColumn_reference(functionType.column_reference()), + visitNumeric_value_expression(functionType.numeric_value_expression())}); + } + } else { + functionBody = new GeneralSetFunctionExpr("lead", false, new Expr[]{visitColumn_reference(functionType.column_reference())}); + } + } else { + functionBody = visitAggregate_function(functionType.aggregate_function()); + } + windowFunction = new WindowFunctionExpr(functionBody); + + Window_name_or_specificationContext windowNameOrSpec = context.window_name_or_specification(); + if (checkIfExist(windowNameOrSpec.window_name())) { + windowFunction.setWindowName(windowNameOrSpec.window_name().getText()); + } else { + windowFunction.setWindowSpec(buildWindowSpec(windowNameOrSpec.window_specification())); + } + + return windowFunction; + } + + @Override + public Window visitWindow_clause(@NotNull Window_clauseContext ctx) { + Window.WindowDefinition [] definitions = + new Window.WindowDefinition[ctx.window_definition_list().window_definition().size()]; + for (int i = 0; i < definitions.length; i++) { + Window_definitionContext windowDefinitionContext = ctx.window_definition_list().window_definition(i); + String windowName = windowDefinitionContext.window_name().identifier().getText(); + WindowSpec windowSpec = buildWindowSpec(windowDefinitionContext.window_specification()); + definitions[i] = new Window.WindowDefinition(windowName, windowSpec); + } + return new Window(definitions); + } + + public WindowSpec buildWindowSpec(Window_specificationContext ctx) { + WindowSpec windowSpec = new WindowSpec(); + if (checkIfExist(ctx.window_specification_details())) { + Window_specification_detailsContext windowSpecDetail = ctx.window_specification_details(); + + if (checkIfExist(windowSpecDetail.existing_window_name())) { + windowSpec.setWindowName(windowSpecDetail.existing_window_name().getText()); + } + + if (checkIfExist(windowSpecDetail.window_partition_clause())) { + windowSpec.setPartitionKeys( + buildRowValuePredicands(windowSpecDetail.window_partition_clause().row_value_predicand_list())); + } + + if (checkIfExist(windowSpecDetail.window_order_clause())) { + windowSpec.setSortSpecs( + buildSortSpecs(windowSpecDetail.window_order_clause().orderby_clause().sort_specifier_list())); + } + + if (checkIfExist(windowSpecDetail.window_frame_clause())) { + Window_frame_clauseContext frameContext = windowSpecDetail.window_frame_clause(); + + WindowSpec.WindowFrameUnit unit; + // frame unit - there are only two cases: RANGE and ROW + if (checkIfExist(frameContext.window_frame_units().RANGE())) { + unit = WindowSpec.WindowFrameUnit.RANGE; + } else { + unit = WindowSpec.WindowFrameUnit.ROW; + } + + WindowSpec.WindowFrame windowFrame; + + if (checkIfExist(frameContext.window_frame_extent().window_frame_between())) { // when 'between' is given + Window_frame_betweenContext between = frameContext.window_frame_extent().window_frame_between(); + WindowSpec.WindowStartBound startBound = buildWindowStartBound(between.window_frame_start_bound()); + WindowSpec.WindowEndBound endBound = buildWindowEndBound(between.window_frame_end_bound()); + + windowFrame = new WindowSpec.WindowFrame(unit, startBound, endBound); + } else { // if there is only start bound + WindowSpec.WindowStartBound startBound = + buildWindowStartBound(frameContext.window_frame_extent().window_frame_start_bound()); + windowFrame = new WindowSpec.WindowFrame(unit, startBound); + } + + windowSpec.setWindowFrame(windowFrame); + } + } + return windowSpec; + } + + public WindowSpec.WindowStartBound buildWindowStartBound(Window_frame_start_boundContext context) { + WindowFrameStartBoundType boundType = null; + if (checkIfExist(context.UNBOUNDED())) { + boundType = WindowFrameStartBoundType.UNBOUNDED_PRECEDING; + } else if (checkIfExist(context.unsigned_value_specification())) { + boundType = WindowFrameStartBoundType.PRECEDING; + } else { + boundType = WindowFrameStartBoundType.CURRENT_ROW; + } + + WindowSpec.WindowStartBound bound = new WindowSpec.WindowStartBound(boundType); + if (boundType == WindowFrameStartBoundType.PRECEDING) { + bound.setNumber(visitUnsigned_value_specification(context.unsigned_value_specification())); + } + + return bound; + } + + public WindowSpec.WindowEndBound buildWindowEndBound(Window_frame_end_boundContext context) { + WindowFrameEndBoundType boundType; + if (checkIfExist(context.UNBOUNDED())) { + boundType = WindowFrameEndBoundType.UNBOUNDED_FOLLOWING; + } else if (checkIfExist(context.unsigned_value_specification())) { + boundType = WindowFrameEndBoundType.FOLLOWING; + } else { + boundType = WindowFrameEndBoundType.CURRENT_ROW; + } + + WindowSpec.WindowEndBound endBound = new WindowSpec.WindowEndBound(boundType); + if (boundType == WindowFrameEndBoundType.FOLLOWING) { + endBound.setNumber(visitUnsigned_value_specification(context.unsigned_value_specification())); + } + + return endBound; + } + + public Sort.SortSpec[] buildSortSpecs(Sort_specifier_listContext context) { + int size = context.sort_specifier().size(); + + Sort.SortSpec specs[] = new Sort.SortSpec[size]; + for (int i = 0; i < size; i++) { + Sort_specifierContext specContext = context.sort_specifier(i); + Expr sortKeyExpr = visitRow_value_predicand(specContext.key); + specs[i] = new Sort.SortSpec(sortKeyExpr); + if (specContext.order_specification() != null) { + if (specContext.order.DESC() != null) { + specs[i].setDescending(); + } + } + + if (specContext.null_ordering() != null) { + if (specContext.null_ordering().FIRST() != null) { + specs[i].setNullFirst(); + } + } + } + + return specs; + } + + @Override + public Sort visitOrderby_clause(Orderby_clauseContext ctx) { + return new Sort(buildSortSpecs(ctx.sort_specifier_list())); + } + + @Override + public Limit visitLimit_clause(Limit_clauseContext ctx) { + return new Limit(visitNumeric_value_expression(ctx.numeric_value_expression())); + } + + @Override + public Expr visitJoined_table(Joined_tableContext ctx) { + Expr top = visitTable_primary(ctx.table_primary()); + + // The following loop builds a left deep join tree. + Join join; + for (int i = 0; i < ctx.joined_table_primary().size(); i++) { + join = visitJoined_table_primary(ctx.joined_table_primary(i)); + join.setLeft(top); + top = join; + } + + return top; + } + + @Override + public Join visitJoined_table_primary(Joined_table_primaryContext ctx) { + Join join; + if (ctx.CROSS() != null) { + join = new Join(JoinType.CROSS); + } else if (ctx.UNION() != null) { + join = new Join(JoinType.UNION); + } else { // qualified join or natural + if (ctx.join_type() != null && ctx.join_type().outer_join_type() != null) { + Outer_join_type_part2Context outer_join_typeContext = ctx.join_type().outer_join_type() + .outer_join_type_part2(); + if (outer_join_typeContext.FULL() != null) { + join = new Join(JoinType.FULL_OUTER); + } else if (outer_join_typeContext.LEFT() != null) { + join = new Join(JoinType.LEFT_OUTER); + } else { + join = new Join(JoinType.RIGHT_OUTER); + } + } else { + join = new Join(JoinType.INNER); + } + + if (ctx.NATURAL() != null) { + join.setNatural(); + } + + if (ctx.join_specification() != null) { // only for qualified join + if (ctx.join_specification().join_condition() != null) { + Expr searchCondition = visitSearch_condition(ctx.join_specification(). + join_condition().search_condition()); + join.setQual(searchCondition); + } else if (ctx.join_specification().named_columns_join() != null) { + ColumnReferenceExpr[] columns = buildColumnReferenceList(ctx.join_specification(). + named_columns_join().column_reference_list()); + join.setJoinColumns(columns); + } + } + } + + join.setRight(visitTable_primary(ctx.right)); + return join; + } + + private Expr[] getRowValuePredicandsFromOrdinaryGroupingSetList(Ordinary_grouping_set_listContext ctx) { + ArrayList<Expr> rowValuePredicands = new ArrayList<Expr>(); + for (int i = 0; i < ctx.ordinary_grouping_set().size(); i++) { + Collections.addAll(rowValuePredicands, getRowValuePredicandsFromOrdinaryGroupingSet(ctx.ordinary_grouping_set(i))); + } + return rowValuePredicands.toArray(new Expr[rowValuePredicands.size()]); + } + + private Expr[] getRowValuePredicandsFromOrdinaryGroupingSet(Ordinary_grouping_setContext ctx) { + ArrayList<Expr> rowValuePredicands = new ArrayList<Expr>(); + if (ctx.row_value_predicand() != null) { + rowValuePredicands.add(visitRow_value_predicand(ctx.row_value_predicand())); + } + if (ctx.row_value_predicand_list() != null) { + Collections.addAll(rowValuePredicands, buildRowValuePredicands(ctx.row_value_predicand_list())); + } + return rowValuePredicands.toArray(new Expr[rowValuePredicands.size()]); + } + + private Expr[] buildRowValuePredicands(Row_value_predicand_listContext ctx) { + Expr[] rowValuePredicands = new Expr[ctx.row_value_predicand().size()]; + for (int i = 0; i < rowValuePredicands.length; i++) { + rowValuePredicands[i] = visitRow_value_predicand(ctx.row_value_predicand(i)); + } + return rowValuePredicands; + } + + private ColumnReferenceExpr[] buildColumnReferenceList(Column_reference_listContext ctx) { + ColumnReferenceExpr[] columnRefs = new ColumnReferenceExpr[ctx.column_reference().size()]; + for (int i = 0; i < columnRefs.length; i++) { + columnRefs[i] = visitColumn_reference(ctx.column_reference(i)); + } + return columnRefs; + } + + @Override + public Expr visitTable_primary(Table_primaryContext ctx) { + if (ctx.table_or_query_name() != null) { + Relation relation = new Relation(ctx.table_or_query_name().getText()); + if (ctx.alias != null) { + relation.setAlias(ctx.alias.getText()); + } + return relation; + } else if (ctx.derived_table() != null) { + return new TablePrimarySubQuery(ctx.name.getText(), visit(ctx.derived_table().table_subquery())); + } else { + return null; + } + } + + + @Override + public Expr visitSubquery(SubqueryContext ctx) { + return visitQuery_expression(ctx.query_expression()); + } + + @Override + public BetweenPredicate visitBetween_predicate(Between_predicateContext ctx) { + Expr predicand = visitRow_value_predicand(ctx.predicand); + Expr begin = visitRow_value_predicand(ctx.between_predicate_part_2().begin); + Expr end = visitRow_value_predicand(ctx.between_predicate_part_2().end); + return new BetweenPredicate(checkIfExist(ctx.between_predicate_part_2().NOT()), + checkIfExist(ctx.between_predicate_part_2().SYMMETRIC()), predicand, begin, end); + } + + @Override + public CaseWhenPredicate visitSimple_case(Simple_caseContext ctx) { + Expr leftTerm = visitBoolean_value_expression(ctx.boolean_value_expression()); + CaseWhenPredicate caseWhen = new CaseWhenPredicate(); + + for (int i = 0; i < ctx.simple_when_clause().size(); i++) { + Simple_when_clauseContext simpleWhenCtx = ctx.simple_when_clause(i); + BinaryOperator bin = new BinaryOperator(OpType.Equals, leftTerm, + visitValue_expression(simpleWhenCtx.search_condition().value_expression())); + caseWhen.addWhen(bin, buildCaseResult(simpleWhenCtx.result())); + } + if (ctx.else_clause() != null) { + caseWhen.setElseResult(buildCaseResult(ctx.else_clause().result())); + } + return caseWhen; + } + + private Expr buildCaseResult(ResultContext result) { + if (result.NULL() != null) { + return new NullLiteral(); + } else { + return visitValue_expression(result.value_expression()); + } + } + + @Override + public CaseWhenPredicate visitSearched_case(Searched_caseContext ctx) { + CaseWhenPredicate caseWhen = new CaseWhenPredicate(); + + for (int i = 0; i < ctx.searched_when_clause().size(); i++) { + Searched_when_clauseContext searchedWhenCtx = ctx.searched_when_clause(i); + caseWhen.addWhen( + visitSearch_condition(searchedWhenCtx.search_condition()), + buildCaseResult(searchedWhenCtx.result())); + } + if (ctx.else_clause() != null) { + caseWhen.setElseResult(buildCaseResult(ctx.else_clause().result())); + } + return caseWhen; + } + + @Override + public Expr visitCommon_value_expression(Common_value_expressionContext ctx) { + if (checkIfExist(ctx.NULL())) { + return new NullLiteral(); + } else { + return visitChildren(ctx); + } + } + + @Override + public Expr visitParenthesized_value_expression(Parenthesized_value_expressionContext ctx) { + return visitValue_expression(ctx.value_expression()); + } + + @Override + public Expr visitBoolean_value_expression(Boolean_value_expressionContext ctx) { + Expr current = visitOr_predicate(ctx.or_predicate()); + return current; + } + + @Override + public Expr visitOr_predicate(Or_predicateContext ctx) { + Expr current = visitAnd_predicate(ctx.and_predicate()); + + Expr left; + Expr right; + for (int i = 0; i < ctx.or_predicate().size(); i++) { + left = current; + right = visitOr_predicate(ctx.or_predicate(i)); + current = new BinaryOperator(OpType.Or, left, right); + } + + return current; + } + + @Override + public Expr visitAnd_predicate(And_predicateContext ctx) { + Expr current = visitBoolean_factor(ctx.boolean_factor()); + + Expr left; + Expr right; + for (int i = 0; i < ctx.and_predicate().size(); i++) { + left = current; + right = visitAnd_predicate(ctx.and_predicate(i)); + current = new BinaryOperator(OpType.And, left, right); + } + + return current; + } + + @Override + public Expr visitBoolean_factor(Boolean_factorContext ctx) { + if (ctx.NOT() != null) { + return new NotExpr(visitBoolean_test(ctx.boolean_test())); + } else { + return visitBoolean_test(ctx.boolean_test()); + } + } + + @Override + public Expr visitBoolean_test(Boolean_testContext ctx) { + if (checkIfExist(ctx.is_clause())) { + Is_clauseContext isClauseContext = ctx.is_clause(); + if (checkIfExist(isClauseContext.NOT())) { + if (checkIfExist(ctx.is_clause().truth_value().TRUE())) { + return new NotExpr(visitBoolean_primary(ctx.boolean_primary())); + } else { + return visitBoolean_primary(ctx.boolean_primary()); + } + } else { + if (checkIfExist(ctx.is_clause().truth_value().TRUE())) { + return visitBoolean_primary(ctx.boolean_primary()); + } else { + return new NotExpr(visitBoolean_primary(ctx.boolean_primary())); + } + } + } else { + return visitBoolean_primary(ctx.boolean_primary()); + } + } + + @Override + public Expr visitBoolean_primary(Boolean_primaryContext ctx) { + if (ctx.predicate() != null) { + return visitPredicate(ctx.predicate()); + } else { + return visitBoolean_predicand(ctx.boolean_predicand()); + } + } + + @Override + public Expr visitBoolean_predicand(Boolean_predicandContext ctx) { + if (checkIfExist(ctx.nonparenthesized_value_expression_primary())) { + return visitNonparenthesized_value_expression_primary(ctx.nonparenthesized_value_expression_primary()); + } else { + return visitBoolean_value_expression(ctx.parenthesized_boolean_value_expression().boolean_value_expression()); + } + } + + @Override + public Expr visitNonparenthesized_value_expression_primary( + Nonparenthesized_value_expression_primaryContext ctx) { + return visitChildren(ctx); + } + + @Override + public Expr visitRow_value_predicand(Row_value_predicandContext ctx) { + if (checkIfExist(ctx.row_value_special_case())) { + return visitRow_value_special_case(ctx.row_value_special_case()); + } else { + return visitRow_value_constructor_predicand(ctx.row_value_constructor_predicand()); + } + } + + @Override + public Expr visitRow_value_constructor_predicand(Row_value_constructor_predicandContext ctx) { + if (checkIfExist(ctx.boolean_predicand())) { + return visitBoolean_predicand(ctx.boolean_predicand()); + } else { + return visitCommon_value_expression(ctx.common_value_expression()); + } + } + + @Override + public BinaryOperator visitComparison_predicate(Comparison_predicateContext ctx) { + TerminalNode operator = (TerminalNode) ctx.comp_op().getChild(0); + return new BinaryOperator(tokenToExprType(operator.getSymbol().getType()), + visitRow_value_predicand(ctx.left), + visitRow_value_predicand(ctx.right)); + } + + @Override + public Expr visitNumeric_value_expression(Numeric_value_expressionContext ctx) { + Expr current = visitTerm(ctx.term(0)); + + Expr left; + Expr right; + for (int i = 1; i < ctx.getChildCount(); i++) { + left = current; + TerminalNode operator = (TerminalNode) ctx.getChild(i++); + right = visitTerm((TermContext) ctx.getChild(i)); + + if (operator.getSymbol().getType() == PLUS) { + current = new BinaryOperator(OpType.Plus, left, right); + } else { + current = new BinaryOperator(OpType.Minus, left, right); + } + } + + return current; + } + + @Override + public Expr visitTerm(TermContext ctx) { + Expr current = visitFactor(ctx.factor(0)); + + Expr left; + Expr right; + for (int i = 1; i < ctx.getChildCount(); i++) { + left = current; + TerminalNode operator = (TerminalNode) ctx.getChild(i++); + right = visitFactor((FactorContext) ctx.getChild(i)); + + if (operator.getSymbol().getType() == MULTIPLY) { + current = new BinaryOperator(OpType.Multiply, left, right); + } else if (operator.getSymbol().getType() == DIVIDE) { + current = new BinaryOperator(OpType.Divide, left, right); + } else { + current = new BinaryOperator(OpType.Modular, left, right); + } + } + + return current; + } + + @Override + public Expr visitFactor(FactorContext ctx) { + Expr current = visitNumeric_primary(ctx.numeric_primary()); + if (checkIfExist(ctx.sign()) && checkIfExist(ctx.sign().MINUS())) { + current = new SignedExpr(true, current); + } + return current; + } + + @Override + public Expr visitNumeric_primary(Numeric_primaryContext ctx) { + Expr current = null; + if (checkIfExist(ctx.value_expression_primary())) { + current = visitValue_expression_primary(ctx.value_expression_primary()); + for (int i = 0; i < ctx.CAST_EXPRESSION().size(); i++) { + current = new CastExpr(current, visitData_type(ctx.cast_target(i).data_type())); + } + } else if (checkIfExist(ctx.numeric_value_function())) { + current = visitNumeric_value_function(ctx.numeric_value_function()); + } + + return current; + } + + public static OpType tokenToExprType(int tokenId) { + switch (tokenId) { + case UNION: + return OpType.Union; + case EXCEPT: + return OpType.Except; + case INTERSECT: + return OpType.Intersect; + + case AND: + return OpType.And; + case OR: + return OpType.Or; + + case EQUAL: + return OpType.Equals; + case NOT_EQUAL: + return OpType.NotEquals; + case LTH: + return OpType.LessThan; + case LEQ: + return OpType.LessThanOrEquals; + case GTH: + return OpType.GreaterThan; + case GEQ: + return OpType.GreaterThanOrEquals; + + case MULTIPLY: + return OpType.Multiply; + case DIVIDE: + return OpType.Divide; + case MODULAR: + return OpType.Modular; + case PLUS: + return OpType.Plus; + case MINUS: + return OpType.Minus; + + default: + throw new RuntimeException("Unknown Token Id: " + tokenId); + } + } + + @Override + public InPredicate visitIn_predicate(In_predicateContext ctx) { + return new InPredicate(visitChildren(ctx.numeric_value_expression()), + visitIn_predicate_value(ctx.in_predicate_value()), ctx.NOT() != null); + } + + @Override + public Expr visitIn_predicate_value(In_predicate_valueContext ctx) { + if (checkIfExist(ctx.in_value_list())) { + int size = ctx.in_value_list().row_value_predicand().size(); + Expr [] exprs = new Expr[size]; + for (int i = 0; i < size; i++) { + exprs[i] = visitRow_value_predicand(ctx.in_value_list().row_value_predicand(i)); + } + return new ValueListExpr(exprs); + } else { + return new SimpleTableSubquery(visitChildren(ctx.table_subquery())); + } + } + + @Override + public Expr visitArray(ArrayContext ctx) { + int size = ctx.numeric_value_expression().size(); + Expr[] exprs = new Expr[size]; + for (int i = 0; i < size; i++) { + exprs[i] = visit(ctx.numeric_value_expression(i)); + } + return new ValueListExpr(exprs); + } + + @Override + public Expr visitPattern_matching_predicate(Pattern_matching_predicateContext ctx) { + Expr predicand = visitChildren(ctx.row_value_predicand()); + Expr pattern = new LiteralValue(stripQuote(ctx.Character_String_Literal().getText()), + LiteralType.String); + + if (checkIfExist(ctx.pattern_matcher().negativable_matcher())) { + boolean not = ctx.pattern_matcher().NOT() != null; + Negativable_matcherContext matcher = ctx.pattern_matcher().negativable_matcher(); + if (checkIfExist(matcher.LIKE())) { + return new PatternMatchPredicate(OpType.LikePredicate, not, predicand, pattern); + } else if (checkIfExist(matcher.ILIKE())) { + return new PatternMatchPredicate(OpType.LikePredicate, not, predicand, pattern, true); + } else if (checkIfExist(matcher.SIMILAR())) { + return new PatternMatchPredicate(OpType.SimilarToPredicate, not, predicand, pattern); + } else if (checkIfExist(matcher.REGEXP()) || checkIfExist(matcher.RLIKE())) { + return new PatternMatchPredicate(OpType.Regexp, not, predicand, pattern); + } else { + throw new TajoRuntimeException(new SQLSyntaxError("Unsupported predicate: " + matcher.getText())); + } + } else if (checkIfExist(ctx.pattern_matcher().regex_matcher())) { + Regex_matcherContext matcher = ctx.pattern_matcher().regex_matcher(); + if (checkIfExist(matcher.Similar_To())) { + return new PatternMatchPredicate(OpType.Regexp, false, predicand, pattern, false); + } else if (checkIfExist(matcher.Not_Similar_To())) { + return new PatternMatchPredicate(OpType.Regexp, true, predicand, pattern, false); + } else if (checkIfExist(matcher.Similar_To_Case_Insensitive())) { + return new PatternMatchPredicate(OpType.Regexp, false, predicand, pattern, true); + } else if (checkIfExist(matcher.Not_Similar_To_Case_Insensitive())) { + return new PatternMatchPredicate(OpType.Regexp, true, predicand, pattern, true); + } else { + throw new TajoRuntimeException(new SQLSyntaxError("Unsupported predicate: " + matcher.getText())); + } + } else { + throw new TajoRuntimeException(new SQLSyntaxError("Unsupported predicate: " + ctx.pattern_matcher().getText())); + } + } + + @Override + public IsNullPredicate visitNull_predicate(Null_predicateContext ctx) { + Expr predicand = visitRow_value_predicand(ctx.row_value_predicand()); + return new IsNullPredicate(ctx.NOT() != null, predicand); + } + + @Override + public ExistsPredicate visitExists_predicate(Exists_predicateContext ctx) { + return new ExistsPredicate(new SimpleTableSubquery(visitTable_subquery(ctx.table_subquery())), ctx.NOT() != null); + } + + @Override + public ColumnReferenceExpr visitColumn_reference(Column_referenceContext ctx) { + String columnReferenceName = ctx.getText(); + // find the last dot (.) position to separate a name into both a qualifier and name + int lastDotIdx = columnReferenceName.lastIndexOf("."); + + if (lastDotIdx > 0) { // if any qualifier is given + String qualifier = columnReferenceName.substring(0, lastDotIdx); + String name = columnReferenceName.substring(lastDotIdx + 1, columnReferenceName.length()); + return new ColumnReferenceExpr(qualifier, name); + } else { + return new ColumnReferenceExpr(ctx.getText()); + } + } + + @Override + public LiteralValue visitUnsigned_numeric_literal(@NotNull Unsigned_numeric_literalContext ctx) { + if (ctx.NUMBER() != null) { + long lValue = Long.parseLong(ctx.getText()); + if (lValue >= Integer.MIN_VALUE && lValue <= Integer.MAX_VALUE) { + return new LiteralValue(ctx.getText(), LiteralType.Unsigned_Integer); + } else { + return new LiteralValue(ctx.getText(), LiteralType.Unsigned_Large_Integer); + } + } else { + return new LiteralValue(ctx.getText(), LiteralType.Unsigned_Float); + } + } + + @Override + public GeneralSetFunctionExpr visitAggregate_function(Aggregate_functionContext ctx) { + if (ctx.COUNT() != null && ctx.MULTIPLY() != null) { + return new CountRowsFunctionExpr(); + } else { + return visitGeneral_set_function(ctx.general_set_function()); + } + } + + @Override + public GeneralSetFunctionExpr visitGeneral_set_function(General_set_functionContext ctx) { + String signature = ctx.set_function_type().getText(); + boolean distinct = checkIfExist(ctx.set_qualifier()) && checkIfExist(ctx.set_qualifier().DISTINCT()); + Expr param = visitValue_expression(ctx.value_expression()); + + return new GeneralSetFunctionExpr(signature, distinct, new Expr [] {param}); + } + + @Override + public FunctionExpr visitRoutine_invocation(Routine_invocationContext ctx) { + String signature = ctx.function_name().getText(); + FunctionExpr function = new FunctionExpr(signature); + if (ctx.sql_argument_list() != null) { + int numArgs = ctx.sql_argument_list().value_expression().size(); + Expr[] argument_list = new Expr[numArgs]; + for (int i = 0; i < numArgs; i++) { + argument_list[i] = visitValue_expression(ctx.sql_argument_list(). + value_expression().get(i)); + } + + function.setParams(argument_list); + } + return function; + } + + @Override + public NamedExpr visitDerived_column(Derived_columnContext ctx) { + NamedExpr target = new NamedExpr(visitValue_expression(ctx.value_expression())); + if (ctx.as_clause() != null) { + target.setAlias(ctx.as_clause().identifier().getText()); + } + return target; + } + + @Override + public NamedExpr visitQualified_asterisk(Qualified_asteriskContext ctx) { + QualifiedAsteriskExpr target = new QualifiedAsteriskExpr(); + if (ctx.tb_name != null) { + target.setQualifier(ctx.tb_name.getText()); + } + + return new NamedExpr(target); + } + + @Override + public Expr visitCharacter_string_type(Character_string_typeContext ctx) { + return new LiteralValue(stripQuote(ctx.getText()), LiteralType.String); + } + + @Override + public Expr visitCharacter_value_expression(Character_value_expressionContext ctx) { + Expr current = visitCharacter_factor(ctx.character_factor(0)); + + Expr left; + Expr right; + for (int i = 1; i < ctx.getChildCount(); i++) { + left = current; + i++; // skip '||' operator + right = visitCharacter_factor((Character_factorContext) ctx.getChild(i)); + + if (left.getType() == OpType.Literal && right.getType() == OpType.Literal) { + current = new LiteralValue(((LiteralValue) left).getValue() + ((LiteralValue) right).getValue(), + LiteralType.String); + } else { + current = new BinaryOperator(OpType.Concatenate, left, right); + } + } + + return current; + } + + + @Override + public Expr visitNumeric_value_function(Numeric_value_functionContext ctx) { + if (checkIfExist(ctx.extract_expression())) { + return visitExtract_expression(ctx.extract_expression()); + } + if (checkIfExist(ctx.datetime_value_function())) { + return visitDatetime_value_function(ctx.datetime_value_function()); + } + return null; + } + + @Override + public Expr visitExtract_expression(Extract_expressionContext ctx) { + Expr extractTarget = new LiteralValue(ctx.extract_field_string.getText(), LiteralType.String); + Expr extractSource = visitDatetime_value_expression(ctx.extract_source().datetime_value_expression()); +// if (checkIfExist(ctx.extract_source().column_reference())) { +// extractSource = visitColumn_reference(ctx.extract_source().column_reference()); +// } else if (checkIfExist(ctx.extract_source().datetime_literal())) { +// extractSource = visitDatetime_literal(ctx.extract_source().datetime_literal()); +// } else { +// return null; +// } + + + String functionName = "date_part"; + Expr[] params = new Expr[]{extractTarget, extractSource}; + + return new FunctionExpr(functionName, params); + } + + @Override + public Expr visitTrim_function(Trim_functionContext ctx) { + Expr trimSource = visitChildren(ctx.trim_operands().trim_source); + String functionName = "trim"; + if (checkIfExist(ctx.trim_operands().FROM())) { + if (checkIfExist(ctx.trim_operands().trim_specification())) { + Trim_specificationContext specification = ctx.trim_operands().trim_specification(); + if (checkIfExist(specification.LEADING())) { + functionName = "ltrim"; + } else if (checkIfExist(specification.TRAILING())) { + functionName = "rtrim"; + } else { + functionName = "trim"; + } + } + } + + Expr trimCharacters = null; + if (checkIfExist(ctx.trim_operands().trim_character)) { + trimCharacters = visitCharacter_value_expression(ctx.trim_operands().trim_character); + } + + Expr[] params; + if (trimCharacters != null) { + params = new Expr[]{trimSource, trimCharacters}; + } else { + params = new Expr[]{trimSource}; + } + + return new FunctionExpr(functionName, params); + } + + @Override + public Expr visitCreate_index_statement(Create_index_statementContext ctx) { + String indexName = ctx.index_name.getText(); + String tableName = ctx.table_name().getText(); + Relation relation = new Relation(tableName); + SortSpec[] sortSpecs = buildSortSpecs(ctx.sort_specifier_list()); + NamedExpr[] targets = new NamedExpr[sortSpecs.length]; + Projection projection = new Projection(); + int i = 0; + for (SortSpec sortSpec : sortSpecs) { + targets[i++] = new NamedExpr(sortSpec.getKey()); + } + projection.setNamedExprs(targets); + projection.setChild(relation); + + CreateIndex createIndex = new CreateIndex(indexName, sortSpecs); + if (checkIfExist(ctx.UNIQUE())) { + createIndex.setUnique(true); + } + if (checkIfExist(ctx.method_specifier())) { + String methodName = ctx.method_specifier().identifier().getText(); + createIndex.setMethodSpec(new IndexMethodSpec(methodName)); + } + if (checkIfExist(ctx.param_clause())) { + Map<String, String> params = getParams(ctx.param_clause()); + createIndex.setParams(params); + } + if (checkIfExist(ctx.where_clause())) { + Selection selection = visitWhere_clause(ctx.where_clause()); + selection.setChild(relation); + projection.setChild(selection); + } + if (checkIfExist(ctx.LOCATION())) { + createIndex.setIndexPath(stripQuote(ctx.path.getText())); + } + createIndex.setChild(projection); + return createIndex; + } + + @Override + public Expr visitDrop_index_statement(Drop_index_statementContext ctx) { + String indexName = ctx.identifier().getText(); + return new DropIndex(indexName); + } + + @Override + public Expr visitDatabase_definition(@NotNull Database_definitionContext ctx) { + return new CreateDatabase(ctx.identifier().getText(), null, checkIfExist(ctx.if_not_exists())); + } + + @Override + public Expr visitDrop_database_statement(@NotNull Drop_database_statementContext ctx) { + return new DropDatabase(ctx.identifier().getText(), checkIfExist(ctx.if_exists())); + } + + @Override + public Expr visitCreate_table_statement(Create_table_statementContext ctx) { + String tableName = ctx.table_name(0).getText(); + CreateTable createTable = new CreateTable(tableName, checkIfExist(ctx.if_not_exists())); + if(checkIfExist(ctx.LIKE())) { + createTable.setLikeParentTable(ctx.like_table_name.getText()); + return createTable; + } + + if (checkIfExist(ctx.EXTERNAL())) { + createTable.setExternal(); + + ColumnDefinition[] elements = getDefinitions(ctx.table_elements()); + String storageType = ctx.storage_type.getText(); + createTable.setTableElements(elements); + createTable.setStorageType(storageType); + + if (checkIfExist(ctx.LOCATION())) { + String uri = stripQuote(ctx.uri.getText()); + createTable.setLocation(uri); + } + } else { + if (checkIfExist(ctx.table_elements())) { + ColumnDefinition[] elements = getDefinitions(ctx.table_elements()); + createTable.setTableElements(elements); + } + + if (checkIfExist(ctx.TABLESPACE())) { + String spaceName = ctx.spacename.getText(); + createTable.setTableSpaceName(spaceName); + } + + if (checkIfExist(ctx.USING())) { + String fileType = ctx.storage_type.getText(); + createTable.setStorageType(fileType); + } + + if (checkIfExist(ctx.query_expression())) { + Expr subquery = visitQuery_expression(ctx.query_expression()); + createTable.setSubQuery(subquery); + } + } + + if (checkIfExist(ctx.param_clause())) { + Map<String, String> params = escapeTableMeta(getParams(ctx.param_clause())); + createTable.setParams(params); + } + + if (checkIfExist(ctx.table_partitioning_clauses())) { + PartitionMethodDescExpr partitionMethodDesc = + parseTablePartitioningClause(ctx.table_partitioning_clauses()); + createTable.setPartitionMethod(partitionMethodDesc); + } + return createTable; + } + + @Override + public Expr visitTruncate_table_statement(@NotNull Truncate_table_statementContext ctx) { + List<Table_nameContext> tableNameContexts = ctx.table_name(); + List<String> tableNames = new ArrayList<String>(); + + for (Table_nameContext eachTableNameContext: tableNameContexts) { + tableNames.add(eachTableNameContext.getChild(0).getText()); + } + + return new TruncateTable(tableNames); + } + + private ColumnDefinition[] getDefinitions(Table_elementsContext ctx) { + int size = ctx.field_element().size(); + ColumnDefinition[] elements = new ColumnDefinition[size]; + for (int i = 0; i < size; i++) { + String name = ctx.field_element(i).name.getText(); + + String dataTypeName = ctx.field_element(i).field_type().data_type().getText(); + DataTypeExpr typeDef = visitData_type(ctx.field_element(i).field_type().data_type()); + Preconditions.checkNotNull(typeDef, dataTypeName + " is not handled correctly"); + elements[i] = new ColumnDefinition(name, typeDef); + } + + return elements; + } + + public PartitionMethodDescExpr parseTablePartitioningClause(Table_partitioning_clausesContext ctx) { + + if (checkIfExist(ctx.range_partitions())) { // For Range Partition + Range_partitionsContext rangePartitionsContext = ctx.range_partitions(); + List<Range_value_clauseContext> rangeValueClause = rangePartitionsContext. + range_value_clause_list().range_value_clause(); + + List<RangePartitionSpecifier> specifiers = Lists.newArrayList(); + + for (Range_value_clauseContext rangeValue : rangeValueClause) { + if (checkIfExist(rangeValue.MAXVALUE())) { // LESS THAN (MAXVALUE) + specifiers.add(new RangePartitionSpecifier(rangeValue.partition_name().getText())); + } else { // LESS THAN (expr) + specifiers.add(new RangePartitionSpecifier(rangeValue.partition_name().getText(), + visitValue_expression(rangeValue.value_expression()))); + } + } + return new CreateTable.RangePartition(buildColumnReferenceList(ctx.range_partitions().column_reference_list()), + specifiers); + + } else if (checkIfExist(ctx.hash_partitions())) { // For Hash Partition + Hash_partitionsContext hashPartitions = ctx.hash_partitions(); + + if (checkIfExist(hashPartitions.hash_partitions_by_quantity())) { // PARTITIONS (num) + return new HashPartition(buildColumnReferenceList(hashPartitions.column_reference_list()), + visitNumeric_value_expression(hashPartitions.hash_partitions_by_quantity().quantity)); + + } else { // ( PARTITION part_name , ...) + List<CreateTable.PartitionSpecifier> specifiers = Lists.newArrayList(); + for (Individual_hash_partitionContext partition : + hashPartitions.individual_hash_partitions().individual_hash_partition()) { + specifiers.add(new CreateTable.PartitionSpecifier(partition.partition_name().getText())); + } + return new HashPartition(buildColumnReferenceList(hashPartitions.column_reference_list()), specifiers); + } + + } else if (checkIfExist(ctx.list_partitions())) { // For List Partition + List_partitionsContext listPartitions = ctx.list_partitions(); + List<List_value_partitionContext> partitions = listPartitions.list_value_clause_list().list_value_partition(); + List<ListPartitionSpecifier> specifiers = Lists.newArrayList(); + + for (List_value_partitionContext listValuePartition : partitions) { + int size = listValuePartition.in_value_list().row_value_predicand().size(); + Expr [] exprs = new Expr[size]; + for (int i = 0; i < size; i++) { + exprs[i] = visitRow_value_predicand(listValuePartition.in_value_list().row_value_predicand(i)); + } + specifiers.add(new ListPartitionSpecifier(listValuePartition.partition_name().getText(), + new ValueListExpr(exprs))); + } + return new ListPartition(buildColumnReferenceList(ctx.list_partitions().column_reference_list()), specifiers); + + } else if (checkIfExist(ctx.column_partitions())) { // For Column Partition (Hive Style) + return new CreateTable.ColumnPartition(getDefinitions(ctx.column_partitions().table_elements())); + } else { + throw new TajoRuntimeException(new SQLSyntaxError("Invalid Partition Type: " + ctx.toStringTree())); + } + } + + @Override + public DataTypeExpr visitData_type(Data_typeContext ctx) { + Predefined_typeContext predefined_type = ctx.predefined_type(); + + DataTypeExpr typeDefinition = null; + + // CHAR -> FIXED CHAR + // |- VARYING CHAR + // TEXT + if (checkIfExist(predefined_type.character_string_type())) { + + Character_string_typeContext character_string_type = predefined_type.character_string_type(); + + + if ((checkIfExist(character_string_type.CHARACTER()) || checkIfExist(character_string_type.CHAR())) && + !checkIfExist(character_string_type.VARYING())) { + + typeDefinition = new DataTypeExpr(Type.CHAR.name()); + + if (character_string_type.type_length() != null) { + typeDefinition.setLengthOrPrecision( + Integer.parseInt(character_string_type.type_length().NUMBER().getText())); + } + + } else if (checkIfExist(character_string_type.VARCHAR()) || checkIfExist(character_string_type.VARYING())) { + + typeDefinition = new DataTypeExpr(Type.VARCHAR.name()); + + if (character_string_type.type_length() != null) { + typeDefinition.setLengthOrPrecision( + Integer.parseInt(character_string_type.type_length().NUMBER().getText())); + } + + } else if (checkIfExist(character_string_type.TEXT())) { + typeDefinition = new DataTypeExpr(Type.TEXT.name()); + } + + // NCHAR + } else if (checkIfExist(predefined_type.national_character_string_type())) { + + National_character_string_typeContext nchar_type = predefined_type.national_character_string_type(); + + if ((checkIfExist(nchar_type.CHAR()) || checkIfExist(nchar_type.CHARACTER()) || + checkIfExist(nchar_type.NCHAR()) && !checkIfExist(nchar_type.VARYING()))) { + + typeDefinition = new DataTypeExpr(Type.NCHAR.name()); + + } else if (checkIfExist(nchar_type.NVARCHAR()) || checkIfExist(nchar_type.VARYING())) { + + typeDefinition = new DataTypeExpr(Type.NVARCHAR.name()); + } + + // if a length is given + if (checkIfExist(nchar_type.type_length())) { + typeDefinition.setLengthOrPrecision(Integer.parseInt(nchar_type.type_length().NUMBER().getText())); + } + + // BLOB types + } else if (checkIfExist(predefined_type.binary_large_object_string_type())) { + + Binary_large_object_string_typeContext blob_type = predefined_type.binary_large_object_string_type(); + + typeDefinition = new DataTypeExpr(Type.BLOB.name()); + + if (checkIfExist(blob_type.type_length())) { + typeDefinition.setLengthOrPrecision(Integer.parseInt(blob_type.type_length().NUMBER().getText())); + } + + // NUMERIC types + } else if (checkIfExist(predefined_type.numeric_type())) { + // exact number + if (checkIfExist(predefined_type.numeric_type().exact_numeric_type())) { + + Exact_numeric_typeContext exactType = predefined_type.numeric_type().exact_numeric_type(); + + if (checkIfExist(exactType.TINYINT()) || checkIfExist(exactType.INT1())) { + typeDefinition = new DataTypeExpr(Type.INT1.name()); + + } else if (checkIfExist(exactType.INT2()) || checkIfExist(exactType.SMALLINT())) { + typeDefinition = new DataTypeExpr(Type.INT2.name()); + + } else if (checkIfExist(exactType.INT4()) || + checkIfExist(exactType.INTEGER()) || + checkIfExist(exactType.INT())) { + typeDefinition = new DataTypeExpr(Type.INT4.name()); + + } else if (checkIfExist(exactType.INT8()) || checkIfExist(exactType.BIGINT()) ) { + typeDefinition = new DataTypeExpr(Type.INT8.name()); + + } else if (checkIfExist(exactType.NUMERIC()) || + checkIfExist(exactType.DECIMAL()) || + checkIfExist(exactType.DEC())) { + typeDefinition = new DataTypeExpr(Type.NUMERIC.name()); + + if (checkIfExist(exactType.precision_param())) { + typeDefinition.setLengthOrPrecision(Integer.parseInt(exactType.precision_param().precision.getText())); + + if (checkIfExist(exactType.precision_param().scale)) { + typeDefinition.setScale(Integer.parseInt(exactType.precision_param().scale.getText())); + } + } + } + + + } else { // approximate number + Approximate_numeric_typeContext approximateType = predefined_type.numeric_type().approximate_numeric_type(); + if (checkIfExist(approximateType.FLOAT()) || + checkIfExist(approximateType.FLOAT4()) || + checkIfExist(approximateType.REAL())) { + typeDefinition = new DataTypeExpr(Type.FLOAT4.name()); + + } else if (checkIfExist(approximateType.FLOAT8()) || checkIfExist(approximateType.DOUBLE())) { + typeDefinition = new DataTypeExpr(Type.FLOAT8.name()); + } + } + + } else if (checkIfExist(predefined_type.boolean_type())) { + typeDefinition = new DataTypeExpr(Type.BOOLEAN.name()); + + } else if (checkIfExist(predefined_type.datetime_type())) { + + Datetime_typeContext dateTimeType = predefined_type.datetime_type(); + if (checkIfExist(dateTimeType.DATE())) { + typeDefinition = new DataTypeExpr(Type.DATE.name()); + + } else if (checkIfExist(dateTimeType.TIME(0))) { + if (checkIfExist(dateTimeType.ZONE())) { + typeDefinition = new DataTypeExpr(Type.TIMEZ.name()); + } else { + typeDefinition = new DataTypeExpr(Type.TIME.name()); + } + + } else if (checkIfExist(dateTimeType.TIMETZ())) { + typeDefinition = new DataTypeExpr(Type.TIMEZ.name()); + + } else if (checkIfExist(dateTimeType.TIMESTAMP())) { + if (checkIfExist(dateTimeType.ZONE())) { + typeDefinition = new DataTypeExpr(Type.TIMESTAMPZ.name()); + } else { + typeDefinition = new DataTypeExpr(Type.TIMESTAMP.name()); + } + + } else if (checkIfExist(dateTimeType.TIMESTAMPTZ())) { + typeDefinition = new DataTypeExpr(Type.TIMESTAMPZ.name()); + } + + // bit data types + } else if (predefined_type.bit_type() != null) { + Bit_typeContext bitType = predefined_type.bit_type(); + + if (checkIfExist(bitType.VARBIT()) || checkIfExist(bitType.VARYING())) { + typeDefinition = new DataTypeExpr(Type.VARBIT.name()); + + } else { + typeDefinition = new DataTypeExpr(Type.BIT.name()); + } + + if (checkIfExist(bitType.type_length())) { + typeDefinition.setLengthOrPrecision( + Integer.parseInt(bitType.type_length().NUMBER().getText())); + } + + + // binary data types + } else if (checkIfExist(predefined_type.binary_type())) { + Binary_typeContext binaryType = predefined_type.binary_type(); + + if (checkIfExist(binaryType.VARBINARY()) || checkIfExist(binaryType.VARYING())) { + typeDefinition = new DataTypeExpr(Type.VARBINARY.name()); + } else { + typeDefinition = new DataTypeExpr(Type.BINARY.name()); + } + + + if (checkIfExist(binaryType.type_length())) { + typeDefinition.setLengthOrPrecision(Integer.parseInt(binaryType.type_length().NUMBER().getText())); + } + + // inet + } else if (checkIfExist(predefined_type.network_type())) { + typeDefinition = new DataTypeExpr(Type.INET4.name()); + + + } else if (checkIfExist(predefined_type.record_type())) { + ColumnDefinition [] nestedRecordDefine = getDefinitions(predefined_type.record_type().table_elements()); + typeDefinition = new DataTypeExpr(new DataTypeExpr.RecordType(nestedRecordDefine)); + + } else if (checkIfExist(predefined_type.map_type())) { + Map_typeContext mapTypeContext = predefined_type.map_type(); + typeDefinition = new DataTypeExpr( + new MapType(visitData_type(mapTypeContext.key_type), visitData_type(mapTypeContext.value_type))); + } + + return typeDefinition; + } + + @Override + public Expr visitInsert_statement(Insert_statementContext ctx) { + Insert insertExpr = new Insert(); + + if (ctx.OVERWRITE() != null) { + insertExpr.setOverwrite(); + } + + if (ctx.table_name() != null) { + insertExpr.setTableName(ctx.table_name().getText()); + + if (ctx.column_reference_list() != null) { + ColumnReferenceExpr [] targetColumns = + new ColumnReferenceExpr[ctx.column_reference_list().column_reference().size()]; + + for (int i = 0; i < targetColumns.length; i++) { + targetColumns[i] = visitColumn_reference(ctx.column_reference_list().column_reference(i)); + } + + insertExpr.setTargetColumns(targetColumns); + } + } + + if (ctx.LOCATION() != null) { + insertExpr.setLocation(stripQuote(ctx.path.getText())); + + if (ctx.USING() != null) { + insertExpr.setStorageType(ctx.storage_type.getText()); + + if (ctx.param_clause() != null) { + insertExpr.setParams(escapeTableMeta(getParams(ctx.param_clause()))); + } + } + } + + insertExpr.setSubQuery(visitQuery_expression(ctx.query_expression())); + + Preconditions.checkState(insertExpr.hasTableName() || insertExpr.hasLocation(), + "Either a table name or a location should be given."); + Preconditions.checkState(insertExpr.hasTableName() ^ insertExpr.hasLocation(), + "A table name and a location cannot coexist."); + return insertExpr; + } + + @Override + public Expr visitDrop_table_statement(Drop_table_statementContext ctx) { + return new DropTable(ctx.table_name().getText(), checkIfExist(ctx.if_exists()), checkIfExist(ctx.PURGE())); + } + + + private Map<String, String> getParams(Param_clauseContext ctx) { + Map<String, String> params = new HashMap<String, String>(); + for (int i = 0; i < ctx.param().size(); i++) { + params.put(stripQuote(ctx.param(i).key.getText()), stripQuote(ctx.param(i).value.getText())); + } + + return params; + } + + public Map<String, String> escapeTableMeta(Map<String, String> map) { + Map<String, String> params = new HashMap<String, String>(); + for (Map.Entry<String, String> entry : map.entrySet()) { + if (entry.getKey().equals(StorageConstants.TEXT_DELIMITER)) { + params.put(StorageConstants.TEXT_DELIMITER, StringUtils.unicodeEscapedDelimiter(entry.getValue())); + } else { + params.put(entry.getKey(), entry.getValue()); + } + } + return params; + } + + private static String stripQuote(String str) { + return str.substring(1, str.length() - 1); + } + + @Override + public Expr visitCast_specification(Cast_specificationContext ctx) { + Expr operand = visitChildren(ctx.cast_operand()); + DataTypeExpr castTarget = visitData_type(ctx.cast_target().data_type()); + return new CastExpr(operand, castTarget); + } + + @Override + public Expr visitUnsigned_value_specification(@NotNull Unsigned_value_specificationContext ctx) { + return visitChildren(ctx); + } + + @Override + public Expr visitUnsigned_literal(@NotNull Unsigned_literalContext ctx) { + if (checkIfExist(ctx.unsigned_numeric_literal())) { + return visitUnsigned_numeric_literal(ctx.unsigned_numeric_literal()); + } else { + return visitGeneral_literal(ctx.general_literal()); + } + } + + @Override + public Expr visitGeneral_literal(General_literalContext ctx) { + if (checkIfExist(ctx.Character_String_Literal())) { + return new LiteralValue(stripQuote(ctx.Character_String_Literal().getText()), LiteralType.String); + } else if (checkIfExist(ctx.datetime_literal())) { + return visitDatetime_literal(ctx.datetime_literal()); + } else { + return new BooleanLiteral(checkIfExist(ctx.boolean_literal().TRUE())); + } + } + + @Override + public Expr visitDatetime_literal(@NotNull Datetime_literalContext ctx) { + if (checkIfExist(ctx.time_literal())) { + return visitTime_literal(ctx.time_literal()); + } else if (checkIfExist(ctx.date_literal())) { + return visitDate_literal(ctx.date_literal()); + } else if (checkIfExist(ctx.interval_literal())) { + return visitInterval_literal(ctx.interval_literal()); + } else { + return visitTimestamp_literal(ctx.timestamp_literal()); + } + } + + @Override + public Expr visitTime_literal(Time_literalContext ctx) { + String timePart = stripQuote(ctx.time_string.getText()); + return new TimeLiteral(parseTime(timePart)); + } + + @Override + public Expr visitDate_literal(Date_literalContext ctx) { + String datePart = stripQuote(ctx.date_string.getText()); + return new DateLiteral(parseDate(datePart)); + } + + @Override + public Expr visitTimestamp_literal(Timestamp_literalContext ctx) { + String timestampStr = stripQuote(ctx.timestamp_string.getText()); + String[] parts = timestampStr.split(" "); + String datePart = parts[0]; + String timePart = parts[1]; + return new TimestampLiteral(parseDate(datePart), parseTime(timePart)); + } + + @Override + public Expr visitInterval_literal(@NotNull Interval_literalContext ctx) { + String intervalStr = stripQuote(ctx.interval_string.getText()); + return new IntervalLiteral(intervalStr); + } + + @Override + public Expr visitDatetime_value_expression(@NotNull Datetime_value_expressionContext ctx) { + return visitDatetime_term(ctx.datetime_term()); + } + + @Override + public Expr visitDatetime_term(@NotNull Datetime_termContext ctx) { + return visitDatetime_factor(ctx.datetime_factor()); + } + + @Override + public Expr visitDatetime_factor(@NotNull Datetime_factorContext ctx) { + return visitDatetime_primary(ctx.datetime_primary()); + } + + @Override + public Expr visitDatetime_primary(@NotNull Datetime_primaryContext ctx) { + if (checkIfExist(ctx.value_expression_primary())) { + return visitValue_expression_primary(ctx.value_expression_primary()); + } else { + return visitDatetime_value_function(ctx.datetime_value_function()); + } + } + + @Override + public Expr visitDatetime_value_function(@NotNull Datetime_value_functionContext ctx) { + if (checkIfExist(ctx.current_date_value_function())) { + return visitCurrent_date_value_function(ctx.current_date_value_function()); + } else if (checkIfExist(ctx.current_time_value_function())) { + return visitCurrent_time_value_function(ctx.current_time_value_function()); + } else { + return visitCurrent_timestamp_value_function(ctx.current_timestamp_value_function()); + } + } + + @Override + public Expr visitCurrent_date_value_function(@NotNull Current_date_value_functionContext ctx) { + String functionName = "current_date"; + Expr[] params = new Expr[]{}; + return new FunctionExpr(functionName, params); + } + + @Override + public Expr visitCurrent_time_value_function(@NotNull Current_time_value_functionContext ctx) { + String functionName = "current_time"; + Expr[] params = new Expr[]{}; + return new FunctionExpr(functionName, params); + } + + @Override + public Expr visitCurrent_timestamp_value_function(@NotNull Current_timestamp_value_functionContext ctx) { + String functionName = "now"; + Expr[] params = new Expr[]{}; + return new FunctionExpr(functionName, params); + } + + private DateValue parseDate(String datePart) { + // e.g., 1980-04-01 + String[] parts = datePart.split("-"); + return new DateValue(parts[0], parts[1], parts[2]); + } + + private TimeValue parseTime(String timePart) { + // e.g., 12:01:50.399 + String[] parts = timePart.split(":"); + + TimeValue time; + boolean hasFractionOfSeconds = (parts.length > 2 && parts[2].indexOf('.') > 0); + if (hasFractionOfSeconds) { + String[] secondsParts = parts[2].split("\\."); + time = new TimeValue(parts[0], parts[1], secondsParts[0]); + if (secondsParts.length == 2) { + time.setSecondsFraction(secondsParts[1]); + } + } else { + time = new TimeValue(parts[0], + (parts.length > 1 ? parts[1] : "0"), + (parts.length > 2 ? parts[2] : "0")); + } + return time; + } + + @Override + public Expr visitAlter_tablespace_statement(@NotNull Alter_tablespace_statementContext ctx) { + AlterTablespace alter = new AlterTablespace(ctx.space_name.getText()); + alter.setLocation(stripQuote(ctx.uri.getText())); + return alter; + } + + @Override + public Expr visitAlter_table_statement(Alter_table_statementContext ctx) { + + final List<Table_nameContext> tables = ctx.table_name(); + + final AlterTable alterTable = new AlterTable(tables.get(0).getText()); + + if (tables.size() == 2) { + alterTable.setNewTableName(tables.get(1).getText()); + } + + if (checkIfExist(ctx.column_name()) && ctx.column_name().size() == 2) { + final List<Column_nameContext> columns = ctx.column_name(); + alterTable.setColumnName(columns.get(0).getText()); + alterTable.setNewColumnName(columns.get(1).getText()); + } + + Field_elementContext field_elementContext = ctx.field_element(); + if (checkIfExist(field_elementContext)) { + final String name = field_elementContext.name.getText(); + final DataTypeExpr typeDef = visitData_type(field_elementContext.field_type().data_type()); + final ColumnDefinition columnDefinition = new ColumnDefinition(name, typeDef); + alterTable.setAddNewColumn(columnDefinition); + } + + if (checkIfExist(ctx.partition_column_value_list())) { + List<Partition_column_valueContext> columnValueList = ctx.partition_column_value_list().partition_column_value(); + int size = columnValueList.size(); + ColumnReferenceExpr[] columns = new ColumnReferenceExpr[size]; + Expr[] values = new Expr[size]; + for (int i = 0; i < size; i++) { + Partition_column_valueContext columnValue = columnValueList.get(i); + columns[i] = new ColumnReferenceExpr(columnValue.identifier().getText()); + values[i] = visitRow_value_predicand(columnValue.row_value_predicand()); + } + alterTable.setColumns(columns); + alterTable.setValues(values); + if (ctx.LOCATION() != null) { + String path = stripQuote(ctx.path.getText()); + alterTable.setLocation(path); + } + alterTable.setPurge(checkIfExist(ctx.PURGE())); + alterTable.setIfNotExists(checkIfExist(ctx.if_not_exists())); + alterTable.setIfExists(checkIfExist(ctx.if_exists())); + } + + if (checkIfExist(ctx.property_list())) { + alterTable.setParams(getProperties(ctx.property_list())); + } + + alterTable.setAlterTableOpType(determineAlterTableType(ctx)); + + return alterTable; + } + + private Map<String, String> getProperties(Property_listContext ctx) { + Map<String, String> params = new HashMap<String, String>(); + for (int i = 0; i < ctx.property().size(); i++) { + params.put(stripQuote(ctx.property(i).key.getText()), stripQuote(ctx.property(i).value.getText())); + } + + return params; + } + + private AlterTableOpType determineAlterTableType(Alter_table_statementContext ctx) { + + final int RENAME_MASK = 00000001; + final int COLUMN_MASK = 00000010; + final int TO_MASK = 00000100; + final int ADD_MASK = 00001000; + final int DROP_MASK = 00001001; + final int PARTITION_MASK = 00000020; + final int SET_MASK = 00000002; + final int PROPERTY_MASK = 00010000; + + int val = 00000000; + + for (int idx = 1; idx < ctx.getChildCount(); idx++) { + + if (ctx.getChild(idx) instanceof TerminalNode) { + switch (((TerminalNode) ctx.getChild(idx)).getSymbol().getType()) { + case RENAME: + val = val | RENAME_MASK; + break; + case COLUMN: + val = val | COLUMN_MASK; + break; + case TO: + val = val | TO_MASK; + break; + case ADD: + val = val | ADD_MASK; + break; + case DROP: + val = val | DROP_MASK; + break; + case PARTITION: + val = val | PARTITION_MASK; + break; + case SET: + val = val | SET_MASK; + break; + case PROPERTY: + val = val | PROPERTY_MASK; + break; + default: + break; + } + } + } + return evaluateAlterTableOperationTye(val); + } + + private AlterTableOpType evaluateAlterTableOperationTye(final int value) { + + switch (value) { + case 65: + return AlterTableOpType.RENAME_TABLE; + case 73: + return AlterTableOpType.RENAME_COLUMN; + case 520: + return AlterTableOpType.ADD_COLUMN; + case 528: + return AlterTableOpType.ADD_PARTITION; + case 529: + return AlterTableOpType.DROP_PARTITION; + case 4098: + return AlterTableOpType.SET_PROPERTY; + default: + return null; + } + } +}
http://git-wip-us.apache.org/repos/asf/tajo/blob/d6bc9118/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLErrorListener.java ---------------------------------------------------------------------- diff --git a/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLErrorListener.java b/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLErrorListener.java new file mode 100644 index 0000000..e960844 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLErrorListener.java @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.tajo.parser.sql; + +import org.antlr.v4.runtime.*; +import org.apache.commons.lang.StringUtils; + +public class SQLErrorListener extends BaseErrorListener { + public void syntaxError(Recognizer<?, ?> recognizer, + Object offendingSymbol, + int line, int charPositionInLine, + String msg, + RecognitionException e) { + CommonTokenStream tokens = (CommonTokenStream) recognizer.getInputStream(); + String input = tokens.getTokenSource().getInputStream().toString(); + Token token = (Token) offendingSymbol; + String[] lines = StringUtils.splitPreserveAllTokens(input, '\n'); + String errorLine = lines[line - 1]; + + throw new SQLParseError(token, line, charPositionInLine, msg, errorLine); + } +} http://git-wip-us.apache.org/repos/asf/tajo/blob/d6bc9118/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLErrorStrategy.java ---------------------------------------------------------------------- diff --git a/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLErrorStrategy.java b/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLErrorStrategy.java new file mode 100644 index 0000000..236b854 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLErrorStrategy.java @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.tajo.parser.sql; + +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.NotNull; + +public class SQLErrorStrategy extends DefaultErrorStrategy { + + @Override + public void reportError(Parser recognizer, RecognitionException e) { + // if we've already reported an error and have not matched a token + // yet successfully, don't report any errors. + if (inErrorRecoveryMode(recognizer)) { + return; // don't report spurious errors + } + beginErrorCondition(recognizer); + if (e instanceof NoViableAltException) { + reportNoViableAltException(recognizer, (NoViableAltException) e); + } else if (e instanceof InputMismatchException) { + reportInputMismatchException(recognizer, (InputMismatchException) e); + } else if (e instanceof FailedPredicateException) { + reportFailedPredicate(recognizer, (FailedPredicateException) e); + } else { + recognizer.notifyErrorListeners(e.getOffendingToken(), e.getMessage(), e); + } + } + + protected void reportNoViableAltException(@NotNull Parser recognizer, @NotNull NoViableAltException e) { + TokenStream tokens = recognizer.getInputStream(); + String msg; + Token token = e.getStartToken(); + if (tokens != null) { + if (tokens.LT(-1) != null && token.getType() == Token.EOF) { + token = tokens.LT(-1); + } + msg = "syntax error at or near " + getTokenErrorDisplay(token); + } else { + msg = "no viable alternative at input " + escapeWSAndQuote("<unknown input>"); + } + recognizer.notifyErrorListeners(token, msg, e); + } + + protected void reportInputMismatchException(@NotNull Parser recognizer, + @NotNull InputMismatchException e) { + String msg = "mismatched input " + getTokenErrorDisplay(e.getOffendingToken()) + + " expecting " + e.getExpectedTokens().toString(recognizer.getTokenNames()); + recognizer.notifyErrorListeners(e.getOffendingToken(), msg, e); + } +} http://git-wip-us.apache.org/repos/asf/tajo/blob/d6bc9118/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLParseError.java ---------------------------------------------------------------------- diff --git a/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLParseError.java b/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLParseError.java new file mode 100644 index 0000000..f0568a5 --- /dev/null +++ b/tajo-core/src/main/java/org/apache/tajo/parser/sql/SQLParseError.java @@ -0,0 +1,107 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.tajo.parser.sql; + + +import org.antlr.v4.runtime.Token; +import org.apache.commons.lang.StringUtils; + +public class SQLParseError extends RuntimeException { + private String header; + private String errorLine; + private int charPositionInLine; + private int line; + private Token offendingToken; + private String detailedMessage; + + public SQLParseError(Token offendingToken, + int line, int charPositionInLine, + String msg, + String errorLine) { + super(msg); + this.offendingToken = offendingToken; + this.charPositionInLine = charPositionInLine; + this.line = line; + this.errorLine = errorLine; + this.header = msg; + } + + @Override + public String getMessage() { + if (detailedMessage == null) { + if (offendingToken != null) { + detailedMessage = getDetailedMessageWithLocation(); + } else { + StringBuilder sb = new StringBuilder(); + sb.append("ERROR: ").append(header).append("\n"); + sb.append("LINE: ").append(errorLine); + detailedMessage = sb.toString(); + } + } + + return detailedMessage; + } + + public String getMessageHeader(){ + return this.header; + } + + private String getDetailedMessageWithLocation() { + StringBuilder sb = new StringBuilder(); + int displayLimit = 80; + String queryPrefix = "LINE " + line + ":" + charPositionInLine + " "; + String prefixPadding = StringUtils.repeat(" ", queryPrefix.length()); + String locationString; + + int tokenLength = offendingToken.getStopIndex() - offendingToken.getStartIndex() + 1; + if(tokenLength > 0){ + locationString = StringUtils.repeat(" ", charPositionInLine) + StringUtils.repeat("^", tokenLength); + } else { + locationString = StringUtils.repeat(" ", charPositionInLine) + "^"; + } + + sb.append("ERROR: ").append(header).append("\n"); + sb.append(queryPrefix); + + if (errorLine.length() > displayLimit) { + int padding = (displayLimit / 2); + + String ellipsis = " ... "; + int startPos = locationString.length() - padding - 1; + if (startPos <= 0) { + startPos = 0; + sb.append(errorLine.substring(startPos, displayLimit)).append(ellipsis).append("\n"); + sb.append(prefixPadding).append(locationString); + } else if (errorLine.length() - (locationString.length() + padding) <= 0) { + startPos = errorLine.length() - displayLimit - 1; + sb.append(ellipsis).append(errorLine.substring(startPos)).append("\n"); + sb.append(prefixPadding).append(StringUtils.repeat(" ", ellipsis.length())) + .append(locationString.substring(startPos)); + } else { + sb.append(ellipsis).append(errorLine.substring(startPos, startPos + displayLimit)).append(ellipsis).append("\n"); + sb.append(prefixPadding).append(StringUtils.repeat(" ", ellipsis.length())) + .append(locationString.substring(startPos)); + } + } else { + sb.append(errorLine).append("\n"); + sb.append(prefixPadding).append(locationString); + } + return sb.toString(); + } +} http://git-wip-us.apache.org/repos/asf/tajo/blob/d6bc9118/tajo-project/pom.xml ---------------------------------------------------------------------- diff --git a/tajo-project/pom.xml b/tajo-project/pom.xml index 0c042e4..b6a13af 100644 --- a/tajo-project/pom.xml +++ b/tajo-project/pom.xml @@ -781,6 +781,11 @@ </dependency> <dependency> <groupId>org.apache.tajo</groupId> + <artifactId>tajo-sql-parser</artifactId> + <version>${tajo.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tajo</groupId> <artifactId>tajo-pullserver</artifactId> <version>${tajo.version}</version> </dependency>
