All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javacc.Parser.jj Maven / Gradle / Ivy


options {
    STATIC = false;
    IGNORE_CASE = true;
    UNICODE_INPUT = true;
}


PARSER_BEGIN(DrillParserImpl)

package org.apache.drill.exec.planner.sql.parser.impl;

import org.apache.drill.exec.planner.sql.parser.*;
import org.apache.calcite.util.*;
import java.util.*;

import org.apache.calcite.avatica.util.Casing;
import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.runtime.CalciteContextException;
import org.apache.calcite.sql.JoinConditionType;
import org.apache.calcite.sql.JoinType;
import org.apache.calcite.sql.SqlAlter;
import org.apache.calcite.sql.SqlBasicTypeNameSpec;
import org.apache.calcite.sql.SqlBinaryOperator;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlCharStringLiteral;
import org.apache.calcite.sql.SqlCollation;
import org.apache.calcite.sql.SqlCollectionTypeNameSpec;
import org.apache.calcite.sql.SqlDataTypeSpec;
import org.apache.calcite.sql.SqlDelete;
import org.apache.calcite.sql.SqlDescribeSchema;
import org.apache.calcite.sql.SqlDescribeTable;
import org.apache.calcite.sql.SqlDynamicParam;
import org.apache.calcite.sql.SqlExplain;
import org.apache.calcite.sql.SqlExplainFormat;
import org.apache.calcite.sql.SqlExplainLevel;
import org.apache.calcite.sql.SqlFunctionCategory;
import org.apache.calcite.sql.SqlHint;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlInsert;
import org.apache.calcite.sql.SqlInsertKeyword;
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlJdbcDataTypeName;
import org.apache.calcite.sql.SqlJdbcFunctionCall;
import org.apache.calcite.sql.SqlJoin;
import org.apache.calcite.sql.SqlJsonConstructorNullClause;
import org.apache.calcite.sql.SqlJsonEncoding;
import org.apache.calcite.sql.SqlJsonExistsErrorBehavior;
import org.apache.calcite.sql.SqlJsonEmptyOrError;
import org.apache.calcite.sql.SqlJsonQueryEmptyOrErrorBehavior;
import org.apache.calcite.sql.SqlJsonQueryWrapperBehavior;
import org.apache.calcite.sql.SqlJsonValueEmptyOrErrorBehavior;
import org.apache.calcite.sql.SqlJsonValueReturning;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlMatchRecognize;
import org.apache.calcite.sql.SqlMerge;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlNumericLiteral;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlOrderBy;
import org.apache.calcite.sql.SqlPivot;
import org.apache.calcite.sql.SqlPostfixOperator;
import org.apache.calcite.sql.SqlPrefixOperator;
import org.apache.calcite.sql.SqlRowTypeNameSpec;
import org.apache.calcite.sql.SqlSampleSpec;
import org.apache.calcite.sql.SqlSelect;
import org.apache.calcite.sql.SqlSelectKeyword;
import org.apache.calcite.sql.SqlSetOption;
import org.apache.calcite.sql.SqlSnapshot;
import org.apache.calcite.sql.SqlTableRef;
import org.apache.calcite.sql.SqlTypeNameSpec;
import org.apache.calcite.sql.SqlUnnestOperator;
import org.apache.calcite.sql.SqlUnpivot;
import org.apache.calcite.sql.SqlUpdate;
import org.apache.calcite.sql.SqlUserDefinedTypeNameSpec;
import org.apache.calcite.sql.SqlUtil;
import org.apache.calcite.sql.SqlWindow;
import org.apache.calcite.sql.SqlWith;
import org.apache.calcite.sql.SqlWithItem;
import org.apache.calcite.sql.fun.SqlCase;
import org.apache.calcite.sql.fun.SqlInternalOperators;
import org.apache.calcite.sql.fun.SqlLibraryOperators;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.fun.SqlTrimFunction;
import org.apache.calcite.sql.parser.Span;
import org.apache.calcite.sql.parser.SqlAbstractParserImpl;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.parser.SqlParserImplFactory;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.parser.SqlParserUtil;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.util.Glossary;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.SourceStringReader;
import org.apache.calcite.util.Util;
import org.apache.calcite.util.trace.CalciteTrace;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;

import java.io.Reader;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import static org.apache.calcite.util.Static.RESOURCE;

