You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2022/06/06 22:14:35 UTC
[calcite] 01/01: [CALCITE-5147] Allow DATE, TIME, TIMESTAMP, INTERVAL literals in BigQuery dialect
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 <we...@singleorigin.tech>
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