diff --git a/kareldb-core/src/main/codegen/templates/Parser.jj b/kareldb-core/src/main/codegen/templates/Parser.jj index 5ec18288..66fccd44 100644 --- a/kareldb-core/src/main/codegen/templates/Parser.jj +++ b/kareldb-core/src/main/codegen/templates/Parser.jj @@ -437,6 +437,27 @@ JAVACODE SqlParseException convertException(Throwable ex) 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, @@ -1807,7 +1828,15 @@ SqlNode SelectItem() : e = SelectExpression() [ [ ] - id = SimpleIdentifier() { + ( + id = SimpleIdentifier() + | + // Mute the warning about ambiguity between alias and continued + // string literal. + LOOKAHEAD(1) + id = SimpleIdentifierFromStringLiteral() + ) + { e = SqlStdOperatorTable.AS.createCall(span().end(e), e, id); } ] @@ -3741,7 +3770,7 @@ SqlNode AtomicRowExpression() : } { ( - e = Literal() + e = LiteralOrIntervalExpression() | e = DynamicParam() | @@ -3977,11 +4006,29 @@ SqlDrop SqlDrop() : * 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() @@ -3991,8 +4038,6 @@ SqlNode Literal() : e = SpecialLiteral() | e = DateTimeLiteral() - | - e = IntervalLiteral() <#-- additional literal parser methods are included here --> <#list parser.literalParserMethods as method> | @@ -4002,8 +4047,25 @@ SqlNode Literal() : { 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 */ @@ -4095,6 +4157,12 @@ SqlNode StringLiteral() : } } ( + // 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 { @@ -4144,6 +4212,12 @@ SqlNode StringLiteral() : nfrags++; } ( + // 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); @@ -4374,6 +4448,53 @@ SqlLiteral IntervalLiteral() : } } +/** Parses an interval literal (e.g. {@code INTERVAL '2:3' HOUR TO MINUTE}) + * or an interval expression (e.g. {@code INTERVAL emp.empno MINUTE} + * or {@code INTERVAL 3 MONTHS}). */ +SqlNode IntervalLiteralOrExpression() : +{ + final String p; + final SqlIntervalQualifier intervalQualifier; + int sign = 1; + final Span s; + SqlNode e; +} +{ + { s = span(); } + [ + { sign = -1; } + | + { sign = 1; } + ] + ( + // literal (with quoted string) + { p = token.image; } + intervalQualifier = IntervalQualifier() { + return SqlParserUtil.parseIntervalLiteral(s.end(intervalQualifier), + sign, p, intervalQualifier); + } + | + // To keep parsing simple, any expressions besides numeric literal and + // identifiers must be enclosed in parentheses. + ( + + e = Expression(ExprContext.ACCEPT_SUB_QUERY) + + | + e = UnsignedNumericLiteral() + | + e = CompoundIdentifier() + ) + intervalQualifier = IntervalQualifierStart() { + if (sign == -1) { + e = SqlStdOperatorTable.UNARY_MINUS.createCall(e.getParserPosition(), e); + } + return SqlStdOperatorTable.INTERVAL.createCall(s.end(this), e, + intervalQualifier); + } + ) +} + TimeUnit Year() : { } @@ -4430,6 +4551,7 @@ TimeUnit Second() : SqlIntervalQualifier IntervalQualifier() : { + final Span s; final TimeUnit start; TimeUnit end = null; int startPrec = RelDataType.PRECISION_NOT_SPECIFIED; @@ -4437,27 +4559,28 @@ SqlIntervalQualifier IntervalQualifier() : } { ( - start = Year() [ startPrec = UnsignedIntLiteral() ] + start = Year() { s = span(); } startPrec = PrecisionOpt() [ LOOKAHEAD(2) end = Month() ] | - start = Month() [ startPrec = UnsignedIntLiteral() ] + start = Month() { s = span(); } startPrec = PrecisionOpt() | - start = Day() [ startPrec = UnsignedIntLiteral() ] - [ LOOKAHEAD(2) + start = Day() { s = span(); } startPrec = PrecisionOpt() + [ + LOOKAHEAD(2) ( end = Hour() | end = Minute() | - end = Second() - [ secondFracPrec = UnsignedIntLiteral() ] + end = Second() secondFracPrec = PrecisionOpt() ) ] | - start = Hour() [ startPrec = UnsignedIntLiteral() ] - [ LOOKAHEAD(2) + start = Hour() { s = span(); } startPrec = PrecisionOpt() + [ + LOOKAHEAD(2) ( end = Minute() | @@ -4466,26 +4589,54 @@ SqlIntervalQualifier IntervalQualifier() : ) ] | - start = Minute() [ startPrec = UnsignedIntLiteral() ] - [ LOOKAHEAD(2) - ( - end = Second() - [ secondFracPrec = UnsignedIntLiteral() ] - ) + start = Minute() { s = span(); } startPrec = PrecisionOpt() + [ + LOOKAHEAD(2) end = Second() + [ secondFracPrec = UnsignedIntLiteral() ] ] | - start = Second() + start = Second() { s = span(); } + [ + startPrec = UnsignedIntLiteral() + [ secondFracPrec = UnsignedIntLiteral() ] + + ] + ) + { + return new SqlIntervalQualifier(start, startPrec, end, secondFracPrec, + s.end(this)); + } +} + +/** Interval qualifier without 'TO unit'. */ +SqlIntervalQualifier IntervalQualifierStart() : +{ + final Span s; + final TimeUnit start; + int startPrec = RelDataType.PRECISION_NOT_SPECIFIED; + int secondFracPrec = RelDataType.PRECISION_NOT_SPECIFIED; +} +{ + ( + ( + start = Year() + | start = Month() + | start = Day() + | start = Hour() + | start = Minute() + ) + { s = span(); } + startPrec = PrecisionOpt() + | + start = Second() { s = span(); } [ startPrec = UnsignedIntLiteral() [ secondFracPrec = UnsignedIntLiteral() ] ] ) { - return new SqlIntervalQualifier(start, - startPrec, - end, - secondFracPrec, - getPos()); + return new SqlIntervalQualifier(start, startPrec, null, secondFracPrec, + s.end(this)); } } @@ -4656,6 +4807,23 @@ SqlIdentifier SimpleIdentifier() : } } +/** + * 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. */ @@ -5201,7 +5369,6 @@ int PrecisionOpt() : int precision = -1; } { - LOOKAHEAD(2) precision = UnsignedIntLiteral() @@ -6055,22 +6222,22 @@ SqlCall GroupByWindowingCall(): ( { - s = span(); op = SqlStdOperatorTable.TUMBLE_OLD; } | { - s = span(); op = SqlStdOperatorTable.HOP_OLD; } | { - s = span(); op = SqlStdOperatorTable.SESSION_OLD; } ) + { + s = span(); + } args = UnquantifiedFunctionParameterList(ExprContext.ACCEPT_SUB_QUERY) { return op.createCall(s.end(this), args); } diff --git a/kareldb-core/src/main/java/io/kareldb/jdbc/rules/EnumerableTableModifyExtensionRule.java b/kareldb-core/src/main/java/io/kareldb/jdbc/rules/EnumerableTableModifyExtensionRule.java index 1939be62..e7c541b6 100644 --- a/kareldb-core/src/main/java/io/kareldb/jdbc/rules/EnumerableTableModifyExtensionRule.java +++ b/kareldb-core/src/main/java/io/kareldb/jdbc/rules/EnumerableTableModifyExtensionRule.java @@ -18,6 +18,7 @@ import org.apache.calcite.adapter.enumerable.EnumerableConvention; import org.apache.calcite.plan.Convention; +import org.apache.calcite.plan.RelTrait; import org.apache.calcite.plan.RelTraitSet; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.convert.ConverterRule; @@ -45,9 +46,15 @@ public class EnumerableTableModifyExtensionRule extends ConverterRule { * @param relBuilderFactory Builder for relational expressions */ public EnumerableTableModifyExtensionRule(RelBuilderFactory relBuilderFactory) { - super(LogicalTableModify.class, (Predicate) r -> true, - Convention.NONE, EnumerableConvention.INSTANCE, relBuilderFactory, - "EnumerableTableModificationExtensionRule"); + super(Config.EMPTY + .withRelBuilderFactory(relBuilderFactory) + .as(Config.class) + .withConversion( + LogicalTableModify.class, + (Predicate) r -> true, + Convention.NONE, + EnumerableConvention.INSTANCE, + "EnumerableTableModificationExtensionRule")); } @Override diff --git a/pom.xml b/pom.xml index 101da26f..c65c9c23 100644 --- a/pom.xml +++ b/pom.xml @@ -57,13 +57,12 @@ limitations under the License. 1.17.0 1.10.0 1.66 - 1.24.0 + 1.25.0 3.10 3.0.2 19.0 - 2.2 2.11.1 2.6