/**
 * SQL parser, generated from Parser.jj by JavaCC.
 *
 * 

The public wrapper for this parser is {@link SqlParser}. */ public class DrillParserImpl extends SqlAbstractParserImpl { private static final Logger LOGGER = CalciteTrace.getParserTracer(); // Can't use quoted literal because of a bug in how JavaCC translates // backslash-backslash. private static final char BACKSLASH = 0x5c; private static final char DOUBLE_QUOTE = 0x22; private static final String DQ = DOUBLE_QUOTE + ""; private static final String DQDQ = DQ + DQ; private static final SqlLiteral LITERAL_ZERO = SqlLiteral.createExactNumeric("0", SqlParserPos.ZERO); private static final SqlLiteral LITERAL_ONE = SqlLiteral.createExactNumeric("1", SqlParserPos.ZERO); private static final SqlLiteral LITERAL_MINUS_ONE = SqlLiteral.createExactNumeric("-1", SqlParserPos.ZERO); private static Metadata metadata; private Casing unquotedCasing; private Casing quotedCasing; private int identifierMaxLength; private SqlConformance conformance; /** * {@link SqlParserImplFactory} implementation for creating parser. */ public static final SqlParserImplFactory FACTORY = new SqlParserImplFactory() { public SqlAbstractParserImpl getParser(Reader reader) { final DrillParserImpl parser = new DrillParserImpl(reader); if (reader instanceof SourceStringReader) { final String sql = ((SourceStringReader) reader).getSourceString(); parser.setOriginalSql(sql); } return parser; } }; public SqlParseException normalizeException(Throwable ex) { try { if (ex instanceof ParseException) { ex = cleanupParseException((ParseException) ex); } return convertException(ex); } catch (ParseException e) { throw new AssertionError(e); } } public Metadata getMetadata() { synchronized (DrillParserImpl.class) { if (metadata == null) { metadata = new MetadataImpl( new DrillParserImpl(new java.io.StringReader(""))); } return metadata; } } public void setTabSize(int tabSize) { jj_input_stream.setTabSize(tabSize); } public void switchTo(SqlAbstractParserImpl.LexicalState state) { final int stateOrdinal = Arrays.asList(DrillParserImplTokenManager.lexStateNames) .indexOf(state.name()); token_source.SwitchTo(stateOrdinal); } public void setQuotedCasing(Casing quotedCasing) { this.quotedCasing = quotedCasing; } public void setUnquotedCasing(Casing unquotedCasing) { this.unquotedCasing = unquotedCasing; } public void setIdentifierMaxLength(int identifierMaxLength) { this.identifierMaxLength = identifierMaxLength; } public void setConformance(SqlConformance conformance) { this.conformance = conformance; } public SqlNode parseSqlExpressionEof() throws Exception { return SqlExpressionEof(); } public SqlNode parseSqlStmtEof() throws Exception { return SqlStmtEof(); } public SqlNodeList parseSqlStmtList() throws Exception { return SqlStmtList(); } public SqlNode parseArray() throws SqlParseException { switchTo(LexicalState.BQID); try { return ArrayLiteral(); } catch (ParseException ex) { throw normalizeException(ex); } catch (TokenMgrError ex) { throw normalizeException(ex); } } private SqlNode extend(SqlNode table, SqlNodeList extendList) { return SqlStdOperatorTable.EXTEND.createCall( Span.of(table, extendList).pos(), table, extendList); } /** Adds a warning that a token such as "HOURS" was used, * whereas the SQL standard only allows "HOUR". * *

Currently, we silently add an exception to a list of warnings. In * future, we may have better compliance checking, for example a strict * compliance mode that throws if any non-standard features are used. */ private TimeUnit warn(TimeUnit timeUnit) throws ParseException { final String token = getToken(0).image.toUpperCase(Locale.ROOT); warnings.add( SqlUtil.newContextException(getPos(), RESOURCE.nonStandardFeatureUsed(token))); return timeUnit; } } PARSER_END(DrillParserImpl) /*************************************** * Utility Codes for Semantic Analysis * ***************************************/ /* For Debug */ JAVACODE void debug_message1() { LOGGER.info("{} , {}", getToken(0).image, getToken(1).image); } JAVACODE String unquotedIdentifier() { return SqlParserUtil.toCase(getToken(0).image, unquotedCasing); } /** * Allows parser to be extended with new types of table references. The * default implementation of this production is empty. */ SqlNode ExtendedTableRef() : { } { UnusedExtension() { return null; } } /** * Allows an OVER clause following a table expression as an extension to * standard SQL syntax. The default implementation of this production is empty. */ SqlNode TableOverOpt() : { } { { return null; } } /* * Parses dialect-specific keywords immediately following the SELECT keyword. */ void SqlSelectKeywords(List keywords) : {} { E() } /* * Parses dialect-specific keywords immediately following the INSERT keyword. */ void SqlInsertKeywords(List keywords) : {} { E() } /* * Parse Floor/Ceil function parameters */ SqlNode FloorCeilOptions(Span s, boolean floorFlag) : { SqlNode node; } { node = StandardFloorCeilOptions(s, floorFlag) { return node; } } /* // This file contains the heart of a parser for SQL SELECT statements. // code can be shared between various parsers (for example, a DDL parser and a // DML parser) but is not a standalone JavaCC file. You need to prepend a // parser declaration (such as that in Parser.jj). */ /* Epsilon */ JAVACODE void E() {} /** @Deprecated */ JAVACODE List startList(Object o) { List list = new ArrayList(); list.add(o); return list; } /* * NOTE jvs 6-Feb-2004: The straightforward way to implement the SQL grammar is * to keep query expressions (SELECT, UNION, etc) separate from row expressions * (+, LIKE, etc). However, this is not possible with an LL(k) parser, because * both kinds of expressions allow parenthesization, so no fixed amount of left * context is ever good enough. A sub-query can be a leaf in a row expression, * and can include operators like UNION, so it's not even possible to use a * syntactic lookahead rule like "look past an indefinite number of parentheses * until you see SELECT, VALUES, or TABLE" (since at that point we still * don't know whether we're parsing a sub-query like ((select ...) + x) * vs. (select ... union select ...). * * The somewhat messy solution is to unify the two kinds of expression, * and to enforce syntax rules using parameterized context. This * is the purpose of the ExprContext parameter. It is passed to * most expression productions, which check the expressions encountered * against the context for correctness. When a query * element like SELECT is encountered, the production calls * checkQueryExpression, which will throw an exception if * a row expression was expected instead. When a row expression like * IN is encountered, the production calls checkNonQueryExpression * instead. It is very important to understand how this works * when modifying the grammar. * * The commingling of expressions results in some bogus ambiguities which are * resolved with LOOKAHEAD hints. The worst example is comma. SQL allows both * (WHERE x IN (1,2)) and (WHERE x IN (select ...)). This means when we parse * the right-hand-side of an IN, we have to allow any kind of expression inside * the parentheses. Now consider the expression "WHERE x IN(SELECT a FROM b * GROUP BY c,d)". When the parser gets to "c,d" it doesn't know whether the * comma indicates the end of the GROUP BY or the end of one item in an IN * list. Luckily, we know that select and comma-list are mutually exclusive * within IN, so we use maximal munch for the GROUP BY comma. However, this * usage of hints could easily mask unintended ambiguities resulting from * future changes to the grammar, making it very brittle. */ JAVACODE protected SqlParserPos getPos() { return new SqlParserPos( token.beginLine, token.beginColumn, token.endLine, token.endColumn); } /** Starts a span at the current position. */ JAVACODE Span span() { return Span.of(getPos()); } JAVACODE void checkQueryExpression(ExprContext exprContext) { switch (exprContext) { case ACCEPT_NON_QUERY: case ACCEPT_SUB_QUERY: case ACCEPT_CURSOR: throw SqlUtil.newContextException(getPos(), RESOURCE.illegalQueryExpression()); } } JAVACODE void checkNonQueryExpression(ExprContext exprContext) { switch (exprContext) { case ACCEPT_QUERY: throw SqlUtil.newContextException(getPos(), RESOURCE.illegalNonQueryExpression()); } } JAVACODE SqlNode checkNotJoin(SqlNode e) { if (e instanceof SqlJoin) { throw SqlUtil.newContextException(e.getParserPosition(), RESOURCE.illegalJoinExpression()); } return e; } /** * Converts a ParseException (local to this particular instantiation * of the parser) into a SqlParseException (common to all parsers). */ JAVACODE SqlParseException convertException(Throwable ex) { if (ex instanceof SqlParseException) { return (SqlParseException) ex; } SqlParserPos pos = null; int[][] expectedTokenSequences = null; String[] tokenImage = null; if (ex instanceof ParseException) { ParseException pex = (ParseException) ex; expectedTokenSequences = pex.expectedTokenSequences; tokenImage = pex.tokenImage; if (pex.currentToken != null) { final Token token = pex.currentToken.next; // Checks token.image.equals("1") to avoid recursive call. // The SqlAbstractParserImpl#MetadataImpl constructor uses constant "1" to // throw intentionally to collect the expected tokens. if (!token.image.equals("1") && getMetadata().isKeyword(token.image) && SqlParserUtil.allowsIdentifier(tokenImage, expectedTokenSequences)) { // If the next token is a keyword, reformat the error message as: // Incorrect syntax near the keyword '{keyword}' at line {line_number}, // column {column_number}. final String expecting = ex.getMessage() .substring(ex.getMessage().indexOf("Was expecting")); final String errorMsg = String.format("Incorrect syntax near the keyword '%s' " + "at line %d, column %d.\n%s", token.image, token.beginLine, token.beginColumn, expecting); // Replace the ParseException with explicit error message. ex = new ParseException(errorMsg); } pos = new SqlParserPos( token.beginLine, token.beginColumn, token.endLine, token.endColumn); } } else if (ex instanceof TokenMgrError) { expectedTokenSequences = null; tokenImage = null; // Example: // Lexical error at line 3, column 24. Encountered "#" after "a". final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile( "(?s)Lexical error at line ([0-9]+), column ([0-9]+).*"); java.util.regex.Matcher matcher = pattern.matcher(ex.getMessage()); if (matcher.matches()) { int line = Integer.parseInt(matcher.group(1)); int column = Integer.parseInt(matcher.group(2)); pos = new SqlParserPos(line, column, line, column); } } else if (ex instanceof CalciteContextException) { // CalciteContextException is the standard wrapper for exceptions // produced by the validator, but in the parser, the standard is // SqlParseException; so, strip it away. In case you were wondering, // the CalciteContextException appears because the parser // occasionally calls into validator-style code such as // SqlSpecialOperator.reduceExpr. CalciteContextException ece = (CalciteContextException) ex; pos = new SqlParserPos( ece.getPosLine(), ece.getPosColumn(), ece.getEndPosLine(), ece.getEndPosColumn()); ex = ece.getCause(); } return new SqlParseException( ex.getMessage(), pos, expectedTokenSequences, tokenImage, ex); } /** * Removes or transforms misleading information from a parse exception. * * @param e dirty excn * * @return clean excn */ JAVACODE ParseException cleanupParseException(ParseException ex) { if (ex.expectedTokenSequences == null) { return ex; } int iIdentifier = Arrays.asList(ex.tokenImage).indexOf(""); // Find all sequences in the error which contain identifier. For // example, // {} // {A} // {B, C} // {D, } // {D, A} // {D, B} // // would yield // {} // {D} final List prefixList = new ArrayList(); for (int i = 0; i < ex.expectedTokenSequences.length; ++i) { int[] seq = ex.expectedTokenSequences[i]; int j = seq.length - 1; int i1 = seq[j]; if (i1 == iIdentifier) { int[] prefix = new int[j]; System.arraycopy(seq, 0, prefix, 0, j); prefixList.add(prefix); } } if (prefixList.isEmpty()) { return ex; } int[][] prefixes = (int[][]) prefixList.toArray(new int[prefixList.size()][]); // Since was one of the possible productions, // we know that the parser will also have included all // of the non-reserved keywords (which are treated as // identifiers in non-keyword contexts). So, now we need // to clean those out, since they're totally irrelevant. final List list = new ArrayList(); Metadata metadata = getMetadata(); for (int i = 0; i < ex.expectedTokenSequences.length; ++i) { int [] seq = ex.expectedTokenSequences[i]; String tokenImage = ex.tokenImage[seq[seq.length - 1]]; String token = SqlParserUtil.getTokenVal(tokenImage); if (token == null || !metadata.isNonReservedKeyword(token)) { list.add(seq); continue; } boolean match = matchesPrefix(seq, prefixes); if (!match) { list.add(seq); } } ex.expectedTokenSequences = (int [][]) list.toArray(new int [list.size()][]); return ex; } JAVACODE boolean matchesPrefix(int[] seq, int[][] prefixes) { nextPrefix: for (int[] prefix : prefixes) { if (seq.length == prefix.length + 1) { for (int k = 0; k < prefix.length; k++) { if (prefix[k] != seq[k]) { continue nextPrefix; } } return true; } } return false; } /***************************************** * Syntactical Descriptions * *****************************************/ SqlNode ExprOrJoinOrOrderedQuery(ExprContext exprContext) : { SqlNode e; final List list = new ArrayList(); } { // Lookhead to distinguish between "TABLE emp" (which will be // matched by ExplicitTable() via Query()) // and "TABLE fun(args)" (which will be matched by TableRef()) ( LOOKAHEAD(2) e = Query(exprContext) e = OrderByLimitOpt(e) { return e; } | e = TableRef1(ExprContext.ACCEPT_QUERY_OR_JOIN) ( e = JoinTable(e) )* { list.add(e); } ( AddSetOpQuery(list, exprContext) )* { return SqlParserUtil.toTree(list); } ) } /** * Parses either a row expression or a query expression with an optional * ORDER BY. * *

Postgres syntax for limit: * *

 *    [ LIMIT { count | ALL } ]
 *    [ OFFSET start ]
*
* *

Trino syntax for limit: * *

 *    [ OFFSET start ]
 *    [ LIMIT { count | ALL } ]
*
* *

MySQL syntax for limit: * *

 *    [ LIMIT { count | start, count } ]
*
* *

SQL:2008 syntax for limit: * *

 *    [ OFFSET start { ROW | ROWS } ]
 *    [ FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } ONLY ]
*
*/ SqlNode OrderedQueryOrExpr(ExprContext exprContext) : { SqlNode e; } { e = QueryOrExpr(exprContext) e = OrderByLimitOpt(e) { return e; } } /** Reads optional "ORDER BY", "LIMIT", "OFFSET", "FETCH" following a query, * {@code e}. If any of them are present, adds them to the query; * otherwise returns the query unchanged. * Throws if they are present and {@code e} is not a query. */ SqlNode OrderByLimitOpt(SqlNode e) : { final SqlNodeList orderBy; final Span s = Span.of(); SqlNode[] offsetFetch = {null, null}; } { ( // use the syntactic type of the expression we just parsed // to decide whether ORDER BY makes sense orderBy = OrderBy(e.isA(SqlKind.QUERY)) | { orderBy = null; } ) [ LimitClause(s, offsetFetch) [ OffsetClause(s, offsetFetch) ] | OffsetClause(s, offsetFetch) [ LimitClause(s, offsetFetch) { if (!this.conformance.isOffsetLimitAllowed()) { throw SqlUtil.newContextException(s.end(this), RESOURCE.offsetLimitNotAllowed()); } } | FetchClause(offsetFetch) ] | FetchClause(offsetFetch) ] { if (orderBy != null || offsetFetch[0] != null || offsetFetch[1] != null) { return new SqlOrderBy(getPos(), e, Util.first(orderBy, SqlNodeList.EMPTY), offsetFetch[0], offsetFetch[1]); } return e; } } /** * Parses an OFFSET clause in an ORDER BY expression. */ void OffsetClause(Span s, SqlNode[] offsetFetch) : { } { // ROW or ROWS is required in SQL:2008 but we make it optional // because it is not present in Postgres-style syntax. { s.add(this); } offsetFetch[0] = UnsignedNumericLiteralOrParam() [ | ] } /** * Parses a FETCH clause in an ORDER BY expression. */ void FetchClause(SqlNode[] offsetFetch) : { } { // SQL:2008-style syntax. "OFFSET ... FETCH ...". // If you specify both LIMIT and FETCH, FETCH wins. ( | ) offsetFetch[1] = UnsignedNumericLiteralOrParam() ( | ) } /** * Parses a LIMIT clause in an ORDER BY expression. */ void LimitClause(Span s, SqlNode[] offsetFetch) : { } { // Postgres-style syntax. "LIMIT ... OFFSET ..." { s.add(this); } ( // MySQL-style syntax. "LIMIT start, count" LOOKAHEAD(2) offsetFetch[0] = UnsignedNumericLiteralOrParam() offsetFetch[1] = UnsignedNumericLiteralOrParam() { if (!this.conformance.isLimitStartCountAllowed()) { throw SqlUtil.newContextException(s.end(this), RESOURCE.limitStartCountNotAllowed()); } } | offsetFetch[1] = UnsignedNumericLiteralOrParam() | ) } /** * Parses a leaf in a query expression (SELECT, VALUES or TABLE). */ SqlNode LeafQuery(ExprContext exprContext) : { SqlNode e; } { { // ensure a query is legal in this context checkQueryExpression(exprContext); } e = SqlSelect() { return e; } | e = TableConstructor() { return e; } | e = ExplicitTable(getPos()) { return e; } } /** * Parses a parenthesized query or single row expression. * Depending on {@code exprContext}, may also accept a join. */ SqlNode ParenthesizedExpression(ExprContext exprContext) : { SqlNode e; } { { // we've now seen left paren, so queries inside should // be allowed as sub-queries switch (exprContext) { case ACCEPT_SUB_QUERY: exprContext = ExprContext.ACCEPT_NONCURSOR; break; case ACCEPT_CURSOR: exprContext = ExprContext.ACCEPT_ALL; break; } } e = ExprOrJoinOrOrderedQuery(exprContext) { exprContext.throwIfNotCompatible(e); return e; } } /** * Parses a parenthesized query or comma-list of row expressions. * *

REVIEW jvs 8-Feb-2004: There's a small hole in this production. It can be * used to construct something like * *

 * WHERE x IN (select count(*) from t where c=d,5)
*
* *

which should be illegal. The above is interpreted as equivalent to * *

 * WHERE x IN ((select count(*) from t where c=d),5)
*
* *

which is a legal use of a sub-query. The only way to fix the hole is to * be able to remember whether a subexpression was parenthesized or not, which * means preserving parentheses in the SqlNode tree. This is probably * desirable anyway for use in purely syntactic parsing applications (e.g. SQL * pretty-printer). However, if this is done, it's important to also make * isA() on the paren node call down to its operand so that we can * always correctly discriminate a query from a row expression. */ SqlNodeList ParenthesizedQueryOrCommaList( ExprContext exprContext) : { SqlNode e; final List list = new ArrayList(); ExprContext firstExprContext = exprContext; final Span s; } { { // we've now seen left paren, so a query by itself should // be interpreted as a sub-query s = span(); switch (exprContext) { case ACCEPT_SUB_QUERY: firstExprContext = ExprContext.ACCEPT_NONCURSOR; break; case ACCEPT_CURSOR: firstExprContext = ExprContext.ACCEPT_ALL; break; } } e = OrderedQueryOrExpr(firstExprContext) { list.add(e); } ( { // a comma-list can't appear where only a query is expected checkNonQueryExpression(exprContext); } AddExpression(list, exprContext) )* { return new SqlNodeList(list, s.end(this)); } } /** As ParenthesizedQueryOrCommaList, but allows DEFAULT * in place of any of the expressions. For example, * {@code (x, DEFAULT, null, DEFAULT)}. */ SqlNodeList ParenthesizedQueryOrCommaListWithDefault( ExprContext exprContext) : { SqlNode e; final List list = new ArrayList(); ExprContext firstExprContext = exprContext; final Span s; } { { // we've now seen left paren, so a query by itself should // be interpreted as a sub-query s = span(); switch (exprContext) { case ACCEPT_SUB_QUERY: firstExprContext = ExprContext.ACCEPT_NONCURSOR; break; case ACCEPT_CURSOR: firstExprContext = ExprContext.ACCEPT_ALL; break; } } ( e = OrderedQueryOrExpr(firstExprContext) { list.add(e); } | e = Default() { list.add(e); } ) ( { // a comma-list can't appear where only a query is expected checkNonQueryExpression(exprContext); } ( e = Expression(exprContext) { list.add(e); } | e = Default() { list.add(e); } ) )* { return new SqlNodeList(list, s.end(this)); } } /** * Parses function parameter lists. * If the list starts with DISTINCT or ALL, it is discarded. */ List UnquantifiedFunctionParameterList(ExprContext exprContext) : { final List args; } { args = FunctionParameterList(exprContext) { args.remove(0); // remove DISTINCT or ALL, if present return args; } } /** * Parses function parameter lists including DISTINCT keyword recognition, * DEFAULT, and named argument assignment. */ List FunctionParameterList(ExprContext exprContext) : { final SqlLiteral qualifier; final List list = new ArrayList(); } { ( qualifier = AllOrDistinct() { list.add(qualifier); } | { list.add(null); } ) AddArg0(list, exprContext) ( { // a comma-list can't appear where only a query is expected checkNonQueryExpression(exprContext); } AddArg(list, exprContext) )* { return list; } } SqlLiteral AllOrDistinct() : { } { { return SqlSelectKeyword.DISTINCT.symbol(getPos()); } | { return SqlSelectKeyword.ALL.symbol(getPos()); } } void AddArg0(List list, ExprContext exprContext) : { final SqlIdentifier name; SqlNode e; final ExprContext firstExprContext; { // we've now seen left paren, so queries inside should // be allowed as sub-queries switch (exprContext) { case ACCEPT_SUB_QUERY: firstExprContext = ExprContext.ACCEPT_NONCURSOR; break; case ACCEPT_CURSOR: firstExprContext = ExprContext.ACCEPT_ALL; break; default: firstExprContext = exprContext; break; } } } { ( LOOKAHEAD(2) name = SimpleIdentifier() | { name = null; } ) ( e = Default() | LOOKAHEAD(3) e = TableParam() | e = PartitionedQueryOrQueryOrExpr(firstExprContext) ) { if (name != null) { e = SqlStdOperatorTable.ARGUMENT_ASSIGNMENT.createCall( Span.of(name, e).pos(), e, name); } list.add(e); } } void AddArg(List list, ExprContext exprContext) : { final SqlIdentifier name; SqlNode e; } { ( LOOKAHEAD(2) name = SimpleIdentifier() | { name = null; } ) ( e = Default() | e = Expression(exprContext) | e = TableParam() ) { if (name != null) { e = SqlStdOperatorTable.ARGUMENT_ASSIGNMENT.createCall( Span.of(name, e).pos(), e, name); } list.add(e); } } SqlNode Default() : {} { { return SqlStdOperatorTable.DEFAULT.createCall(getPos()); } } /** * Parses a query (SELECT, UNION, INTERSECT, EXCEPT, VALUES, TABLE) followed by * the end-of-file symbol. */ SqlNode SqlQueryEof() : { SqlNode query; } { query = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) { return query; } } /** * Parses a list of SQL statements separated by semicolon. * The semicolon is required between statements, but is * optional at the end. */ SqlNodeList SqlStmtList() : { final List stmtList = new ArrayList(); SqlNode stmt; } { stmt = SqlStmt() { stmtList.add(stmt); } ( [ stmt = SqlStmt() { stmtList.add(stmt); } ] )* { return new SqlNodeList(stmtList, Span.of(stmtList).pos()); } } /** * Parses an SQL statement. */ SqlNode SqlStmt() : { SqlNode stmt; } { ( LOOKAHEAD(2) stmt = SqlShowTables() | LOOKAHEAD(2) stmt = SqlShowSchemas() | LOOKAHEAD(2) stmt = SqlDescribeSchema() | LOOKAHEAD(2) stmt = SqlDescribeTable() | LOOKAHEAD(2) stmt = SqlUseSchema() | LOOKAHEAD(2) stmt = SqlCreateOrReplace() | LOOKAHEAD(2) stmt = SqlDropAlias() | LOOKAHEAD(2) stmt = SqlDropAllAliases() | LOOKAHEAD(2) stmt = SqlDrop() | LOOKAHEAD(2) stmt = SqlShowFiles() | LOOKAHEAD(2) stmt = SqlRefreshMetadata() | LOOKAHEAD(2) stmt = SqlCreateFunction() | LOOKAHEAD(2) stmt = SqlDropFunction() | LOOKAHEAD(2) stmt = SqlAnalyzeTable() | LOOKAHEAD(2) stmt = DrillSqlSetOption(Span.of(), null) | LOOKAHEAD(2) stmt = DrillSqlResetOption(Span.of(), null) | LOOKAHEAD(2) stmt = SqlAlterSchema() | stmt = SqlSetOption(Span.of(), null) | stmt = SqlAlter() | stmt = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) | stmt = SqlExplain() | stmt = SqlDescribe() | stmt = SqlInsert() | stmt = SqlDelete() | stmt = SqlUpdate() | stmt = SqlMerge() | stmt = SqlProcedureCall() ) { return stmt; } } /** * Parses an SQL statement followed by the end-of-file symbol. */ SqlNode SqlStmtEof() : { SqlNode stmt; } { stmt = SqlStmt() { return stmt; } } /** * Parses statement * SHOW TABLES [{FROM | IN} db_name] [LIKE 'pattern' | WHERE expr] */ SqlNode SqlShowTables() : { SqlParserPos pos; SqlIdentifier db = null; SqlNode likePattern = null; SqlNode where = null; } { { pos = getPos(); } [ ( | ) { db = CompoundIdentifier(); } ] [ { likePattern = StringLiteral(); } | { where = Expression(ExprContext.ACCEPT_SUBQUERY); } ] { return new SqlShowTables(pos, db, likePattern, where); } } /** * Parses statement * SHOW FILES [{FROM | IN} schema] */ SqlNode SqlShowFiles() : { SqlParserPos pos = null; SqlIdentifier db = null; } { { pos = getPos(); } [ ( | ) { db = CompoundIdentifier(); } ] { return new SqlShowFiles(pos, db); } } /** * Parses statement SHOW {DATABASES | SCHEMAS} [LIKE 'pattern' | WHERE expr] */ SqlNode SqlShowSchemas() : { SqlParserPos pos; SqlNode likePattern = null; SqlNode where = null; } { { pos = getPos(); } ( | ) [ { likePattern = StringLiteral(); } | { where = Expression(ExprContext.ACCEPT_SUBQUERY); } ] { return new SqlShowSchemas(pos, likePattern, where); } } /** * Parses statement * { DESCRIBE | DESC } [TABLE] tblname [col_name | wildcard ] */ SqlNode SqlDescribeTable() : { SqlParserPos pos; SqlIdentifier table; SqlIdentifier column = null; SqlNode columnPattern = null; } { ( | ) { pos = getPos(); } (

)? table = CompoundIdentifier() ( column = CompoundIdentifier() | columnPattern = StringLiteral() | E() ) { return new DrillSqlDescribeTable(pos, table, column, columnPattern); } } SqlNode SqlUseSchema(): { SqlIdentifier schema; SqlParserPos pos; } { { pos = getPos(); } schema = CompoundIdentifier() { return new SqlUseSchema(pos, schema); } } /** Parses an optional field list and makes sure no field is a "*". */ SqlNodeList ParseOptionalFieldList(String relType) : { SqlNodeList fieldList; } { fieldList = ParseRequiredFieldList(relType) { return fieldList; } | { return SqlNodeList.EMPTY; } } /** Parses a required field list and makes sure no field is a "*". */ SqlNodeList ParseRequiredFieldList(String relType) : { Pair fieldList; } { fieldList = ParenthesizedCompoundIdentifierList() { for(SqlNode node : fieldList.left) { if (((SqlIdentifier) node).isStar()) throw new ParseException(String.format("%s's field list has a '*', which is invalid.", relType)); } return fieldList.left; } } /** * Parses CREATE [OR REPLACE] command for VIEW, TABLE or SCHEMA. */ SqlNode SqlCreateOrReplace() : { SqlParserPos pos; String createType = "SIMPLE"; boolean isTemporary = false; boolean isPublic = false; } { { pos = getPos(); } [ { createType = "OR_REPLACE"; } ] [ { isTemporary = true; } ] ( { if (isTemporary) { throw new ParseException("Create view statement does not allow keyword."); } return SqlCreateView(pos, createType); } |
{ if (createType == "OR_REPLACE") { throw new ParseException("Create table statement does not allow ."); } return SqlCreateTable(pos, isTemporary); } | { if (isTemporary) { throw new ParseException("Create schema statement does not allow keyword."); } return SqlCreateSchema(pos, createType); } | [ { isPublic = true; } ] { if (isTemporary) { throw new ParseException("Create alias statement does not allow keyword."); } return SqlCreateAlias(pos, createType.equals("OR_REPLACE"), isPublic); } ) } /** * Parses a create view or replace existing view statement. * after CREATE OR REPLACE VIEW statement which is handled in the SqlCreateOrReplace method. * * CREATE { [OR REPLACE] VIEW | VIEW [IF NOT EXISTS] | VIEW } view_name [ (field1, field2 ...) ] AS select_statement */ SqlNode SqlCreateView(SqlParserPos pos, String createType) : { SqlIdentifier viewName; SqlNode query; SqlNodeList fieldList; } { [ { if (createType == "OR_REPLACE") { throw new ParseException("Create view statement cannot have both and clause"); } createType = "IF_NOT_EXISTS"; } ] viewName = CompoundIdentifier() fieldList = ParseOptionalFieldList("View") query = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) { return new SqlCreateView(pos, viewName, fieldList, query, SqlLiteral.createCharString(createType, getPos())); } } /** * Parses a CTAS or CTTAS statement after CREATE [TEMPORARY] TABLE statement * which is handled in the SqlCreateOrReplace method. * * CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tblname [ (field1, field2, ...) ] AS select_statement. */ SqlNode SqlCreateTable(SqlParserPos pos, boolean isTemporary) : { SqlIdentifier tblName; SqlNodeList fieldList; SqlNodeList partitionFieldList; SqlNode query; boolean tableNonExistenceCheck = false; } { { partitionFieldList = SqlNodeList.EMPTY; } ( { tableNonExistenceCheck = true; } )? tblName = CompoundIdentifier() fieldList = ParseOptionalFieldList("Table") ( partitionFieldList = ParseRequiredFieldList("Partition") )? query = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) { return new SqlCreateTable(pos, tblName, fieldList, partitionFieldList, query, SqlLiteral.createBoolean(isTemporary, getPos()), SqlLiteral.createBoolean(tableNonExistenceCheck, getPos())); } } /** * Parses create table schema statement after CREATE OR REPLACE SCHEMA statement * which is handled in the SqlCreateOrReplace method. * * CREATE [OR REPLACE] SCHEMA * [ * LOAD 'file:///path/to/raw_schema' * | * ( * col1 int, * col2 varchar(10) not null * ) * ] * [FOR TABLE dfs.my_table] * [PATH 'file:///path/to/schema'] * [PROPERTIES ('prop1'='val1', 'prop2'='val2')] */ SqlNode SqlCreateSchema(SqlParserPos pos, String createType) : { SqlCharStringLiteral schema = null; SqlNode load = null; SqlIdentifier table = null; SqlNode path = null; SqlNodeList properties = null; } { { token_source.pushState(); token_source.SwitchTo(SCH); } ( { load = StringLiteral(); } | { schema = SqlLiteral.createCharString(token.image, getPos()); } ) (
{ table = CompoundIdentifier(); } | { path = StringLiteral(); if (createType == "OR_REPLACE") { throw new ParseException(" cannot be used with property."); } } ) [ { properties = new SqlNodeList(getPos()); addProperty(properties); } ( { addProperty(properties); } )* ] { return new SqlSchema.Create(pos, schema, load, table, path, properties, SqlLiteral.createCharString(createType, getPos())); } } /** * Helper method to add string literals divided by equals into SqlNodeList. */ void addProperty(SqlNodeList properties) : {} { { properties.add(StringLiteral()); } { properties.add(StringLiteral()); } } SKIP : { " " | "\t" | "\n" | "\r" } TOKEN : { < SCH_LOAD: "LOAD" > { popState(); } | < SCH_NUM: (" " | "\t" | "\n" | "\r")* > // once schema is found, switch back to initial lexical state // must be enclosed in the parentheses // inside may have left parenthesis only if number precedes (covers cases with varchar(10)), // if left parenthesis is present in column name, it must be escaped with backslash | < SCH_PAREN_STRING: ((~[")"]) | ( ")") | ("\\)"))* > { popState(); } } /** * Parses DROP command for VIEW, TABLE and SCHEMA. */ SqlNode SqlDrop() : { SqlParserPos pos; } { { pos = getPos(); } ( { return SqlDropView(pos); } |
{ return SqlDropTable(pos); } | { return SqlDropSchema(pos); } ) } /** * Parses a drop view or drop view if exists statement * after DROP VIEW statement which is handled in SqlDrop method. * * DROP VIEW [IF EXISTS] view_name; */ SqlNode SqlDropView(SqlParserPos pos) : { boolean viewExistenceCheck = false; } { [ { viewExistenceCheck = true; } ] { return new SqlDropView(pos, CompoundIdentifier(), viewExistenceCheck); } } /** * Parses a drop table or drop table if exists statement * after DROP TABLE statement which is handled in SqlDrop method. * * DROP TABLE [IF EXISTS] table_name; */ SqlNode SqlDropTable(SqlParserPos pos) : { boolean tableExistenceCheck = false; } { [ { tableExistenceCheck = true; } ] { return new SqlDropTable(pos, CompoundIdentifier(), tableExistenceCheck); } } /** * Parses drop schema or drop schema if exists statement * after DROP SCHEMA statement which is handled in SqlDrop method. * * DROP SCHEMA [IF EXISTS] * FOR TABLE dfs.my_table */ SqlNode SqlDropSchema(SqlParserPos pos) : { SqlIdentifier table = null; boolean existenceCheck = false; } { [ { existenceCheck = true; } ]
{ table = CompoundIdentifier(); } { return new SqlSchema.Drop(pos, table, SqlLiteral.createBoolean(existenceCheck, getPos())); } } /** * Parse refresh table metadata statement. * REFRESH TABLE METADATA [COLUMNS ((field1, field2,..) | NONE)] table_name */ SqlNode SqlRefreshMetadata() : { SqlParserPos pos; SqlIdentifier tblName; SqlNodeList fieldList = null; SqlNode query; boolean allColumnsInteresting = true; } { { pos = getPos(); }
[ { allColumnsInteresting = false; } ( fieldList = ParseRequiredFieldList("Table") | ) ] tblName = CompoundIdentifier() { return new SqlRefreshMetadata(pos, tblName, SqlLiteral.createBoolean(allColumnsInteresting, getPos()), fieldList); } } /** * Parses statement * { DESCRIBE | DESC } { SCHEMA | DATABASE } name * { DESCRIBE | DESC } SCHEMA FOR TABLE dfs.my_table [AS (JSON | STATEMENT)] */ SqlNode SqlDescribeSchema() : { SqlParserPos pos; SqlIdentifier table; String format = "JSON"; } { ( | ) { pos = getPos(); } ( (
{ table = CompoundIdentifier(); } [ ( { format = "JSON"; } | { format = "STATEMENT"; } ) ] { return new SqlSchema.Describe(pos, table, SqlLiteral.createCharString(format, getPos())); } | { return new SqlDescribeSchema(pos, CompoundIdentifier()); } ) | { return new SqlDescribeSchema(pos, CompoundIdentifier()); } ) } /** * Parses ALTER SCHEMA statements: * * ALTER SCHEMA * (FOR TABLE dfs.tmp.nation | PATH '/tmp/schema.json') * ADD [OR REPLACE] * [COLUMNS (col1 int, col2 varchar)] * [PROPERTIES ('prop1'='val1', 'prop2'='val2')] * * ALTER SCHEMA * (FOR TABLE dfs.tmp.nation | PATH '/tmp/schema.json') * REMOVE * [COLUMNS (`col1`, `col2`)] * [PROPERTIES ('prop1', 'prop2')] */ SqlNode SqlAlterSchema() : { SqlParserPos pos; SqlIdentifier table = null; SqlNode path = null; } { { pos = getPos(); } (
{ table = CompoundIdentifier(); } | { path = StringLiteral(); } ) ( { return SqlAlterSchemaAdd(pos, table, path); } | { return SqlAlterSchemaRemove(pos, table, path); } ) } SqlNode SqlAlterSchemaAdd(SqlParserPos pos, SqlIdentifier table, SqlNode path) : { boolean replace = false; SqlCharStringLiteral schema = null; SqlNodeList properties = null; } { [ { replace = true; } ] [ { schema = ParseSchema(); } ] [ { properties = new SqlNodeList(getPos()); addProperty(properties); } ( { addProperty(properties); } )* ] { if (schema == null && properties == null) { throw new ParseException("ALTER SCHEMA ADD command must have at least or keyword indicated."); } return new SqlSchema.Add(pos, table, path, SqlLiteral.createBoolean(replace, getPos()), schema, properties); } } SqlCharStringLiteral ParseSchema() : {} { { token_source.pushState(); token_source.SwitchTo(SCH); } { return SqlLiteral.createCharString(token.image, getPos()); } } SqlNode SqlAlterSchemaRemove(SqlParserPos pos, SqlIdentifier table, SqlNode path) : { SqlNodeList columns = null; SqlNodeList properties = null; } { [ { columns = ParseRequiredFieldList("Schema"); } ] [ { properties = new SqlNodeList(getPos()); properties.add(StringLiteral()); } ( { properties.add(StringLiteral()); } )* ] { if (columns == null && properties == null) { throw new ParseException("ALTER SCHEMA REMOVE command must have at least or keyword indicated."); } return new SqlSchema.Remove(pos, table, path, columns, properties); } } /** * Parse create UDF statement * CREATE FUNCTION USING JAR 'jar_name' */ SqlNode SqlCreateFunction() : { SqlParserPos pos; SqlNode jar; } { { pos = getPos(); } jar = StringLiteral() { return new SqlCreateFunction(pos, jar); } } /** * Parse drop UDF statement * DROP FUNCTION USING JAR 'jar_name' */ SqlNode SqlDropFunction() : { SqlParserPos pos; SqlNode jar; } { { pos = getPos(); } jar = StringLiteral() { return new SqlDropFunction(pos, jar); } } /** * Parses a analyze statements: *
    *
  • ANALYZE TABLE [table_name | table({table function name}(parameters))] [COLUMNS {(col1, col2, ...) | NONE}] REFRESH METADATA ['level' LEVEL] [{COMPUTE | ESTIMATE} | STATISTICS [ SAMPLE number PERCENT ]] *
  • ANALYZE TABLE [table_name] DROP [METADATA|STATISTICS] [IF EXISTS] *
  • ANALYZE TABLE [table_name | table({table function name}(parameters))] {COMPUTE | ESTIMATE} | STATISTICS [(column1, column2, ...)] [ SAMPLE numeric PERCENT ] *
*/ SqlNode SqlAnalyzeTable() : { SqlParserPos pos; SqlNode tableRef; Span s = null; SqlNodeList fieldList = null; SqlNode level = null; SqlLiteral estimate = null; SqlLiteral dropMetadata = null; SqlLiteral checkMetadataExistence = null; SqlNumericLiteral percent = null; } { { pos = getPos(); }
( tableRef = CompoundIdentifier() | tableRef = TableFunctionCall() ) [ ( ( { estimate = SqlLiteral.createBoolean(false, pos); } | { if (true) { throw new ParseException("ESTIMATE statistics collecting is not supported. See DRILL-7438."); } estimate = SqlLiteral.createBoolean(true, pos); } ) [ (fieldList = ParseRequiredFieldList("Table")) ] [ percent = UnsignedNumericLiteral() { BigDecimal rate = percent.bigDecimalValue(); if (rate.compareTo(BigDecimal.ZERO) <= 0 || rate.compareTo(BigDecimal.valueOf(100L)) > 0) { throw new ParseException("Invalid percentage for ANALYZE TABLE"); } } ] { if (percent == null) { percent = SqlLiteral.createExactNumeric("100.0", pos); } return new SqlAnalyzeTable(pos, tableRef, estimate, fieldList, percent); } ) | ( [ ( fieldList = ParseRequiredFieldList("Table") | {fieldList = SqlNodeList.EMPTY;} ) ] [ level = StringLiteral() ] [ ( { estimate = SqlLiteral.createBoolean(false, pos); } | { if (true) { throw new ParseException("ESTIMATE statistics collecting is not supported. See DRILL-7438."); } estimate = SqlLiteral.createBoolean(true, pos); } ) ] [ percent = UnsignedNumericLiteral() { BigDecimal rate = percent.bigDecimalValue(); if (rate.compareTo(BigDecimal.ZERO) <= 0 || rate.compareTo(BigDecimal.valueOf(100L)) > 0) { throw new ParseException("Invalid percentage for ANALYZE TABLE"); } } ] { return new SqlMetastoreAnalyzeTable(pos, tableRef, fieldList, level, estimate, percent); } ) | ( [ { dropMetadata = SqlLiteral.createCharString("METADATA", pos); } | { if (true) { throw new ParseException("DROP STATISTICS is not supported."); } dropMetadata = SqlLiteral.createCharString("STATISTICS", pos); } ] [ { checkMetadataExistence = SqlLiteral.createBoolean(false, pos); } ] { if (checkMetadataExistence == null) { checkMetadataExistence = SqlLiteral.createBoolean(true, pos); } if (s != null) { throw new ParseException("Table functions shouldn't be used in DROP METADATA statement."); } return new SqlDropTableMetadata(pos, (SqlIdentifier) tableRef, dropMetadata, checkMetadataExistence); } ) ] { throw generateParseException(); } } /** * Parses a SET statement without a leading "ALTER ": * * SET <NAME> [ = VALUE ] *

* Statement handles in: {@link SetAndResetOptionHandler} */ DrillSqlSetOption DrillSqlSetOption(Span s, String scope) : { SqlParserPos pos; SqlIdentifier name; SqlNode val = null; } { { s.add(this); } name = CompoundIdentifier() ( ( val = Literal() | val = SimpleIdentifier() ) )? { pos = (val == null) ? s.end(name) : s.end(val); return new DrillSqlSetOption(pos, scope, name, val); } } /** * Parses a RESET statement without a leading "ALTER ": * * RESET { | ALL } *

* Statement handles in: {@link SetAndResetOptionHandler} */ DrillSqlResetOption DrillSqlResetOption(Span s, String scope) : { SqlIdentifier name; } { { s.add(this); } ( name = CompoundIdentifier() | { name = new SqlIdentifier(token.image.toUpperCase(Locale.ROOT), getPos()); } ) { return new DrillSqlResetOption(s.end(name), scope, name); } } /** * Parses CREATE ALIAS statement * CREATE [OR REPLACE] [PUBLIC] ALIAS `alias` FOR [TABLE | STORAGE] `table/storage` [AS USER 'username'] */ SqlNode SqlCreateAlias(SqlParserPos pos, boolean replace, boolean isPublic) : { SqlIdentifier alias = null; String aliasTarget = "TABLE"; SqlIdentifier source = null; SqlNode user = null; } { { alias = CompoundIdentifier(); } [

{ aliasTarget = "TABLE"; } | { aliasTarget = "STORAGE"; } ] { source = CompoundIdentifier(); } [ { user = StringLiteral(); } ] { return SqlCreateAlias.builder() .pos(pos) .alias(alias) .source(source) .aliasKind(SqlLiteral.createCharString(aliasTarget, pos)) .replace(SqlLiteral.createBoolean(replace, pos)) .isPublic(SqlLiteral.createBoolean(isPublic, pos)) .user(user) .build(); } } /** * Parses DROP ALIAS statement * DROP [PUBLIC] ALIAS [IF EXISTS] `employee-alias` [FOR (TABLE | STORAGE)] [AS USER 'username'] */ SqlNode SqlDropAlias() : { SqlParserPos pos; boolean isPublic = false; boolean ifExists = false; SqlIdentifier alias = null; String aliasTarget = "TABLE"; SqlNode user = null; } { { pos = getPos(); } [ { isPublic = true; } ] [ { ifExists = true; } ] { alias = CompoundIdentifier(); } [ (
{ aliasTarget = "TABLE"; } | { aliasTarget = "STORAGE"; } ) ] [ { user = StringLiteral(); } ] { return SqlDropAlias.builder() .pos(pos) .alias(alias) .aliasKind(SqlLiteral.createCharString(aliasTarget, pos)) .ifExists(SqlLiteral.createBoolean(ifExists, pos)) .isPublic(SqlLiteral.createBoolean(isPublic, pos)) .user(user) .build(); } } /** * Parses DROP ALL ALIASES statement * DROP ALL [PUBLIC] ALIASES [FOR (TABLE | STORAGE)] [AS USER 'username'] */ SqlNode SqlDropAllAliases() : { SqlParserPos pos; boolean isPublic = false; String aliasTarget = "TABLE"; SqlNode user = null; } { { pos = getPos(); } [ { isPublic = true; } ] [ (
{ aliasTarget = "TABLE"; } | { aliasTarget = "STORAGE"; } ) ] [ { user = StringLiteral(); } ] { return SqlDropAllAliases.builder() .pos(pos) .aliasKind(SqlLiteral.createCharString(aliasTarget, pos)) .isPublic(SqlLiteral.createBoolean(isPublic, pos)) .user(user) .build(); } } SqlNodeList ParenthesizedKeyValueOptionCommaList() : { final Span s; final List list = new ArrayList(); } { { s = span(); } AddKeyValueOption(list) ( AddKeyValueOption(list) )* { return new SqlNodeList(list, s.end(this)); } } /** * Parses an option with format key=val whose key is a simple identifier or string literal * and value is a string literal. */ void AddKeyValueOption(List list) : { final SqlNode key; final SqlNode value; } { ( key = SimpleIdentifier() | key = StringLiteral() ) value = StringLiteral() { list.add(key); list.add(value); } } /** Parses an option value (either a string or a numeric) and adds to a list. */ void AddOptionValue(List list) : { final SqlNode value; } { ( value = NumericLiteral() { list.add(value); } | value = StringLiteral() { list.add(value); } ) } /** * Parses a literal list separated by comma. The literal is either a string or a numeric. */ SqlNodeList ParenthesizedLiteralOptionCommaList() : { final Span s; final List list = new ArrayList(); } { { s = span(); } AddOptionValue(list) ( AddOptionValue(list) )* { return new SqlNodeList(list, s.end(this)); } } void AddHint(List hints) : { final SqlIdentifier hintName; final SqlNodeList hintOptions; final SqlHint.HintOptionFormat optionFormat; } { hintName = SimpleIdentifier() ( LOOKAHEAD(5) hintOptions = ParenthesizedKeyValueOptionCommaList() { optionFormat = SqlHint.HintOptionFormat.KV_LIST; } | LOOKAHEAD(3) hintOptions = ParenthesizedSimpleIdentifierList() { optionFormat = SqlHint.HintOptionFormat.ID_LIST; } | LOOKAHEAD(3) hintOptions = ParenthesizedLiteralOptionCommaList() { optionFormat = SqlHint.HintOptionFormat.LITERAL_LIST; } | LOOKAHEAD(2) [ ] { hintOptions = SqlNodeList.EMPTY; optionFormat = SqlHint.HintOptionFormat.EMPTY; } ) { hints.add( new SqlHint(Span.of(hintOptions).end(this), hintName, hintOptions, optionFormat)); } } /** Parses hints following a table reference, * and returns the wrapped table reference. */ SqlNode TableHints(SqlIdentifier tableName) : { final List hints = new ArrayList(); } { AddHint(hints) ( AddHint(hints) )* { final SqlParserPos pos = Span.of(tableName).addAll(hints).end(this); final SqlNodeList hintList = new SqlNodeList(hints, pos); return new SqlTableRef(pos, tableName, hintList); } } /** * Parses a leaf SELECT expression without ORDER BY. */ SqlSelect SqlSelect() : { final List keywords = new ArrayList(); final SqlLiteral keyword; final SqlNodeList keywordList; final List selectList = new ArrayList(); final SqlNode fromClause; final SqlNode where; final SqlNodeList groupBy; final SqlNode having; final SqlNodeList windowDecls; final SqlNode qualify; final List hints = new ArrayList(); final Span s; } {
| | | | | | ) (
)? table = CompoundIdentifier() ( column = SimpleIdentifier() | { column = null; } ) { return new SqlDescribeTable(s.add(table).addIf(column).pos(), table, column); } | (LOOKAHEAD(1) )? stmt = SqlQueryOrDml() { // DESCRIBE STATEMENT currently does the same as EXPLAIN. See // [CALCITE-1221] Implement DESCRIBE DATABASE, CATALOG, STATEMENT final SqlExplainLevel detailLevel = SqlExplainLevel.EXPPLAN_ATTRIBUTES; final SqlExplain.Depth depth = SqlExplain.Depth.PHYSICAL; final SqlExplainFormat format = SqlExplainFormat.TEXT; return new SqlExplain(s.end(stmt), stmt, detailLevel.symbol(SqlParserPos.ZERO), depth.symbol(SqlParserPos.ZERO), format.symbol(SqlParserPos.ZERO), nDynamicParams); } ) } /** * Parses a CALL statement. */ SqlNode SqlProcedureCall() : { final Span s; SqlNode routineCall; } { { s = span(); } routineCall = NamedRoutineCall( SqlFunctionCategory.USER_DEFINED_PROCEDURE, ExprContext.ACCEPT_SUB_QUERY) { return SqlStdOperatorTable.PROCEDURE_CALL.createCall( s.end(routineCall), routineCall); } } SqlNode NamedRoutineCall( SqlFunctionCategory routineType, ExprContext exprContext) : { final SqlIdentifier name; final List list = new ArrayList(); final Span s; } { name = CompoundIdentifier() { s = span(); } [ AddArg0(list, exprContext) ( { // a comma-list can't appear where only a query is expected checkNonQueryExpression(exprContext); } AddArg(list, exprContext) )* ] { return createCall(name, s.end(this), routineType, null, list); } } /** * Table parameter of a table function. * The input table with set semantics may be partitioned/ordered on one or more columns. */ SqlNode TableParam() : { final Span s; final SqlNodeList partitionList; final SqlNodeList orderList; SqlNode tableRef; } { { s = span(); } tableRef = ExplicitTable(getPos()) ( partitionList = SimpleIdentifierOrList() | { partitionList = SqlNodeList.EMPTY; } ) ( orderList = OrderByOfSetSemanticsTable() | { orderList = SqlNodeList.EMPTY; } ) { return CreateSetSemanticsTableIfNeeded(s, tableRef, partitionList, orderList); } } SqlNode PartitionedQueryOrQueryOrExpr(ExprContext exprContext) : { SqlNode e; } { e = OrderedQueryOrExpr(exprContext) e = PartitionedByAndOrderBy(e) { return e; } } SqlNode PartitionedByAndOrderBy(SqlNode e) : { final Span s; final SqlNodeList partitionList; final SqlNodeList orderList; } { { s = span(); } ( partitionList = SimpleIdentifierOrList() | { partitionList = SqlNodeList.EMPTY; } ) ( orderList = OrderByOfSetSemanticsTable() | { orderList = SqlNodeList.EMPTY; } ) { return CreateSetSemanticsTableIfNeeded(s, e, partitionList, orderList); } } SqlNodeList OrderByOfSetSemanticsTable() : { final List list = new ArrayList(); final Span s; } { { s = span(); } ( LOOKAHEAD(2) AddOrderItem(list) ( // NOTE jvs 6-Feb-2004: See comments at top of file for why // hint is necessary here. LOOKAHEAD(2) AddOrderItem(list) )* { return new SqlNodeList(list, s.addAll(list).pos()); } | AddOrderItem(list) { return new SqlNodeList(list, s.addAll(list).pos()); } ) } SqlNode CreateSetSemanticsTableIfNeeded( final Span s, final SqlNode e, final SqlNodeList partitionList, final SqlNodeList orderList) : { } { { if (partitionList.isEmpty() && orderList.isEmpty()) { return e; } else { return SqlStdOperatorTable.SET_SEMANTICS_TABLE.createCall( s.pos(), e, partitionList, orderList); } } } /** * Parses an INSERT statement. */ SqlNode SqlInsert() : { final List keywords = new ArrayList(); final SqlNodeList keywordList; final SqlIdentifier tableName; SqlNode tableRef; SqlNode source; final SqlNodeList columnList; final Span s; final Pair p; } { ( | { keywords.add(SqlInsertKeyword.UPSERT.symbol(getPos())); } ) { s = span(); } SqlInsertKeywords(keywords) { keywordList = new SqlNodeList(keywords, s.addAll(keywords).pos()); } tableName = CompoundTableIdentifier() ( tableRef = TableHints(tableName) | { tableRef = tableName; } ) [ LOOKAHEAD(5) tableRef = ExtendTable(tableRef) ] ( LOOKAHEAD(2) p = ParenthesizedCompoundIdentifierList() { if (p.right.size() > 0) { tableRef = extend(tableRef, p.right); } if (p.left.size() > 0) { columnList = p.left; } else { columnList = null; } } | { columnList = null; } ) source = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) { return new SqlInsert(s.end(source), keywordList, tableRef, source, columnList); } } /* * Abstract production: * * void SqlInsertKeywords(List keywords) * * Parses dialect-specific keywords immediately following the INSERT keyword. */ /** * Parses a DELETE statement. */ SqlNode SqlDelete() : { final SqlIdentifier tableName; SqlNode tableRef; final SqlIdentifier alias; final SqlNode where; final Span s; } { { s = span(); } tableName = CompoundTableIdentifier() ( tableRef = TableHints(tableName) | { tableRef = tableName; } ) [ tableRef = ExtendTable(tableRef) ] ( [ ] alias = SimpleIdentifier() | { alias = null; } ) ( where = Where() | { where = null; } ) { return new SqlDelete(s.add(tableRef).addIf(alias).addIf(where).pos(), tableRef, where, null, alias); } } /** * Parses an UPDATE statement. */ SqlNode SqlUpdate() : { final SqlIdentifier tableName; SqlNode tableRef; final SqlIdentifier alias; final SqlNode where; final SqlNodeList sourceExpressionList; final SqlNodeList targetColumnList; SqlIdentifier id; final Span s; } { { s = span(); targetColumnList = new SqlNodeList(s.pos()); sourceExpressionList = new SqlNodeList(s.pos()); } tableName = CompoundTableIdentifier() ( tableRef = TableHints(tableName) | { tableRef = tableName; } ) [ tableRef = ExtendTable(tableRef) ] ( [ ] alias = SimpleIdentifier() | { alias = null; } ) id = SimpleIdentifier() { targetColumnList.add(id); } // TODO: support DEFAULT also AddExpression(sourceExpressionList, ExprContext.ACCEPT_SUB_QUERY) ( id = SimpleIdentifier() { targetColumnList.add(id); } AddExpression(sourceExpressionList, ExprContext.ACCEPT_SUB_QUERY) )* ( where = Where() | { where = null; } ) { final SqlParserPos pos = s.addAll(targetColumnList) .addAll(sourceExpressionList).addIf(where).pos(); return new SqlUpdate(pos, tableRef, targetColumnList, sourceExpressionList, where, null, alias); } } /** * Parses a MERGE statement. */ SqlNode SqlMerge() : { final SqlIdentifier tableName; SqlNode tableRef; final SqlIdentifier alias; final SqlNode sourceTableRef; final SqlNode condition; final SqlUpdate updateCall; final SqlInsert insertCall; final Span s; } { { s = span(); } tableName = CompoundTableIdentifier() ( tableRef = TableHints(tableName) | { tableRef = tableName; } ) [ tableRef = ExtendTable(tableRef) ] ( [ ] alias = SimpleIdentifier() | { alias = null; } ) sourceTableRef = TableRef() condition = Expression(ExprContext.ACCEPT_SUB_QUERY) ( LOOKAHEAD(2) updateCall = WhenMatchedClause(tableRef, alias) ( insertCall = WhenNotMatchedClause(tableRef) | { insertCall = null; } ) | { updateCall = null; } insertCall = WhenNotMatchedClause(tableRef) ) { final SqlParserPos pos = s.addIf(updateCall).addIf(insertCall).pos(); return new SqlMerge(pos, tableRef, condition, sourceTableRef, updateCall, insertCall, null, alias); } } SqlUpdate WhenMatchedClause(SqlNode table, SqlIdentifier alias) : { SqlIdentifier id; final Span s; final SqlNodeList updateColumnList = new SqlNodeList(SqlParserPos.ZERO); final SqlNodeList updateExprList = new SqlNodeList(SqlParserPos.ZERO); } { { s = span(); } id = CompoundIdentifier() { updateColumnList.add(id); } AddExpression(updateExprList, ExprContext.ACCEPT_SUB_QUERY) ( id = CompoundIdentifier() { updateColumnList.add(id); } AddExpression(updateExprList, ExprContext.ACCEPT_SUB_QUERY) )* { return new SqlUpdate(s.addAll(updateExprList).pos(), table, updateColumnList, updateExprList, null, null, alias); } } SqlInsert WhenNotMatchedClause(SqlNode table) : { final Span insertSpan, valuesSpan; final List keywords = new ArrayList(); final SqlNodeList keywordList; final SqlNodeList insertColumnList; SqlNode rowConstructor; SqlNode insertValues; } { { insertSpan = span(); } SqlInsertKeywords(keywords) { keywordList = new SqlNodeList(keywords, insertSpan.end(this)); } ( LOOKAHEAD(2) insertColumnList = ParenthesizedSimpleIdentifierList() | { insertColumnList = null; } ) ( { valuesSpan = span(); } rowConstructor = RowConstructor() | { valuesSpan = span(); } rowConstructor = RowConstructor() ) { // TODO zfong 5/26/06: note that extra parentheses are accepted above // around the VALUES clause as a hack for unparse, but this is // actually invalid SQL; should fix unparse insertValues = SqlStdOperatorTable.VALUES.createCall( valuesSpan.end(this), rowConstructor); return new SqlInsert(insertSpan.end(this), keywordList, table, insertValues, insertColumnList); } } /** * Parses one item in a select list. */ void AddSelectItem(List list) : { final SqlNode e; final SqlIdentifier id; } { e = SelectExpression() ( [ ] ( id = SimpleIdentifier() | // Mute the warning about ambiguity between alias and continued // string literal. LOOKAHEAD(1) id = SimpleIdentifierFromStringLiteral() ) { list.add(SqlStdOperatorTable.AS.createCall(span().end(e), e, id)); } | { list.add(e); } ) } /** * Parses one unaliased expression in a select list. */ SqlNode SelectExpression() : { SqlNode e; } { { return SqlIdentifier.star(getPos()); } | e = Expression(ExprContext.ACCEPT_SUB_QUERY) { return e; } } SqlLiteral Natural() : { } { { return SqlLiteral.createBoolean(true, getPos()); } | { return SqlLiteral.createBoolean(false, getPos()); } } SqlLiteral JoinType() : { JoinType joinType; } { ( LOOKAHEAD(3) // required for "LEFT SEMI JOIN" in Babel { joinType = JoinType.INNER; } | { joinType = JoinType.INNER; } | [ ] { joinType = JoinType.LEFT; } | [ ] { joinType = JoinType.RIGHT; } | [ ] { joinType = JoinType.FULL; } | { joinType = JoinType.CROSS; } ) { return joinType.symbol(getPos()); } } /** * Parses the FROM clause for a SELECT. * *

FROM is mandatory in standard SQL, optional in dialects such as MySQL, * PostgreSQL. The parser allows SELECT without FROM, but the validator fails * if conformance is, say, STRICT_2003. */ SqlNode FromClause() : { SqlNode e, e2; SqlLiteral joinType; } { e = Join() ( // Comma joins should only occur at top-level in the FROM clause. // Valid: // * FROM a, b // * FROM (a CROSS JOIN b), c // Not valid: // * FROM a CROSS JOIN (b, c) LOOKAHEAD(1) { joinType = JoinType.COMMA.symbol(getPos()); } e2 = Join() { e = new SqlJoin(joinType.getParserPosition(), e, SqlLiteral.createBoolean(false, joinType.getParserPosition()), joinType, e2, JoinConditionType.NONE.symbol(SqlParserPos.ZERO), null); } )* { return e; } } SqlNode Join() : { SqlNode e; } { e = TableRef1(ExprContext.ACCEPT_QUERY_OR_JOIN) ( LOOKAHEAD(2) e = JoinTable(e) )* { return e; } } /** Matches "LEFT JOIN t ON ...", "RIGHT JOIN t USING ...", "JOIN t". */ SqlNode JoinTable(SqlNode e) : { SqlNode e2, condition; final SqlLiteral natural, joinType, on, using; SqlNodeList list; } { // LOOKAHEAD(3) is needed here rather than a LOOKAHEAD(2) because JavaCC // calculates minimum lookahead count incorrectly for choice that contains // zero size child. For instance, with the generated code, // "LOOKAHEAD(2, Natural(), JoinType())" // returns true immediately if it sees a single "" token. Where we // expect the lookahead succeeds after " ". // // For more information about the issue, // see https://github.com/javacc/javacc/issues/86 // // We allow CROSS JOIN (joinType = CROSS_JOIN) to have a join condition, // even though that is not valid SQL; the validator will catch it. LOOKAHEAD(3) natural = Natural() joinType = JoinType() e2 = TableRef1(ExprContext.ACCEPT_QUERY_OR_JOIN) ( { on = JoinConditionType.ON.symbol(getPos()); } condition = Expression(ExprContext.ACCEPT_SUB_QUERY) { return new SqlJoin(joinType.getParserPosition(), e, natural, joinType, e2, on, condition); } | { using = JoinConditionType.USING.symbol(getPos()); } list = ParenthesizedSimpleIdentifierList() { return new SqlJoin(joinType.getParserPosition(), e, natural, joinType, e2, using, new SqlNodeList(list, Span.of(using).end(this))); } | { return new SqlJoin(joinType.getParserPosition(), e, natural, joinType, e2, JoinConditionType.NONE.symbol(joinType.getParserPosition()), null); } ) | { joinType = JoinType.CROSS.symbol(getPos()); } e2 = TableRef2(true) { if (!this.conformance.isApplyAllowed()) { throw SqlUtil.newContextException(getPos(), RESOURCE.applyNotAllowed()); } return new SqlJoin(joinType.getParserPosition(), e, SqlLiteral.createBoolean(false, joinType.getParserPosition()), joinType, e2, JoinConditionType.NONE.symbol(SqlParserPos.ZERO), null); } | { joinType = JoinType.LEFT.symbol(getPos()); } e2 = TableRef2(true) { if (!this.conformance.isApplyAllowed()) { throw SqlUtil.newContextException(getPos(), RESOURCE.applyNotAllowed()); } return new SqlJoin(joinType.getParserPosition(), e, SqlLiteral.createBoolean(false, joinType.getParserPosition()), joinType, e2, JoinConditionType.ON.symbol(SqlParserPos.ZERO), SqlLiteral.createBoolean(true, joinType.getParserPosition())); } } /** * Parses a table reference in a FROM clause, not lateral unless LATERAL * is explicitly specified. */ SqlNode TableRef() : { final SqlNode e; } { e = TableRef3(ExprContext.ACCEPT_QUERY, false) { return e; } } SqlNode TableRef1(ExprContext exprContext) : { final SqlNode e; } { e = TableRef3(exprContext, false) { return e; } } /** * Parses a table reference in a FROM clause. */ SqlNode TableRef2(boolean lateral) : { final SqlNode e; } { e = TableRef3(ExprContext.ACCEPT_QUERY, lateral) { return e; } } SqlNode TableRef3(ExprContext exprContext, boolean lateral) : { final SqlIdentifier tableName; SqlNode tableRef; final SqlIdentifier alias; final Span s; SqlNodeList args; final SqlNodeList columnAliasList; SqlUnnestOperator unnestOp = SqlStdOperatorTable.UNNEST; } { ( LOOKAHEAD(2) tableName = CompoundTableIdentifier() ( tableRef = TableHints(tableName) | { tableRef = tableName; } ) [ tableRef = ExtendTable(tableRef) ] tableRef = Over(tableRef) [ tableRef = Snapshot(tableRef) ] [ tableRef = MatchRecognize(tableRef) ] | LOOKAHEAD(2) [ { lateral = true; } ] tableRef = ParenthesizedExpression(exprContext) tableRef = Over(tableRef) tableRef = addLateral(tableRef, lateral) [ tableRef = MatchRecognize(tableRef) ] | { s = span(); } args = ParenthesizedQueryOrCommaList(ExprContext.ACCEPT_SUB_QUERY) [ { unnestOp = SqlStdOperatorTable.UNNEST_WITH_ORDINALITY; } ] { tableRef = unnestOp.createCall(s.end(this), (List) args); } | [ { lateral = true; } ] tableRef = TableFunctionCall() tableRef = addLateral(tableRef, lateral) | tableRef = ExtendedTableRef() ) [ LOOKAHEAD(2) tableRef = Pivot(tableRef) ] [ LOOKAHEAD(2) tableRef = Unpivot(tableRef) ] [ [ ] alias = SimpleIdentifier() ( columnAliasList = ParenthesizedSimpleIdentifierList() | { columnAliasList = null; } ) { // Standard SQL (and Postgres) allow applying "AS alias" to a JOIN, // e.g. "FROM (a CROSS JOIN b) AS c". The new alias obscures the // internal aliases, and columns cannot be referenced if they are // not unique. TODO: Support this behavior; see // [CALCITE-5168] Allow AS after parenthesized JOIN checkNotJoin(tableRef); if (columnAliasList == null) { tableRef = SqlStdOperatorTable.AS.createCall( Span.of(tableRef).end(this), tableRef, alias); } else { List idList = new ArrayList(); idList.add(tableRef); idList.add(alias); idList.addAll(columnAliasList.getList()); tableRef = SqlStdOperatorTable.AS.createCall( Span.of(tableRef).end(this), idList); } } ] [ tableRef = Tablesample(tableRef) ] { return tableRef; } } SqlNode Tablesample(SqlNode tableRef) : { final Span s; final SqlNode sample; final boolean isBernoulli; final SqlNumericLiteral samplePercentage; boolean isRepeatable = false; int repeatableSeed = 0; } { { s = span(); checkNotJoin(tableRef); } ( sample = StringLiteral() { String sampleName = SqlLiteral.unchain(sample).getValueAs(String.class); SqlSampleSpec sampleSpec = SqlSampleSpec.createNamed(sampleName); final SqlLiteral sampleLiteral = SqlLiteral.createSample(sampleSpec, s.end(this)); tableRef = SqlStdOperatorTable.TABLESAMPLE.createCall( s.add(tableRef).end(this), tableRef, sampleLiteral); } | ( { isBernoulli = true; } | { isBernoulli = false; } ) samplePercentage = UnsignedNumericLiteral() [ repeatableSeed = IntLiteral() { isRepeatable = true; } ] { final BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100L); BigDecimal rate = samplePercentage.bigDecimalValue(); if (rate.compareTo(BigDecimal.ZERO) < 0 || rate.compareTo(ONE_HUNDRED) > 0) { throw SqlUtil.newContextException(getPos(), RESOURCE.invalidSampleSize()); } // Treat TABLESAMPLE(0) and TABLESAMPLE(100) as no table // sampling at all. Not strictly correct: TABLESAMPLE(0) // should produce no output, but it simplifies implementation // to know that some amount of sampling will occur. // In practice values less than ~1E-43% are treated as 0.0 and // values greater than ~99.999997% are treated as 1.0 float fRate = rate.divide(ONE_HUNDRED).floatValue(); if (fRate > 0.0f && fRate < 1.0f) { SqlSampleSpec tableSampleSpec = isRepeatable ? SqlSampleSpec.createTableSample( isBernoulli, fRate, repeatableSeed) : SqlSampleSpec.createTableSample(isBernoulli, fRate); SqlLiteral tableSampleLiteral = SqlLiteral.createSample(tableSampleSpec, s.end(this)); tableRef = SqlStdOperatorTable.TABLESAMPLE.createCall( s.end(this), tableRef, tableSampleLiteral); } } ) { return tableRef; } } /** Wraps a table reference in a call to EXTEND if an optional "EXTEND" clause * is present. */ SqlNode ExtendTable(SqlNode tableRef) : { final SqlNodeList extendList; } { [ ] extendList = ExtendList() { return extend(tableRef, extendList); } } SqlNodeList ExtendList() : { final Span s; List list = new ArrayList(); } { { s = span(); } AddColumnType(list) ( AddColumnType(list) )* { return new SqlNodeList(list, s.end(this)); } } void AddColumnType(List list) : { final SqlIdentifier name; final SqlDataTypeSpec type; final boolean nullable; } { name = CompoundIdentifier() type = DataType() nullable = NotNullOpt() { list.add(name); list.add(type.withNullable(nullable, getPos())); } } /** * Parses a compound identifier with optional type. */ void AddCompoundIdentifierType(List list, List extendList) : { final SqlIdentifier name; final SqlDataTypeSpec type; final boolean nullable; } { name = CompoundIdentifier() ( type = DataType() nullable = NotNullOpt() | { type = null; nullable = true; } ) { if (type != null) { if (!this.conformance.allowExtend()) { throw SqlUtil.newContextException(type.getParserPosition(), RESOURCE.extendNotAllowed()); } extendList.add(name); extendList.add(type.withNullable(nullable, getPos())); } list.add(name); } } SqlNode TableFunctionCall() : { final Span s; final SqlNode call; SqlFunctionCategory funcType = SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION; } {

{ s = span(); } [ { funcType = SqlFunctionCategory.USER_DEFINED_TABLE_SPECIFIC_FUNCTION; } ] call = NamedRoutineCall(funcType, ExprContext.ACCEPT_CURSOR) { return SqlStdOperatorTable.COLLECTION_TABLE.createCall(s.end(this), call); } } /** * Abstract production: * SqlNode ExtendedTableRef() * *

Allows parser to be extended with new types of table references. The * default implementation of this production is empty. */ /* * Abstract production: * * SqlNode TableOverOpt() * * Allows an OVER clause following a table expression as an extension to * standard SQL syntax. The default implementation of this production is empty. */ /** * Parses an explicit TABLE t reference. */ SqlNode ExplicitTable(SqlParserPos pos) : { SqlNode tableRef; } {

tableRef = CompoundIdentifier() { return SqlStdOperatorTable.EXPLICIT_TABLE.createCall(pos, tableRef); } } /** * Parses a VALUES leaf query expression. */ SqlNode TableConstructor() : { final List list = new ArrayList(); final Span s; } { ( { s = span(); } | { s = span(); if (!this.conformance.isValueAllowed()) { throw SqlUtil.newContextException(getPos(), RESOURCE.valueNotAllowed()); } } ) AddRowConstructor(list) ( LOOKAHEAD(2) AddRowConstructor(list) )* { return SqlStdOperatorTable.VALUES.createCall(s.end(this), list); } } /** Parses a row constructor and adds it to a list. */ void AddRowConstructor(List list) : { SqlNode e; } { e = RowConstructor() { list.add(e); } } /** * Parses a row constructor in the context of a VALUES expression. */ SqlNode RowConstructor() : { final SqlNodeList valueList; final SqlNode value; final Span s; } { // hints are necessary here due to common LPAREN prefixes ( // TODO jvs 8-Feb-2004: extra parentheses are accepted here as a hack // for unparse, but this is actually invalid SQL; should // fix unparse LOOKAHEAD(3) { s = span(); } valueList = ParenthesizedQueryOrCommaListWithDefault(ExprContext.ACCEPT_NONCURSOR) { s.add(this); } | LOOKAHEAD(3) ( { s = span(); } | { s = Span.of(); } ) valueList = ParenthesizedQueryOrCommaListWithDefault(ExprContext.ACCEPT_NONCURSOR) | value = Expression(ExprContext.ACCEPT_NONCURSOR) { // NOTE: A bare value here is standard SQL syntax, believe it or // not. Taken together with multi-row table constructors, it leads // to very easy mistakes if you forget the parentheses on a // single-row constructor. This is also the reason for the // LOOKAHEAD in TableConstructor(). It would be so much more // reasonable to require parentheses. Sigh. s = Span.of(value); valueList = new SqlNodeList(ImmutableList.of(value), value.getParserPosition()); } ) { // REVIEW jvs 8-Feb-2004: Should we discriminate between scalar // sub-queries inside of ROW and row sub-queries? The standard does, // but the distinction seems to be purely syntactic. return SqlStdOperatorTable.ROW.createCall(s.end(valueList), (List) valueList); } } /** Parses a WHERE clause for SELECT, DELETE, and UPDATE. */ SqlNode Where() : { SqlNode condition; } { condition = Expression(ExprContext.ACCEPT_SUB_QUERY) { return condition; } } /** Parses a GROUP BY clause for SELECT. */ SqlNodeList GroupBy() : { final List list; final boolean distinct; final Span s; } { { s = span(); } ( { distinct = true; } | { distinct = false; } | { distinct = false; } ) list = GroupingElementList() { final SqlParserPos pos = s.end(this); final List list2 = distinct ? ImmutableList.of( SqlInternalOperators.GROUP_BY_DISTINCT.createCall(pos, list)) : list; return new SqlNodeList(list2, pos); } } List GroupingElementList() : { final List list = new ArrayList(); } { AddGroupingElement(list) ( LOOKAHEAD(2) AddGroupingElement(list) )* { return list; } } void AddGroupingElement(List list) : { final List subList; final SqlNodeList nodes; final Span s; } { LOOKAHEAD(2) { s = span(); } subList = GroupingElementList() { list.add( SqlStdOperatorTable.GROUPING_SETS.createCall(s.end(this), subList)); } | { s = span(); } nodes = ExpressionCommaList(s, ExprContext.ACCEPT_SUB_QUERY) { list.add( SqlStdOperatorTable.ROLLUP.createCall(s.end(this), nodes.getList())); } | { s = span(); } nodes = ExpressionCommaList(s, ExprContext.ACCEPT_SUB_QUERY) { list.add( SqlStdOperatorTable.CUBE.createCall(s.end(this), nodes.getList())); } | LOOKAHEAD(3) { s = span(); } { list.add(new SqlNodeList(s.end(this))); } | AddExpression(list, ExprContext.ACCEPT_SUB_QUERY) } /** * Parses a list of expressions separated by commas. */ SqlNodeList ExpressionCommaList( final Span s, ExprContext exprContext) : { final List list = new ArrayList(); } { AddExpressions(list, exprContext) { return new SqlNodeList(list, s.addAll(list).pos()); } } /** * Parses a list of expressions separated by commas, * appending expressions to a given list. */ void AddExpressions(List list, ExprContext exprContext) : { } { AddExpression(list, exprContext) ( // NOTE jvs 6-Feb-2004: See comments at top of file for why // hint is necessary here. LOOKAHEAD(2) AddExpression(list, ExprContext.ACCEPT_SUB_QUERY) )* } /** Parses a HAVING clause for SELECT. */ SqlNode Having() : { SqlNode e; } { e = Expression(ExprContext.ACCEPT_SUB_QUERY) { return e; } } /** Parses a WINDOW clause for SELECT. */ SqlNodeList Window() : { final List list = new ArrayList(); final Span s; } { { s = span(); } AddWindowSpec(list) ( LOOKAHEAD(2) AddWindowSpec(list) )* { return new SqlNodeList(list, s.addAll(list).pos()); } } void AddWindowSpec(List list) : { final SqlIdentifier id; final SqlWindow e; } { id = SimpleIdentifier() e = WindowSpecification() { e.setDeclName(id); list.add(e); } } /** * Parses a window specification. */ SqlWindow WindowSpecification() : { final SqlIdentifier id; final SqlNodeList partitionList; final SqlNodeList orderList; final SqlLiteral isRows; final SqlNode lowerBound, upperBound; final Span s, s1, s2; final SqlLiteral allowPartial; } { { s = span(); } ( id = SimpleIdentifier() | { id = null; } ) ( { s1 = span(); } partitionList = ExpressionCommaList(s1, ExprContext.ACCEPT_NON_QUERY) | { partitionList = SqlNodeList.EMPTY; } ) ( orderList = OrderBy(true) | { orderList = SqlNodeList.EMPTY; } ) ( ( { isRows = SqlLiteral.createBoolean(true, getPos()); } | { isRows = SqlLiteral.createBoolean(false, getPos()); } ) ( lowerBound = WindowRange() upperBound = WindowRange() | lowerBound = WindowRange() { upperBound = null; } ) | { isRows = SqlLiteral.createBoolean(false, SqlParserPos.ZERO); lowerBound = upperBound = null; } ) ( { s2 = span(); } { allowPartial = SqlLiteral.createBoolean(true, s2.end(this)); } | { s2 = span(); } { allowPartial = SqlLiteral.createBoolean(false, s2.end(this)); } | { allowPartial = null; } ) { return SqlWindow.create(null, id, partitionList, orderList, isRows, lowerBound, upperBound, allowPartial, s.end(this)); } } SqlNode WindowRange() : { final SqlNode e; final Span s; } { LOOKAHEAD(2) { s = span(); } { return SqlWindow.createCurrentRow(s.end(this)); } | LOOKAHEAD(2) { s = span(); } ( { return SqlWindow.createUnboundedPreceding(s.end(this)); } | { return SqlWindow.createUnboundedFollowing(s.end(this)); } ) | e = Expression(ExprContext.ACCEPT_NON_QUERY) ( { return SqlWindow.createPreceding(e, getPos()); } | { return SqlWindow.createFollowing(e, getPos()); } ) } /** Parses a QUALIFY clause for SELECT. */ SqlNode Qualify() : { SqlNode e; } { e = Expression(ExprContext.ACCEPT_SUB_QUERY) { return e; } } /** * Parses an ORDER BY clause. */ SqlNodeList OrderBy(boolean accept) : { final List list = new ArrayList(); final Span s; } { { s = span(); if (!accept) { // Someone told us ORDER BY wasn't allowed here. So why // did they bother calling us? To get the correct // parser position for error reporting. throw SqlUtil.newContextException(s.pos(), RESOURCE.illegalOrderBy()); } } AddOrderItem(list) ( // NOTE jvs 6-Feb-2004: See comments at top of file for why // hint is necessary here. LOOKAHEAD(2) AddOrderItem(list) )* { return new SqlNodeList(list, s.addAll(list).pos()); } } /** * Parses one item in an ORDER BY clause, and adds it to a list. */ void AddOrderItem(List list) : { SqlNode e; } { e = Expression(ExprContext.ACCEPT_SUB_QUERY) ( | { e = SqlStdOperatorTable.DESC.createCall(getPos(), e); } )? ( LOOKAHEAD(2) { e = SqlStdOperatorTable.NULLS_FIRST.createCall(getPos(), e); } | { e = SqlStdOperatorTable.NULLS_LAST.createCall(getPos(), e); } )? { list.add(e); } } /** Wraps a table reference in a call to OVER if an optional "OVER" clause * is present (if the dialect supports OVER for table expressions). */ SqlNode Over(SqlNode tableRef) : { final SqlNode over; } { over = TableOverOpt() { if (over != null) { return SqlStdOperatorTable.OVER.createCall( getPos(), checkNotJoin(tableRef), over); } else { return tableRef; } } } /** Wraps a table reference in a call to LATERAL if {@code lateral} is true. */ JAVACODE SqlNode addLateral(SqlNode tableRef, boolean lateral) { return lateral ? SqlStdOperatorTable.LATERAL.createCall(getPos(), checkNotJoin(tableRef)) : tableRef; } /** * Parses a FOR SYSTEM_TIME clause following a table expression. */ SqlSnapshot Snapshot(SqlNode tableRef) : { final Span s; final SqlNode e; } { { s = span(); } // Syntax for temporal table in // standard SQL 2011 IWD 9075-2:201?(E) 7.6
// supports grammar as following: // 1. datetime literal // 2. datetime value function, i.e. CURRENT_TIMESTAMP // 3. datetime term in 1 or 2 +(or -) interval term // We extend to support column reference, use Expression // to simplify the parsing code. e = Expression(ExprContext.ACCEPT_NON_QUERY) { return new SqlSnapshot(s.end(this), tableRef, e); } } /** Parses a PIVOT clause following a table expression. */ SqlNode Pivot(SqlNode tableRef) : { final Span s; final Span s2; final List aggList = new ArrayList(); final List valueList = new ArrayList(); final SqlNodeList axisList; final SqlNodeList inList; } { { s = span(); checkNotJoin(tableRef); } AddPivotAgg(aggList) ( AddPivotAgg(aggList) )* axisList = SimpleIdentifierOrList() { s2 = span(); } [ AddPivotValue(valueList) ( AddPivotValue(valueList) )* ] { inList = new SqlNodeList(valueList, s2.end(this)); } { return new SqlPivot(s.end(this), tableRef, new SqlNodeList(aggList, SqlParserPos.sum(aggList)), axisList, inList); } } void AddPivotAgg(List list) : { final SqlNode e; final SqlIdentifier alias; } { e = NamedFunctionCall() ( // Because babel put FOR into non-reserved keyword set. LOOKAHEAD({getToken(1).kind != COMMA && getToken(1).kind != FOR}) [ ] alias = SimpleIdentifier() { list.add( SqlStdOperatorTable.AS.createCall(Span.of(e).end(this), e, alias)); } | { list.add(e); } ) } void AddPivotValue(List list) : { final SqlNode e; final SqlNodeList tuple; final SqlIdentifier alias; } { e = RowConstructor() { tuple = SqlParserUtil.stripRow(e); } ( [ ] alias = SimpleIdentifier() { list.add( SqlStdOperatorTable.AS.createCall(Span.of(tuple).end(this), tuple, alias)); } | { list.add(tuple); } ) } /** Parses an UNPIVOT clause following a table expression. */ SqlNode Unpivot(SqlNode tableRef) : { final Span s; final boolean includeNulls; final SqlNodeList measureList; final SqlNodeList axisList; final Span s2; final List values = new ArrayList(); final SqlNodeList inList; } { { s = span(); checkNotJoin(tableRef); } ( { includeNulls = true; } | { includeNulls = false; } | { includeNulls = false; } ) measureList = SimpleIdentifierOrList() axisList = SimpleIdentifierOrList() { s2 = span(); } AddUnpivotValue(values) ( AddUnpivotValue(values) )* { inList = new SqlNodeList(values, s2.end(this)); } { return new SqlUnpivot(s.end(this), tableRef, includeNulls, measureList, axisList, inList); } } void AddUnpivotValue(List list) : { final SqlNodeList columnList; final SqlNode values; } { columnList = SimpleIdentifierOrList() ( values = RowConstructor() { final SqlNodeList valueList = SqlParserUtil.stripRow(values); list.add( SqlStdOperatorTable.AS.createCall(Span.of(columnList).end(this), columnList, valueList)); } | { list.add(columnList); } ) } /** * Parses a MATCH_RECOGNIZE clause following a table expression. */ SqlMatchRecognize MatchRecognize(SqlNode tableRef) : { final Span s, s0, s1, s2; final SqlNodeList measureList; final SqlNodeList partitionList; final SqlNodeList orderList; final SqlNode pattern; final SqlLiteral interval; final SqlNodeList patternDefList; final SqlNode after; final SqlNode var; final SqlLiteral rowsPerMatch; final SqlNodeList subsetList; final SqlLiteral isStrictStarts; final SqlLiteral isStrictEnds; } { { s = span(); checkNotJoin(tableRef); } ( { s2 = span(); } partitionList = ExpressionCommaList(s2, ExprContext.ACCEPT_NON_QUERY) | { partitionList = SqlNodeList.EMPTY; } ) ( orderList = OrderBy(true) | { orderList = SqlNodeList.EMPTY; } ) ( measureList = MeasureColumnCommaList(span()) | { measureList = SqlNodeList.EMPTY; } ) ( { s0 = span(); } { rowsPerMatch = SqlMatchRecognize.RowsPerMatchOption.ONE_ROW.symbol(s0.end(this)); } | { s0 = span(); } { rowsPerMatch = SqlMatchRecognize.RowsPerMatchOption.ALL_ROWS.symbol(s0.end(this)); } | { rowsPerMatch = null; } ) ( { s1 = span(); } ( ( LOOKAHEAD(2) { after = SqlMatchRecognize.AfterOption.SKIP_TO_NEXT_ROW .symbol(s1.end(this)); } | LOOKAHEAD(2) var = SimpleIdentifier() { after = SqlMatchRecognize.SKIP_TO_FIRST.createCall( s1.end(var), var); } | // This "LOOKAHEAD({true})" is a workaround for Babel. // Because of babel parser uses option "LOOKAHEAD=2" globally, // JavaCC generates something like "LOOKAHEAD(2, [] SimpleIdentifier())" // here. But the correct LOOKAHEAD should be // "LOOKAHEAD(2, [ LOOKAHEAD(2, SimpleIdentifier()) ] // SimpleIdentifier())" which have the syntactic lookahead for considered. // // Overall LOOKAHEAD({true}) is even better as this is the last branch in the // choice. LOOKAHEAD({true}) [ LOOKAHEAD(2, SimpleIdentifier()) ] var = SimpleIdentifier() { after = SqlMatchRecognize.SKIP_TO_LAST.createCall( s1.end(var), var); } ) | { after = SqlMatchRecognize.AfterOption.SKIP_PAST_LAST_ROW .symbol(s1.end(this)); } ) | { after = null; } ) ( { isStrictStarts = SqlLiteral.createBoolean(true, getPos()); } | { isStrictStarts = SqlLiteral.createBoolean(false, getPos()); } ) pattern = PatternExpression() ( { isStrictEnds = SqlLiteral.createBoolean(true, getPos()); } | { isStrictEnds = SqlLiteral.createBoolean(false, getPos()); } ) ( interval = IntervalLiteral() | { interval = null; } ) ( subsetList = SubsetDefinitionCommaList(span()) | { subsetList = SqlNodeList.EMPTY; } ) patternDefList = PatternDefinitionCommaList(span()) { return new SqlMatchRecognize(s.end(this), tableRef, pattern, isStrictStarts, isStrictEnds, patternDefList, measureList, after, subsetList, rowsPerMatch, partitionList, orderList, interval); } } SqlNodeList MeasureColumnCommaList(Span s) : { final List list = new ArrayList(); } { AddMeasureColumn(list) ( AddMeasureColumn(list) )* { return new SqlNodeList(list, s.addAll(list).pos()); } } void AddMeasureColumn(List list) : { final SqlNode e; final SqlIdentifier alias; } { e = Expression(ExprContext.ACCEPT_NON_QUERY) alias = SimpleIdentifier() { list.add(SqlStdOperatorTable.AS.createCall(Span.of(e).end(this), e, alias)); } } SqlNode PatternExpression() : { SqlNode left; SqlNode right; } { left = PatternTerm() ( right = PatternTerm() { left = SqlStdOperatorTable.PATTERN_ALTER.createCall( Span.of(left).end(right), left, right); } )* { return left; } } SqlNode PatternTerm() : { SqlNode left; SqlNode right; } { left = PatternFactor() ( right = PatternFactor() { left = SqlStdOperatorTable.PATTERN_CONCAT.createCall( Span.of(left).end(right), left, right); } )* { return left; } } SqlNode PatternFactor() : { final SqlNode e; final SqlNode extra; final SqlLiteral startNum; final SqlLiteral endNum; final SqlLiteral reluctant; } { e = PatternPrimary() ( LOOKAHEAD(1) ( { startNum = LITERAL_ZERO; endNum = LITERAL_MINUS_ONE; } | { startNum = LITERAL_ONE; endNum = LITERAL_MINUS_ONE; } | { startNum = LITERAL_ZERO; endNum = LITERAL_ONE; } | ( startNum = UnsignedNumericLiteral() ( ( endNum = UnsignedNumericLiteral() | { endNum = LITERAL_MINUS_ONE; } ) | { endNum = startNum; } ) | endNum = UnsignedNumericLiteral() { startNum = LITERAL_MINUS_ONE; } | extra = PatternExpression() { return SqlStdOperatorTable.PATTERN_CONCAT.createCall( Span.of(e).end(this), e, SqlStdOperatorTable.PATTERN_EXCLUDE.createCall( Span.of(extra).end(this), extra)); } ) ) ( { reluctant = SqlLiteral.createBoolean( startNum.intValue(true) != endNum.intValue(true), SqlParserPos.ZERO); } | { reluctant = SqlLiteral.createBoolean(false, SqlParserPos.ZERO); } ) | { return e; } ) { return SqlStdOperatorTable.PATTERN_QUANTIFIER.createCall( span().end(e), e, startNum, endNum, reluctant); } } SqlNode PatternPrimary() : { final Span s; SqlNode e; final List list; } { e = SimpleIdentifier() { return e; } | e = PatternExpression() { return e; } | { s = span(); } e = PatternExpression() { return SqlStdOperatorTable.PATTERN_EXCLUDE.createCall(s.end(this), e); } | ( { s = span(); list = new ArrayList(); } e = PatternExpression() { list.add(e); } ( e = PatternExpression() { list.add(e); } )* { return SqlStdOperatorTable.PATTERN_PERMUTE.createCall( s.end(this), list); } ) } SqlNodeList SubsetDefinitionCommaList(Span s) : { final List list = new ArrayList(); } { AddSubsetDefinition(list) ( AddSubsetDefinition(list) )* { return new SqlNodeList(list, s.addAll(list).pos()); } } void AddSubsetDefinition(List list) : { final SqlNode var; final SqlNodeList varList; } { var = SimpleIdentifier() varList = ExpressionCommaList(span(), ExprContext.ACCEPT_NON_QUERY) { list.add( SqlStdOperatorTable.EQUALS.createCall(span().end(var), var, varList)); } } SqlNodeList PatternDefinitionCommaList(Span s) : { SqlNode e; final List eList = new ArrayList(); } { e = PatternDefinition() { eList.add(e); } ( e = PatternDefinition() { eList.add(e); } )* { return new SqlNodeList(eList, s.addAll(eList).pos()); } } SqlNode PatternDefinition() : { final SqlNode var; final SqlNode e; } { var = SimpleIdentifier() e = Expression(ExprContext.ACCEPT_SUB_QUERY) { return SqlStdOperatorTable.AS.createCall(Span.of(var, e).pos(), e, var); } } // ---------------------------------------------------------------------------- // Expressions /** * Parses a SQL expression (such as might occur in a WHERE clause) followed by * the end-of-file symbol. */ SqlNode SqlExpressionEof() : { SqlNode e; } { e = Expression(ExprContext.ACCEPT_SUB_QUERY) () { return e; } } /** * Parses either a row expression or a query expression without ORDER BY. * *

Examples of valid queries: *

    *
  • {@code SELECT c FROM t} *
  • {@code SELECT c} (valid in some dialects) *
  • {@code SELECT c FROM t UNION SELECT c2 FROM t2} *
  • {@code WITH q AS (SELECT 1) SELECT * FROM q} *
  • {@code VALUES (1, 2)} *
  • {@code TABLE t} *
* *

Non-examples: *

    *
  • {@code emp CROSS JOIN dept} *
  • {@code SELECT c FROM t ORDER BY c} *
  • {@code (SELECT c FROM t)} *
*/ SqlNode QueryOrExpr(ExprContext exprContext) : { SqlNodeList withList = null; final SqlNode e; final List list = new ArrayList(); } { [ withList = WithList() ] e = LeafQueryOrExpr(exprContext) { list.add(e); } ( AddSetOpQuery(list, exprContext) )* { return addWith(withList, SqlParserUtil.toTree(list)); } } SqlNode Query(ExprContext exprContext) : { SqlNodeList withList = null; final SqlNode e; final List list = new ArrayList(); } { [ withList = WithList() ] e = LeafQuery(exprContext) { list.add(e); } ( AddSetOpQuery(list, exprContext) )* { return addWith(withList, SqlParserUtil.toTree(list)); } } JAVACODE SqlNode addWith(SqlNodeList withList, SqlNode e) { return withList == null ? e : new SqlWith(withList.getParserPosition(), withList, e); } /** Parses a set operator (e.g. UNION or INTERSECT) * followed by a query or expression, * and adds both to {@code list}. */ void AddSetOpQueryOrExpr(List list, ExprContext exprContext) : { final SqlOperator op; final SqlParserPos pos; final SqlNode e; } { { if (list.size() == 1 && !((SqlNode) list.get(0)).isA(SqlKind.QUERY)) { // whoops, expression we just parsed wasn't a query, // but we're about to see something like UNION, so // force an exception retroactively checkNonQueryExpression(ExprContext.ACCEPT_QUERY); } } op = BinaryQueryOperator() { // ensure a query is legal in this context pos = getPos(); checkQueryExpression(exprContext); } e = LeafQueryOrExpr(ExprContext.ACCEPT_QUERY) { list.add(new SqlParserUtil.ToTreeListItem(op, pos)); list.add(e); } } /** Parses a set operator (e.g. UNION or INTERSECT) * followed by a query, * and adds both to {@code list}. */ void AddSetOpQuery(List list, ExprContext exprContext) : { final SqlOperator op; final SqlParserPos pos; final SqlNode e; } { { if (list.size() == 1 && !((SqlNode) list.get(0)).isA(SqlKind.QUERY)) { // whoops, expression we just parsed wasn't a query, // but we're about to see something like UNION, so // force an exception retroactively checkNonQueryExpression(ExprContext.ACCEPT_QUERY); } } op = BinaryQueryOperator() { // ensure a query is legal in this context pos = getPos(); checkQueryExpression(exprContext); } e = LeafQueryOrExpr(ExprContext.ACCEPT_QUERY) { list.add(new SqlParserUtil.ToTreeListItem(op, pos)); list.add(e); } } SqlNodeList WithList() : { final Span s; final List list = new ArrayList(); } { { s = span(); } AddWithItem(list) ( AddWithItem(list) )* { return new SqlNodeList(list, s.end(this)); } } void AddWithItem(List list) : { final SqlIdentifier id; final SqlNodeList columnList; final SqlNode definition; } { id = SimpleIdentifier() ( columnList = ParenthesizedSimpleIdentifierList() | { columnList = null; } ) definition = ParenthesizedExpression(ExprContext.ACCEPT_QUERY) { list.add(new SqlWithItem(id.getParserPosition(), id, columnList, definition)); } } /** * Parses either a row expression, a leaf query expression, or * a parenthesized expression of any kind. */ SqlNode LeafQueryOrExpr(ExprContext exprContext) : { SqlNode e; } { e = LeafQuery(exprContext) { return e; } | e = Expression(exprContext) { return e; } } /** As {@link #Expression} but appends to a list. */ void AddExpression(List list, ExprContext exprContext) : { final SqlNode e; } { e = Expression(exprContext) { list.add(e); } } /** * Parses a row expression or a parenthesized expression of any kind. */ SqlNode Expression(ExprContext exprContext) : { final List list; } { list = Expression2(exprContext) { return SqlParserUtil.toTree(list); } } void AddExpression2b(List list, ExprContext exprContext) : { SqlNode e; SqlOperator op; SqlNode ext; } { ( LOOKAHEAD(1) op = PrefixRowOperator() { checkNonQueryExpression(exprContext); list.add(new SqlParserUtil.ToTreeListItem(op, getPos())); } )* e = Expression3(exprContext) { list.add(e); } ( LOOKAHEAD(2) ext = RowExpressionExtension() { list.add( new SqlParserUtil.ToTreeListItem( SqlStdOperatorTable.DOT, getPos())); list.add(ext); } )* } /** * Parses a binary row expression, or a parenthesized expression of any * kind. * *

The result is as a flat list of operators and operands. The top-level * call to get an expression should call {@link #Expression}, but lower-level * calls should call this, to give the parser the opportunity to associate * operator calls. * *

For example 'a = b like c = d' should come out '((a = b) like c) = d' * because LIKE and '=' have the same precedence, but tends to come out as '(a * = b) like (c = d)' because (a = b) and (c = d) are parsed as separate * expressions. */ List Expression2(ExprContext exprContext) : { final List list = new ArrayList(); List list2; final List list3 = new ArrayList(); SqlNodeList nodeList; SqlNode e; SqlOperator op; SqlIdentifier p; final Span s = span(); } { AddExpression2b(list, exprContext) ( LOOKAHEAD(2) ( LOOKAHEAD(2) ( // Special case for "IN", because RHS of "IN" is the only place // that an expression-list is allowed ("exp IN (exp1, exp2)"). LOOKAHEAD(2) { checkNonQueryExpression(exprContext); } ( { op = SqlStdOperatorTable.NOT_IN; } | { op = SqlStdOperatorTable.IN; } | { final SqlKind k; } k = comp() ( { op = SqlStdOperatorTable.some(k); } | { op = SqlStdOperatorTable.some(k); } | { op = SqlStdOperatorTable.all(k); } ) ) { s.clear().add(this); } nodeList = ParenthesizedQueryOrCommaList(ExprContext.ACCEPT_NONCURSOR) { list.add(new SqlParserUtil.ToTreeListItem(op, s.pos())); s.add(nodeList); // special case for stuff like IN (s1 UNION s2) if (nodeList.size() == 1) { SqlNode item = nodeList.get(0); if (item.isA(SqlKind.QUERY)) { list.add(item); } else { list.add(nodeList); } } else { list.add(nodeList); } } | LOOKAHEAD(2) { checkNonQueryExpression(exprContext); } ( { op = SqlStdOperatorTable.NOT_BETWEEN; s.clear().add(this); } [ { op = SqlStdOperatorTable.SYMMETRIC_NOT_BETWEEN; } | ] | { op = SqlStdOperatorTable.BETWEEN; s.clear().add(this); } [ { op = SqlStdOperatorTable.SYMMETRIC_BETWEEN; } | ] ) AddExpression2b(list3, ExprContext.ACCEPT_SUB_QUERY) { list.add(new SqlParserUtil.ToTreeListItem(op, s.pos())); list.addAll(list3); list3.clear(); } | LOOKAHEAD(2) { checkNonQueryExpression(exprContext); s.clear().add(this); } ( ( ( { op = SqlStdOperatorTable.NOT_LIKE; } | { op = SqlLibraryOperators.NOT_ILIKE; } | { op = SqlLibraryOperators.NOT_RLIKE; } | { op = SqlStdOperatorTable.NOT_SIMILAR_TO; } ) | { op = SqlStdOperatorTable.LIKE; } | { op = SqlLibraryOperators.ILIKE; } | { op = SqlLibraryOperators.RLIKE; } | { op = SqlStdOperatorTable.SIMILAR_TO; } ) ) list2 = Expression2(ExprContext.ACCEPT_SUB_QUERY) { list.add(new SqlParserUtil.ToTreeListItem(op, s.pos())); list.addAll(list2); } [ LOOKAHEAD(2) e = Expression3(ExprContext.ACCEPT_SUB_QUERY) { s.clear().add(this); list.add( new SqlParserUtil.ToTreeListItem( SqlStdOperatorTable.ESCAPE, s.pos())); list.add(e); } ] | LOOKAHEAD(3) op = BinaryRowOperator() { checkNonQueryExpression(exprContext); list.add(new SqlParserUtil.ToTreeListItem(op, getPos())); } AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY) | e = Expression(ExprContext.ACCEPT_SUB_QUERY) { list.add( new SqlParserUtil.ToTreeListItem( SqlStdOperatorTable.ITEM, getPos())); list.add(e); } ( LOOKAHEAD(2) p = SimpleIdentifier() { list.add( new SqlParserUtil.ToTreeListItem( SqlStdOperatorTable.DOT, getPos())); list.add(p); } )* | { checkNonQueryExpression(exprContext); } op = PostfixRowOperator() { list.add(new SqlParserUtil.ToTreeListItem(op, getPos())); } ) )+ { return list; } | { return list; } ) } /** Parses a comparison operator inside a SOME / ALL predicate. */ SqlKind comp() : { } { { return SqlKind.LESS_THAN; } | { return SqlKind.LESS_THAN_OR_EQUAL; } | { return SqlKind.GREATER_THAN; } | { return SqlKind.GREATER_THAN_OR_EQUAL; } | { return SqlKind.EQUALS; } | { return SqlKind.NOT_EQUALS; } | { if (!this.conformance.isBangEqualAllowed()) { throw SqlUtil.newContextException(getPos(), RESOURCE.bangEqualNotAllowed()); } return SqlKind.NOT_EQUALS; } } /** * Parses a unary row expression, or a parenthesized expression of any * kind. */ SqlNode Expression3(ExprContext exprContext) : { final SqlNode e; final SqlNodeList list; final SqlNodeList list1; final Span s; final Span rowSpan; } { LOOKAHEAD(2) e = AtomicRowExpression() { checkNonQueryExpression(exprContext); return e; } | e = CursorExpression(exprContext) { return e; } | LOOKAHEAD(3) { s = span(); } list = ParenthesizedQueryOrCommaList(exprContext) { if (exprContext != ExprContext.ACCEPT_ALL && exprContext != ExprContext.ACCEPT_CURSOR && !this.conformance.allowExplicitRowValueConstructor()) { throw SqlUtil.newContextException(s.end(list), RESOURCE.illegalRowExpression()); } return SqlStdOperatorTable.ROW.createCall(list); } | ( { rowSpan = span(); } | { rowSpan = null; } ) list1 = ParenthesizedQueryOrCommaList(exprContext) { if (rowSpan != null) { // interpret as row constructor return SqlStdOperatorTable.ROW.createCall(rowSpan.end(list1), (List) list1); } } [ LOOKAHEAD(2) /* TODO: ( op = periodOperator() list2 = ParenthesizedQueryOrCommaList(exprContext) { if (list1.size() != 2 || list2.size() != 2) { throw SqlUtil.newContextException( list1.getParserPosition().plus( list2.getParserPosition()), RESOURCE.illegalOverlaps()); } for (SqlNode node : list2) { list1.add(node); } return op.createCall( list1.getParserPosition().plus(list2.getParserPosition()), list1.toArray()); } ) | */ ( e = IntervalQualifier() { if ((list1.size() == 1) && list1.get(0) instanceof SqlCall) { final SqlCall call = (SqlCall) list1.get(0); if (call.getKind() == SqlKind.MINUS && call.operandCount() == 2) { return SqlStdOperatorTable.MINUS_DATE.createCall( Span.of(list1).end(this), call.operand(0), call.operand(1), e); } } throw SqlUtil.newContextException(span().end(list1), RESOURCE.illegalMinusDate()); } ) ] { if (list1.size() == 1) { // interpret as single value or query return list1.get(0); } else { // interpret as row constructor return SqlStdOperatorTable.ROW.createCall(span().end(list1), (List) list1); } } } SqlOperator periodOperator() : { } { { return SqlStdOperatorTable.OVERLAPS; } | LOOKAHEAD(2) { return SqlStdOperatorTable.IMMEDIATELY_PRECEDES; } | { return SqlStdOperatorTable.PRECEDES; } | { return SqlStdOperatorTable.IMMEDIATELY_SUCCEEDS; } | { return SqlStdOperatorTable.SUCCEEDS; } | { return SqlStdOperatorTable.PERIOD_EQUALS; } } /** * Parses a COLLATE clause */ SqlCollation CollateClause() : { } { { return new SqlCollation( getToken(0).image, SqlCollation.Coercibility.EXPLICIT); } } /** * Numeric literal or parameter; used in LIMIT, OFFSET and FETCH clauses. */ SqlNode UnsignedNumericLiteralOrParam() : { final SqlNode e; } { ( e = UnsignedNumericLiteral() | e = DynamicParam() ) { return e; } } /** * Parses a row expression extension, it can be either an identifier, * or a call to a named function. */ SqlNode RowExpressionExtension() : { final SqlFunctionCategory funcType = SqlFunctionCategory.USER_DEFINED_FUNCTION; final SqlIdentifier p; final Span s; final List args; final SqlLiteral quantifier; } { p = SimpleIdentifier() ( LOOKAHEAD( ) { s = span(); } ( LOOKAHEAD(2) { quantifier = null; args = ImmutableList.of(SqlIdentifier.star(getPos())); } | LOOKAHEAD(2) { quantifier = null; args = ImmutableList.of(); } | args = FunctionParameterList(ExprContext.ACCEPT_SUB_QUERY) { quantifier = (SqlLiteral) args.get(0); args.remove(0); } ) { return createCall(p, s.end(this), funcType, quantifier, args); } | { return p; } ) } /** * Parses a call to the STRING_AGG aggregate function (or to an aggregate * function with similar syntax: ARRAY_AGG, ARRAY_CONCAT_AGG, GROUP_CONCAT). */ SqlCall StringAggFunctionCall() : { final Span s, s2; final SqlOperator op; final List args = new ArrayList(); final SqlLiteral qualifier; final SqlNodeList orderBy; final Pair nullTreatment; final SqlNode separator; } { ( { s = span(); op = SqlLibraryOperators.ARRAY_AGG; } | { s = span(); op = SqlLibraryOperators.ARRAY_CONCAT_AGG; } | { s = span(); op = SqlLibraryOperators.GROUP_CONCAT; } | { s = span(); op = SqlLibraryOperators.STRING_AGG; } ) ( qualifier = AllOrDistinct() | { qualifier = null; } ) AddArg(args, ExprContext.ACCEPT_SUB_QUERY) ( { // a comma-list can't appear where only a query is expected // TODO: the following line is a no-op; remove it? checkNonQueryExpression(ExprContext.ACCEPT_SUB_QUERY); } AddArg(args, ExprContext.ACCEPT_SUB_QUERY) )* ( nullTreatment = NullTreatment() | { nullTreatment = null; } ) [ orderBy = OrderBy(true) { args.add(orderBy); } ] [ { s2 = span(); } separator = StringLiteral() { args.add(SqlInternalOperators.SEPARATOR.createCall(s2.end(this), separator)); } ] { SqlCall call = op.createCall(qualifier, s.end(this), args); if (nullTreatment != null) { // Wrap in RESPECT_NULLS or IGNORE_NULLS. call = nullTreatment.right.createCall(nullTreatment.left, call); } return call; } } /** * Parses an atomic row expression. */ SqlNode AtomicRowExpression() : { final SqlNode e; } { ( LOOKAHEAD(2) e = LiteralOrIntervalExpression() | e = DynamicParam() | LOOKAHEAD(2) e = BuiltinFunctionCall() | e = JdbcFunctionCall() | e = MultisetConstructor() | e = ArrayConstructor() | LOOKAHEAD(3) e = MapConstructor() | e = PeriodConstructor() | // NOTE jvs 18-Jan-2005: use syntactic lookahead to discriminate // compound identifiers from function calls in which the function // name is a compound identifier LOOKAHEAD( [] FunctionName() ) e = NamedFunctionCall() | e = ContextVariable() | e = CompoundIdentifier() | e = NewSpecification() | e = CaseExpression() | e = SequenceExpression() ) { return e; } } SqlNode CaseExpression() : { final Span whenSpan = Span.of(); final Span thenSpan = Span.of(); final Span s; SqlNode e; final SqlNode caseIdentifier; final SqlNode elseClause; final List whenList = new ArrayList(); final List thenList = new ArrayList(); } { { s = span(); } ( caseIdentifier = Expression(ExprContext.ACCEPT_SUB_QUERY) | { caseIdentifier = null; } ) ( { whenSpan.add(this); } e = ExpressionCommaList(s, ExprContext.ACCEPT_SUB_QUERY) { if (((SqlNodeList) e).size() == 1) { e = ((SqlNodeList) e).get(0); } whenList.add(e); } { thenSpan.add(this); } e = Expression(ExprContext.ACCEPT_SUB_QUERY) { thenList.add(e); } )+ ( elseClause = Expression(ExprContext.ACCEPT_SUB_QUERY) | { elseClause = null; } ) { return SqlCase.createSwitched(s.end(this), caseIdentifier, new SqlNodeList(whenList, whenSpan.addAll(whenList).pos()), new SqlNodeList(thenList, thenSpan.addAll(thenList).pos()), elseClause); } } SqlCall SequenceExpression() : { final Span s; final SqlOperator f; final SqlNode sequenceRef; } { ( { f = SqlStdOperatorTable.NEXT_VALUE; s = span(); } | LOOKAHEAD(3) { f = SqlStdOperatorTable.CURRENT_VALUE; s = span(); } ) sequenceRef = CompoundIdentifier() { return f.createCall(s.end(sequenceRef), sequenceRef); } } /** * Parses "SET <NAME> = VALUE" or "RESET <NAME>", without a leading * "ALTER <SCOPE>". */ SqlSetOption SqlSetOption(Span s, String scope) : { SqlIdentifier name; final SqlNode val; } { ( { s.add(this); } name = CompoundIdentifier() ( val = Literal() | val = SimpleIdentifier() | { // OFF is handled by SimpleIdentifier, ON handled here. val = new SqlIdentifier(token.image.toUpperCase(Locale.ROOT), getPos()); } ) { return new SqlSetOption(s.end(val), scope, name, val); } | { s.add(this); } ( name = CompoundIdentifier() | { name = new SqlIdentifier(token.image.toUpperCase(Locale.ROOT), getPos()); } ) { return new SqlSetOption(s.end(name), scope, name, null); } ) } /** * Parses an expression for setting or resetting an option in SQL, such as QUOTED_IDENTIFIERS, * or explain plan level (physical/logical). */ SqlAlter SqlAlter() : { final Span s; final String scope; final SqlAlter alterNode; } { { s = span(); } scope = Scope() ( alterNode = DrillSqlSetOption(s, scope) | alterNode = DrillSqlResetOption(s, scope) | alterNode = SqlSetOption(s, scope) ) { return alterNode; } } String Scope() : { } { ( | ) { return token.image.toUpperCase(Locale.ROOT); } } /** * Parses a literal expression, allowing continued string literals. * Usually returns an SqlLiteral, but a continued string literal * is an SqlCall expression, which concatenates 2 or more string * literals; the validator reduces this. * *

If the context allows both literals and expressions, * use {@link #LiteralOrIntervalExpression}, which requires less * lookahead. */ SqlNode Literal() : { SqlNode e; } { ( e = NonIntervalLiteral() | e = IntervalLiteral() ) { return e; } } /** Parses a literal that is not an interval literal. */ SqlNode NonIntervalLiteral() : { final SqlNode e; } { ( e = NumericLiteral() | e = StringLiteral() | e = SpecialLiteral() | e = DateTimeLiteral() ) { return e; } } /** Parses a literal or an interval expression. * *

We include them in the same production because it is difficult to * distinguish interval literals from interval expression (both of which * start with the {@code INTERVAL} keyword); this way, we can use less * LOOKAHEAD. */ SqlNode LiteralOrIntervalExpression() : { final SqlNode e; } { ( e = IntervalLiteralOrExpression() | e = NonIntervalLiteral() ) { return e; } } /** Parses a unsigned numeric literal */ SqlNumericLiteral UnsignedNumericLiteral() : { } { { return SqlLiteral.createExactNumeric(token.image, getPos()); } | { return SqlLiteral.createExactNumeric(token.image, getPos()); } | { return SqlLiteral.createApproxNumeric(token.image, getPos()); } } /** Parses a numeric literal (can be signed) */ SqlLiteral NumericLiteral() : { final SqlNumericLiteral num; final Span s; } { num = UnsignedNumericLiteral() { return num; } | { s = span(); } num = UnsignedNumericLiteral() { return SqlLiteral.createNegative(num, s.end(this)); } | num = UnsignedNumericLiteral() { return num; } } /** Parse a special literal keyword */ SqlLiteral SpecialLiteral() : { } { { return SqlLiteral.createBoolean(true, getPos()); } | { return SqlLiteral.createBoolean(false, getPos()); } | { return SqlLiteral.createUnknown(getPos()); } | { return SqlLiteral.createNull(getPos()); } } /** * Parses a string literal. The literal may be continued onto several * lines. For a simple literal, the result is an SqlLiteral. For a continued * literal, the result is an SqlCall expression, which concatenates 2 or more * string literals; the validator reduces this. * * @see SqlLiteral#unchain(SqlNode) * @see SqlLiteral#stringValue(SqlNode) * * @return a literal expression */ SqlNode StringLiteral() : { String p; final List frags; char unicodeEscapeChar = 0; String charSet = null; SqlCharStringLiteral literal; } { // A continued string literal consists of a head fragment and one or more // tail fragments. Since comments may occur between the fragments, and // comments are special tokens, each fragment is a token. But since spaces // or comments may not occur between the prefix and the first quote, the // head fragment, with any prefix, is one token. { frags = new ArrayList(); try { p = SqlParserUtil.trim(token.image, "xX'"); frags.add(SqlLiteral.createBinaryString(p, getPos())); } catch (NumberFormatException ex) { throw SqlUtil.newContextException(getPos(), RESOURCE.illegalBinaryString(token.image)); } } ( // The grammar is ambiguous when a continued literals and a character // string alias are both possible. For example, in // SELECT x'01'\n'ab' // we prefer that 'ab' continues the literal, and is not an alias. // The following LOOKAHEAD mutes the warning about ambiguity. LOOKAHEAD(1) { try { p = SqlParserUtil.trim(token.image, "'"); // no embedded quotes frags.add(SqlLiteral.createBinaryString(p, getPos())); } catch (NumberFormatException ex) { throw SqlUtil.newContextException(getPos(), RESOURCE.illegalBinaryString(token.image)); } } )* { assert !frags.isEmpty(); if (frags.size() == 1) { return frags.get(0); // just the head fragment } else { SqlParserPos pos2 = SqlParserPos.sum(frags); return SqlStdOperatorTable.LITERAL_CHAIN.createCall(pos2, frags); } } | ( { charSet = SqlParserUtil.getCharacterSet(token.image); } | | { // TODO jvs 2-Feb-2009: support the explicit specification of // a character set for Unicode string literals, per SQL:2003 unicodeEscapeChar = BACKSLASH; charSet = "UTF16"; } ) { frags = new ArrayList(); p = SqlParserUtil.parseString(token.image); try { literal = SqlLiteral.createCharString(p, charSet, getPos()); frags.add(literal); } catch (java.nio.charset.UnsupportedCharsetException e) { throw SqlUtil.newContextException(getPos(), RESOURCE.unknownCharacterSet(charSet)); } } ( // The grammar is ambiguous when a continued literals and a character // string alias are both possible. For example, in // SELECT 'taxi'\n'cab' // we prefer that 'cab' continues the literal, and is not an alias. // The following LOOKAHEAD mutes the warning about ambiguity. LOOKAHEAD(1) { p = SqlParserUtil.parseString(token.image); try { literal = SqlLiteral.createCharString(p, charSet, getPos()); frags.add(literal); } catch (java.nio.charset.UnsupportedCharsetException e) { throw SqlUtil.newContextException(getPos(), RESOURCE.unknownCharacterSet(charSet)); } } )* [ { if (unicodeEscapeChar == 0) { throw SqlUtil.newContextException(getPos(), RESOURCE.unicodeEscapeUnexpected()); } String s = SqlParserUtil.parseString(token.image); unicodeEscapeChar = SqlParserUtil.checkUnicodeEscapeChar(s); } ] { assert !frags.isEmpty(); if (frags.size() == 1) { // just the head fragment SqlLiteral lit = (SqlLiteral) frags.get(0); return lit.unescapeUnicode(unicodeEscapeChar); } else { SqlNode[] rands = (SqlNode[]) frags.toArray(new SqlNode[0]); for (int i = 0; i < rands.length; ++i) { rands[i] = ((SqlLiteral) rands[i]).unescapeUnicode( unicodeEscapeChar); } SqlParserPos pos2 = SqlParserPos.sum(rands); return SqlStdOperatorTable.LITERAL_CHAIN.createCall(pos2, rands); } } | { try { p = SqlParserUtil.parseCString(getToken(0).image); } catch (SqlParserUtil.MalformedUnicodeEscape e) { throw SqlUtil.newContextException(getPos(), RESOURCE.unicodeEscapeMalformed(e.i)); } return SqlLiteral.createCharString(p, "UTF16", getPos()); } | { p = SqlParserUtil.stripQuotes(getToken(0).image, DQ, DQ, "\\\"", Casing.UNCHANGED); try { return SqlLiteral.createCharString(p, charSet, getPos()); } catch (java.nio.charset.UnsupportedCharsetException e) { throw SqlUtil.newContextException(getPos(), RESOURCE.unknownCharacterSet(charSet)); } } | { p = SqlParserUtil.stripQuotes(getToken(0).image, "'", "'", "\\'", Casing.UNCHANGED); try { return SqlLiteral.createCharString(p, charSet, getPos()); } catch (java.nio.charset.UnsupportedCharsetException e) { throw SqlUtil.newContextException(getPos(), RESOURCE.unknownCharacterSet(charSet)); } } } /** Parses a character literal. * Matches a single-quoted string, such as 'foo'; * on BigQuery also matches a double-quoted string, such as "foo". * Returns the value of the string with quotes removed. */ String SimpleStringLiteral() : { } { { return SqlParserUtil.parseString(token.image); } | { return SqlParserUtil.stripQuotes(token.image, "'", "'", "\\'", Casing.UNCHANGED); } | { return SqlParserUtil.stripQuotes(token.image, DQ, DQ, "\\\"", Casing.UNCHANGED); } } /** * Parses a date/time literal. */ SqlLiteral DateTimeLiteral() : { final String p; final Span s; } { { p = SqlParserUtil.parseString(token.image); } { return SqlParserUtil.parseDateLiteral(p, getPos()); } | { p = SqlParserUtil.parseString(token.image); } { return SqlParserUtil.parseTimeLiteral(p, getPos()); } | { s = span(); } { p = SqlParserUtil.parseString(token.image); } { return SqlParserUtil.parseTimestampLiteral(p, s.end(this)); } | { s = span(); } p = SimpleStringLiteral() { return SqlLiteral.createUnknown("DATE", p, s.end(this)); } | { s = span(); } p = SimpleStringLiteral() { return SqlLiteral.createUnknown("DATETIME", p, s.end(this)); } |

The units are used in several functions, incuding CEIL, FLOOR, EXTRACT. * Includes NANOSECOND, MILLISECOND, which were previously allowed in EXTRACT * but not CEIL, FLOOR. * *

Includes {@code WEEK} and {@code WEEK(SUNDAY)} through {@code WEEK(SATURDAY)}. * *

Does not include SQL_TSI_DAY, SQL_TSI_FRAC_SECOND etc. These will be * parsed as identifiers and can be resolved in the validator if they are * registered as abbreviations in your time frame set. */ SqlIntervalQualifier TimeUnitOrName() : { final SqlIdentifier unitName; final SqlIntervalQualifier intervalQualifier; } { // When we see a time unit that is also a non-reserved keyword, such as // NANOSECOND, there is a choice between using the TimeUnit enum // (TimeUnit.NANOSECOND) or the name. The following LOOKAHEAD directive // tells the parser that we prefer the former. // // Reserved keywords, such as SECOND, cannot be identifiers, and are // therefore not ambiguous. LOOKAHEAD(2) intervalQualifier = TimeUnit() { return intervalQualifier; } | unitName = SimpleIdentifier() { return new SqlIntervalQualifier(unitName.getSimple(), unitName.getParserPosition()); } } /** Parses a built-in time unit (e.g. "YEAR") * and returns a {@link SqlIntervalQualifier}. * *

Includes {@code WEEK} and {@code WEEK(SUNDAY)} through {@code WEEK(SATURDAY)}. * *

Does not include SQL_TSI_DAY, SQL_TSI_FRAC_SECOND etc. These will be * parsed as identifiers and can be resolved in the validator if they are * registered as abbreviations in your time frame set. */ SqlIntervalQualifier TimeUnit() : { final Span span; final String w; } { { return new SqlIntervalQualifier(TimeUnit.NANOSECOND, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.MICROSECOND, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.MILLISECOND, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.SECOND, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.MINUTE, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.HOUR, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.DAY, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.DOW, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.DOY, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.ISODOW, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.ISOYEAR, null, getPos()); } | { span = span(); } ( // There is a choice between "WEEK(weekday)" and "WEEK". We prefer // the former, and the parser will look ahead for '('. LOOKAHEAD(2) w = weekdayName() { return new SqlIntervalQualifier(w, span.end(this)); } | { return new SqlIntervalQualifier(TimeUnit.WEEK, null, getPos()); } ) | { return new SqlIntervalQualifier(TimeUnit.MONTH, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.QUARTER, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.YEAR, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.EPOCH, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.DECADE, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.CENTURY, null, getPos()); } | { return new SqlIntervalQualifier(TimeUnit.MILLENNIUM, null, getPos()); } } String weekdayName() : { } { { return "WEEK_SUNDAY"; } | { return "WEEK_MONDAY"; } | { return "WEEK_TUESDAY"; } | { return "WEEK_WEDNESDAY"; } | { return "WEEK_THURSDAY"; } | { return "WEEK_FRIDAY"; } | { return "WEEK_SATURDAY"; } } /** * Parses a dynamic parameter marker. */ SqlDynamicParam DynamicParam() : { } { { return new SqlDynamicParam(nDynamicParams++, getPos()); } } /** * Parses one segment of an identifier that may be composite. * *

Each time it reads an identifier it writes one element to each list; * the entry in {@code positions} records its position and whether the * segment was quoted. */ void AddIdentifierSegment(List names, List positions) : { final String id; char unicodeEscapeChar = BACKSLASH; final SqlParserPos pos; final Span span; } { ( { id = unquotedIdentifier(); pos = getPos(); } | { id = unquotedIdentifier(); pos = getPos(); } | { id = SqlParserUtil.stripQuotes(getToken(0).image, DQ, DQ, DQDQ, quotedCasing); pos = getPos().withQuoting(true); } | { id = SqlParserUtil.stripQuotes(getToken(0).image, "`", "`", "``", quotedCasing); pos = getPos().withQuoting(true); } | { id = SqlParserUtil.stripQuotes(getToken(0).image, "`", "`", "\\`", quotedCasing); pos = getPos().withQuoting(true); } | { id = SqlParserUtil.stripQuotes(getToken(0).image, "[", "]", "]]", quotedCasing); pos = getPos().withQuoting(true); } | { span = span(); String image = getToken(0).image; image = image.substring(image.indexOf('"')); image = SqlParserUtil.stripQuotes(image, DQ, DQ, DQDQ, quotedCasing); } [ { String s = SqlParserUtil.parseString(token.image); unicodeEscapeChar = SqlParserUtil.checkUnicodeEscapeChar(s); } ] { pos = span.end(this).withQuoting(true); SqlLiteral lit = SqlLiteral.createCharString(image, "UTF16", pos); lit = lit.unescapeUnicode(unicodeEscapeChar); id = lit.toValue(); } | id = NonReservedKeyWord() { pos = getPos(); } ) { if (id.length() > this.identifierMaxLength) { throw SqlUtil.newContextException(pos, RESOURCE.identifierTooLong(id, this.identifierMaxLength)); } names.add(id); if (positions != null) { positions.add(pos); } } } /** As {@link #AddIdentifierSegment} but part of a table name (for example, * following {@code FROM}, {@code INSERT} or {@code UPDATE}). * *

In some dialects the lexical rules for table names are different from * for other identifiers. For example, in BigQuery, table names may contain * hyphens. */ void AddTableIdentifierSegment(List names, List positions) : { } { AddIdentifierSegment(names, positions) { final int n = names.size(); if (n > 0 && positions.size() == n && names.get(n - 1).contains(".") && positions.get(n - 1).isQuoted() && this.conformance.splitQuotedTableName()) { final String name = names.remove(n - 1); final SqlParserPos pos = positions.remove(n - 1); final String[] splitNames = name.split("\\."); for (String splitName : splitNames) { names.add(splitName); positions.add(pos); } } } } /** * Parses a simple identifier as a String. */ String Identifier() : { final List names = new ArrayList(); } { AddIdentifierSegment(names, null) { return names.get(0); } } /** * Parses a simple identifier as an SqlIdentifier. */ SqlIdentifier SimpleIdentifier() : { final List names = new ArrayList(); final List positions = new ArrayList(); } { AddIdentifierSegment(names, positions) { return new SqlIdentifier(names.get(0), positions.get(0)); } } /** * Parses a character literal as an SqlIdentifier. * Only valid for column aliases in certain dialects. */ SqlIdentifier SimpleIdentifierFromStringLiteral() : { } { { if (!this.conformance.allowCharLiteralAlias()) { throw SqlUtil.newContextException(getPos(), RESOURCE.charLiteralAliasNotValid()); } final String s = SqlParserUtil.parseString(token.image); return new SqlIdentifier(s, getPos()); } } /** * Parses a comma-separated list of simple identifiers. */ void AddSimpleIdentifiers(List list) : { SqlIdentifier id; } { id = SimpleIdentifier() {list.add(id);} ( id = SimpleIdentifier() { list.add(id); } )* } /** * List of simple identifiers in parentheses. The position extends from the * open parenthesis to the close parenthesis. */ SqlNodeList ParenthesizedSimpleIdentifierList() : { final Span s; final List list = new ArrayList(); } { { s = span(); } AddSimpleIdentifiers(list) { return new SqlNodeList(list, s.end(this)); } } /** List of simple identifiers in parentheses or one simple identifier. * *

    Examples: *
  • {@code DEPTNO} *
  • {@code (EMPNO, DEPTNO)} *
*/ SqlNodeList SimpleIdentifierOrList() : { SqlIdentifier id; SqlNodeList list; } { id = SimpleIdentifier() { return new SqlNodeList(Collections.singletonList(id), id.getParserPosition()); } | list = ParenthesizedSimpleIdentifierList() { return list; } } /** * Parses a Drill compound identifier. */ SqlIdentifier CompoundIdentifier() : { DrillCompoundIdentifier.Builder builder = DrillCompoundIdentifier.newBuilder(); String p; int index; } { p = Identifier() { builder.addString(p, getPos()); } ( ( ( p = Identifier() { builder.addString(p, getPos()); } | { builder.addString("*", getPos()); } ) ) | ( index = UnsignedIntLiteral() { builder.addIndex(index, getPos()); } ) ) * { return builder.build(); } } /** * Parses a compound identifier in the FROM clause. */ SqlIdentifier CompoundTableIdentifier() : { final List nameList = new ArrayList(); final List posList = new ArrayList(); } { AddTableIdentifierSegment(nameList, posList) ( LOOKAHEAD(2) AddTableIdentifierSegment(nameList, posList) )* { SqlParserPos pos = SqlParserPos.sum(posList); return new SqlIdentifier(nameList, null, pos, posList); } } /** * Parses a comma-separated list of compound identifiers. */ void AddCompoundIdentifierTypeCommaList(List list, List extendList) : { } { AddCompoundIdentifierType(list, extendList) ( AddCompoundIdentifierType(list, extendList))* } /** * List of compound identifiers in parentheses. The position extends from the * open parenthesis to the close parenthesis. */ Pair ParenthesizedCompoundIdentifierList() : { final Span s; final List list = new ArrayList(); final List extendList = new ArrayList(); } { { s = span(); } AddCompoundIdentifierTypeCommaList(list, extendList) { return Pair.of(new SqlNodeList(list, s.end(this)), new SqlNodeList(extendList, s.end(this))); } } /** * Parses a NEW UDT(...) expression. */ SqlNode NewSpecification() : { final Span s; final SqlNode routineCall; } { { s = span(); } routineCall = NamedRoutineCall(SqlFunctionCategory.USER_DEFINED_CONSTRUCTOR, ExprContext.ACCEPT_SUB_QUERY) { return SqlStdOperatorTable.NEW.createCall(s.end(routineCall), routineCall); } } //TODO: real parse errors. int UnsignedIntLiteral() : { Token t; } { t = { try { return Integer.parseInt(t.image); } catch (NumberFormatException ex) { throw SqlUtil.newContextException(getPos(), RESOURCE.invalidLiteral(t.image, Integer.class.getCanonicalName())); } } } int IntLiteral() : { Token t; } { ( t = | t = ) { try { return Integer.parseInt(t.image); } catch (NumberFormatException ex) { throw SqlUtil.newContextException(getPos(), RESOURCE.invalidLiteral(t.image, Integer.class.getCanonicalName())); } } | t = { try { return -Integer.parseInt(t.image); } catch (NumberFormatException ex) { throw SqlUtil.newContextException(getPos(), RESOURCE.invalidLiteral(t.image, Integer.class.getCanonicalName())); } } } // Type name with optional scale and precision. SqlDataTypeSpec DataType() : { SqlTypeNameSpec typeName; final Span s; } { typeName = TypeName() { s = Span.of(typeName.getParserPos()); } ( typeName = CollectionsTypeName(typeName) )* { return new SqlDataTypeSpec(typeName, s.add(typeName.getParserPos()).pos()); } } // Some SQL type names need special handling due to the fact that they have // spaces in them but are not quoted. SqlTypeNameSpec TypeName() : { final SqlTypeNameSpec typeNameSpec; final SqlIdentifier typeName; final Span s = Span.of(); } { ( LOOKAHEAD(2) typeNameSpec = SqlTypeName(s) | typeNameSpec = RowTypeName() | typeName = CompoundIdentifier() { typeNameSpec = new SqlUserDefinedTypeNameSpec(typeName, s.end(this)); } ) { return typeNameSpec; } } // Types used for JDBC and ODBC scalar conversion function SqlTypeNameSpec SqlTypeName(Span s) : { final SqlTypeNameSpec sqlTypeNameSpec; } { ( sqlTypeNameSpec = SqlTypeName1(s) | sqlTypeNameSpec = SqlTypeName2(s) | sqlTypeNameSpec = SqlTypeName3(s) | sqlTypeNameSpec = CharacterTypeName(s) | sqlTypeNameSpec = DateTimeTypeName() ) { return sqlTypeNameSpec; } } // Parse sql type name that don't allow any extra specifications except the type name. // For extra specification, we mean precision, scale, charSet, etc. SqlTypeNameSpec SqlTypeName1(Span s) : { final SqlTypeName sqlTypeName; } { ( { if (!this.conformance.allowGeometry()) { throw SqlUtil.newContextException(getPos(), RESOURCE.geometryDisabled()); } s.add(this); sqlTypeName = SqlTypeName.GEOMETRY; } | { s.add(this); sqlTypeName = SqlTypeName.BOOLEAN; } | ( | ) { s.add(this); sqlTypeName = SqlTypeName.INTEGER; } | { s.add(this); sqlTypeName = SqlTypeName.TINYINT; } | { s.add(this); sqlTypeName = SqlTypeName.SMALLINT; } | { s.add(this); sqlTypeName = SqlTypeName.BIGINT; } | { s.add(this); sqlTypeName = SqlTypeName.REAL; } | { s.add(this); } [ ] { sqlTypeName = SqlTypeName.DOUBLE; } | { s.add(this); sqlTypeName = SqlTypeName.FLOAT; } ) { return new SqlBasicTypeNameSpec(sqlTypeName, s.end(this)); } } // Parse sql type name that allows precision specification. SqlTypeNameSpec SqlTypeName2(Span s) : { final SqlTypeName sqlTypeName; int precision = -1; } { ( { s.add(this); } ( { sqlTypeName = SqlTypeName.VARBINARY; } | { sqlTypeName = SqlTypeName.BINARY; } ) | { s.add(this); sqlTypeName = SqlTypeName.VARBINARY; } ) precision = PrecisionOpt() { return new SqlBasicTypeNameSpec(sqlTypeName, precision, s.end(this)); } } // Parse sql type name that allows precision and scale specifications. SqlTypeNameSpec SqlTypeName3(Span s) : { final SqlTypeName sqlTypeName; int precision = -1; int scale = -1; } { ( ( | | ) { s.add(this); sqlTypeName = SqlTypeName.DECIMAL; } | { s.add(this); sqlTypeName = SqlTypeName.ANY; } ) [ precision = UnsignedIntLiteral() [ scale = UnsignedIntLiteral() ] ] { return new SqlBasicTypeNameSpec(sqlTypeName, precision, scale, s.end(this)); } } // Types used for for JDBC and ODBC scalar conversion function SqlJdbcDataTypeName JdbcOdbcDataTypeName() : { } { ( | ) { return SqlJdbcDataTypeName.SQL_CHAR; } | ( | ) { return SqlJdbcDataTypeName.SQL_VARCHAR; } | ( | ) { return SqlJdbcDataTypeName.SQL_DATE; } | ( |