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