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:34 UTC

[calcite] branch main updated (f7aa27ec2 -> 4659479a5)

This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


    from f7aa27ec2 [CALCITE-4913] Deduplicate correlated variables in SELECT clause
     add 9d6838a3c [CALCITE-5086] SQL parser should allow OFFSET to occur before LIMIT
     new 4659479a5 [CALCITE-5147] Allow DATE, TIME, TIMESTAMP, INTERVAL literals in BigQuery dialect

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 core/src/main/codegen/templates/Parser.jj          | 153 +++++++++++++++------
 .../apache/calcite/runtime/CalciteResource.java    |   5 +-
 .../apache/calcite/sql/parser/SqlParserUtil.java   |  14 +-
 .../sql/validate/SqlAbstractConformance.java       |   4 +
 .../calcite/sql/validate/SqlConformance.java       |  18 +++
 .../calcite/sql/validate/SqlConformanceEnum.java   |  10 ++
 .../calcite/runtime/CalciteResource.properties     |   3 +-
 site/_docs/reference.md                            |   5 +-
 .../apache/calcite/sql/parser/SqlParserTest.java   |  79 ++++++++++-
 9 files changed, 229 insertions(+), 62 deletions(-)


[calcite] 01/01: [CALCITE-5147] Allow DATE, TIME, TIMESTAMP, INTERVAL literals in BigQuery dialect

Posted by jh...@apache.org.
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