This is an automated email from the ASF dual-hosted git repository. jhyde pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/calcite.git
commit 4659479a508461921c5b1e3e7c4d09cb745de33e Author: wenrui <[email protected]> AuthorDate: Mon May 30 22:50:33 2022 -0700 [CALCITE-5147] Allow DATE, TIME, TIMESTAMP, INTERVAL literals in BigQuery dialect Before this change, the expression DATE '2018-02-17' fails to parse in BigQuery dialect because the parser requires a <QUOTED_STRING> token and actually sees a <BIG_QUERY_QUOTED_STRING> token. Similarly TIME, TIMESTAMP, LITERAL literals. After this change, the above single-quoted form succeeds, as does the double-quoted form like this: DATE "2018-02-17" In SqlParserUtil, the methods parseDateLiteral, parseTimeLiteral, parseTimestampLiteral and parseIntervalLiteral expected a quoted string as an argument (e.g. "'2018-02-17'") and now expect just the value (e.g. "2018-02-17'"). Callers should now call parseString on the argument first. Close apache/calcite#2823 --- core/src/main/codegen/templates/Parser.jj | 44 ++++++++++++++++------ .../apache/calcite/runtime/CalciteResource.java | 2 +- .../apache/calcite/sql/parser/SqlParserUtil.java | 14 +++---- .../calcite/runtime/CalciteResource.properties | 2 +- .../apache/calcite/sql/parser/SqlParserTest.java | 31 +++++++++++++++ 5 files changed, 70 insertions(+), 23 deletions(-) diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj index 0303dca95..6b26d1066 100644 --- a/core/src/main/codegen/templates/Parser.jj +++ b/core/src/main/codegen/templates/Parser.jj @@ -4636,47 +4636,67 @@ SqlNode StringLiteral() : } } +/** 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() : +{ +} +{ + <QUOTED_STRING> { + return SqlParserUtil.parseString(token.image); + } +| + <BIG_QUERY_QUOTED_STRING> { + return SqlParserUtil.stripQuotes(token.image, "'", "'", "\\'", Casing.UNCHANGED); + } +| + <BIG_QUERY_DOUBLE_QUOTED_STRING> { + return SqlParserUtil.stripQuotes(token.image, DQ, DQ, "\\\"", Casing.UNCHANGED); + } +} /** * Parses a date/time literal. */ SqlLiteral DateTimeLiteral() : { - final String p; + final String p; final Span s; } { <LBRACE_D> <QUOTED_STRING> { - p = token.image; + p = SqlParserUtil.parseString(token.image); } <RBRACE> { return SqlParserUtil.parseDateLiteral(p, getPos()); } | <LBRACE_T> <QUOTED_STRING> { - p = token.image; + p = SqlParserUtil.parseString(token.image); } <RBRACE> { return SqlParserUtil.parseTimeLiteral(p, getPos()); } | <LBRACE_TS> { s = span(); } <QUOTED_STRING> { - p = token.image; + p = SqlParserUtil.parseString(token.image); } <RBRACE> { return SqlParserUtil.parseTimestampLiteral(p, s.end(this)); } | - <DATE> { s = span(); } <QUOTED_STRING> { - return SqlParserUtil.parseDateLiteral(token.image, s.end(this)); + <DATE> { s = span(); } p = SimpleStringLiteral() { + return SqlParserUtil.parseDateLiteral(p, s.end(this)); } | - <TIME> { s = span(); } <QUOTED_STRING> { - return SqlParserUtil.parseTimeLiteral(token.image, s.end(this)); + <TIME> { s = span(); } p = SimpleStringLiteral() { + return SqlParserUtil.parseTimeLiteral(p, s.end(this)); } | - <TIMESTAMP> { s = span(); } <QUOTED_STRING> { - return SqlParserUtil.parseTimestampLiteral(token.image, s.end(this)); + <TIMESTAMP> { s = span(); } p = SimpleStringLiteral() { + return SqlParserUtil.parseTimestampLiteral(p, s.end(this)); } } @@ -4817,7 +4837,7 @@ SqlLiteral IntervalLiteral() : | <PLUS> { sign = 1; } ] - <QUOTED_STRING> { p = token.image; } + p = SimpleStringLiteral() intervalQualifier = IntervalQualifier() { return SqlParserUtil.parseIntervalLiteral(s.end(intervalQualifier), sign, p, intervalQualifier); @@ -4844,7 +4864,7 @@ SqlNode IntervalLiteralOrExpression() : ] ( // literal (with quoted string) - <QUOTED_STRING> { p = token.image; } + p = SimpleStringLiteral() intervalQualifier = IntervalQualifier() { return SqlParserUtil.parseIntervalLiteral(s.end(intervalQualifier), sign, p, intervalQualifier); diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java index c8957fbe8..74588ee01 100644 --- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java +++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java @@ -51,7 +51,7 @@ public interface CalciteResource { @BaseMessage("JSON path expression must be specified after the JSON value expression") ExInst<CalciteException> jsonPathMustBeSpecified(); - @BaseMessage("Illegal {0} literal {1}: {2}") + @BaseMessage("Illegal {0} literal ''{1}'': {2}") ExInst<CalciteException> illegalLiteral(String a0, String a1, String a2); @BaseMessage("Length of identifier ''{0}'' must be less than or equal to {1,number,#} characters") diff --git a/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java b/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java index d60a57958..f625c9a0b 100644 --- a/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java +++ b/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java @@ -140,9 +140,8 @@ public final class SqlParserUtil { } public static SqlDateLiteral parseDateLiteral(String s, SqlParserPos pos) { - final String dateStr = parseString(s); final Calendar cal = - DateTimeUtils.parseDateFormat(dateStr, Format.get().date, + DateTimeUtils.parseDateFormat(s, Format.get().date, DateTimeUtils.UTC_ZONE); if (cal == null) { throw SqlUtil.newContextException(pos, @@ -154,9 +153,8 @@ public final class SqlParserUtil { } public static SqlTimeLiteral parseTimeLiteral(String s, SqlParserPos pos) { - final String dateStr = parseString(s); final DateTimeUtils.PrecisionTime pt = - DateTimeUtils.parsePrecisionDateTimeLiteral(dateStr, + DateTimeUtils.parsePrecisionDateTimeLiteral(s, Format.get().time, DateTimeUtils.UTC_ZONE, -1); if (pt == null) { throw SqlUtil.newContextException(pos, @@ -170,14 +168,13 @@ public final class SqlParserUtil { public static SqlTimestampLiteral parseTimestampLiteral(String s, SqlParserPos pos) { - final String dateStr = parseString(s); final Format format = Format.get(); DateTimeUtils.PrecisionTime pt = null; // Allow timestamp literals with and without time fields (as does // PostgreSQL); TODO: require time fields except in Babel's lenient mode final DateFormat[] dateFormats = {format.timestamp, format.date}; for (DateFormat dateFormat : dateFormats) { - pt = DateTimeUtils.parsePrecisionDateTimeLiteral(dateStr, + pt = DateTimeUtils.parsePrecisionDateTimeLiteral(s, dateFormat, DateTimeUtils.UTC_ZONE, -1); if (pt != null) { break; @@ -196,13 +193,12 @@ public final class SqlParserUtil { public static SqlIntervalLiteral parseIntervalLiteral(SqlParserPos pos, int sign, String s, SqlIntervalQualifier intervalQualifier) { - final String intervalStr = parseString(s); - if (intervalStr.equals("")) { + if (s.equals("")) { throw SqlUtil.newContextException(pos, RESOURCE.illegalIntervalLiteral(s + " " + intervalQualifier.toString(), pos.toString())); } - return SqlLiteral.createInterval(sign, intervalStr, intervalQualifier, pos); + return SqlLiteral.createInterval(sign, s, intervalQualifier, pos); } /** diff --git a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties index de833c330..c53c41264 100644 --- a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties +++ b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties @@ -25,7 +25,7 @@ LimitStartCountNotAllowed=''LIMIT start, count'' is not allowed under the curren OffsetLimitNotAllowed=''OFFSET start LIMIT count'' is not allowed under the current SQL conformance level ApplyNotAllowed=APPLY operator is not allowed under the current SQL conformance level JsonPathMustBeSpecified=JSON path expression must be specified after the JSON value expression -IllegalLiteral=Illegal {0} literal {1}: {2} +IllegalLiteral=Illegal {0} literal ''{1}'': {2} IdentifierTooLong=Length of identifier ''{0}'' must be less than or equal to {1,number,#} characters BadFormat=not in format ''{0}'' BetweenWithoutAnd=BETWEEN operator has no terminating AND diff --git a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java index 8fc15d870..7416d2758 100644 --- a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java +++ b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java @@ -4759,6 +4759,37 @@ public class SqlParserTest { sql("select interval '5:6' hour to minute from t").ok(expected4); } + /** Tests that on BigQuery, DATE, TIME and TIMESTAMP literals can use + * single- or double-quoted strings. */ + @Test void testDateLiteralBigQuery() { + final SqlParserFixture f = fixture().withDialect(BIG_QUERY); + f.sql("select date '2020-10-10'") + .ok("SELECT DATE '2020-10-10'"); + f.sql("select date\"2020-10-10\"") + .ok("SELECT DATE '2020-10-10'"); + f.sql("select timestamp '2018-02-17 13:22:04'") + .ok("SELECT TIMESTAMP '2018-02-17 13:22:04'"); + f.sql("select timestamp \"2018-02-17 13:22:04\"") + .ok("SELECT TIMESTAMP '2018-02-17 13:22:04'"); + f.sql("select time '13:22:04'") + .ok("SELECT TIME '13:22:04'"); + f.sql("select time \"13:22:04\"") + .ok("SELECT TIME '13:22:04'"); + } + + @Test void testIntervalLiteralBigQuery() { + final SqlParserFixture f = fixture().withDialect(BIG_QUERY) + .expression(true); + f.sql("interval '1' day") + .ok("INTERVAL '1' DAY"); + f.sql("interval \"1\" day") + .ok("INTERVAL '1' DAY"); + f.sql("interval '1:2:3' hour to second") + .ok("INTERVAL '1:2:3' HOUR TO SECOND"); + f.sql("interval \"1:2:3\" hour to second") + .ok("INTERVAL '1:2:3' HOUR TO SECOND"); + } + // check date/time functions. @Test void testTimeDate() { // CURRENT_TIME - returns time w/ timezone
