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 2020/06/01 17:16:53 UTC

[calcite] 04/06: [CALCITE-3946] Add parser support for MULTISET/SET and VOLATILE modifiers in CREATE TABLE statements (Drew Schmitt)

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

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

commit 8daba770396193f91d967d0e649d42f1057e0d95
Author: dasch-1 <da...@google.com>
AuthorDate: Wed Apr 22 12:43:20 2020 -0700

    [CALCITE-3946] Add parser support for MULTISET/SET and VOLATILE modifiers in CREATE TABLE statements (Drew Schmitt)
    
    The syntax for these statements is:
    
      CREATE TABLE [SET|MULTISET] [VOLATILE] <table_name> [IF NOT EXISTS] (<column_name> <data_type>, ...);
    
    Support is added by extending the Babel parser.
    
    Rename 'isVolatile' to 'volatile_' (the 'is' prefix is for
    methods, not fields; the '_' suffix is necessary because
    'volatile' is a Java keyword) and and 'SetType' to
    'TableCollectionType'.
    
    Close apache/calcite#1938
---
 babel/src/main/codegen/config.fmpp                 |  7 ++
 babel/src/main/codegen/includes/parserImpls.ftl    | 98 ++++++++++++++++++++++
 .../java/org/apache/calcite/sql/babel/Babel.java   | 24 ------
 .../calcite/sql/babel/SqlBabelCreateTable.java     | 58 ++++++-------
 .../calcite/sql/babel/TableCollectionType.java     | 48 +++++++++++
 .../org/apache/calcite/test/BabelParserTest.java   | 26 ++++++
 .../org/apache/calcite/sql/ddl/SqlCreateTable.java |  2 +-
 7 files changed, 210 insertions(+), 53 deletions(-)

diff --git a/babel/src/main/codegen/config.fmpp b/babel/src/main/codegen/config.fmpp
index 503bd1c..95a9307 100644
--- a/babel/src/main/codegen/config.fmpp
+++ b/babel/src/main/codegen/config.fmpp
@@ -21,11 +21,17 @@ data: {
 
     # List of import statements.
     imports: [
+      "org.apache.calcite.sql.SqlCreate",
+      "org.apache.calcite.sql.babel.SqlBabelCreateTable",
+      "org.apache.calcite.sql.babel.TableCollectionType",
+      "org.apache.calcite.sql.ddl.SqlDdlNodes",
     ]
 
     # List of keywords.
     keywords: [
+      "IF"
       "SEMI"
+      "VOLATILE"
     ]
 
     # List of keywords from "keywords" section that are not reserved.
@@ -861,6 +867,7 @@ data: {
     # List of methods for parsing extensions to "CREATE [OR REPLACE]" calls.
     # Each must accept arguments "(SqlParserPos pos, boolean replace)".
     createStatementParserMethods: [
+      "SqlCreateTable"
     ]
 
     # List of methods for parsing extensions to "DROP" calls.
diff --git a/babel/src/main/codegen/includes/parserImpls.ftl b/babel/src/main/codegen/includes/parserImpls.ftl
index 55ab4c9..d4a5bb3 100644
--- a/babel/src/main/codegen/includes/parserImpls.ftl
+++ b/babel/src/main/codegen/includes/parserImpls.ftl
@@ -69,6 +69,104 @@ SqlNode DateaddFunctionCall() :
     }
 }
 
+boolean IfNotExistsOpt() :
+{
+}
+{
+    <IF> <NOT> <EXISTS> { return true; }
+|
+    { return false; }
+}
+
+TableCollectionType TableCollectionTypeOpt() :
+{
+}
+{
+    <MULTISET> { return TableCollectionType.MULTISET; }
+|
+    <SET> { return TableCollectionType.SET; }
+|
+    { return TableCollectionType.UNSPECIFIED; }
+}
+
+boolean VolatileOpt() :
+{
+}
+{
+    <VOLATILE> { return true; }
+|
+    { return false; }
+}
+
+SqlNodeList ExtendColumnList() :
+{
+    final Span s;
+    List<SqlNode> list = new ArrayList<SqlNode>();
+}
+{
+    <LPAREN> { s = span(); }
+    ColumnWithType(list)
+    (
+        <COMMA> ColumnWithType(list)
+    )*
+    <RPAREN> {
+        return new SqlNodeList(list, s.end(this));
+    }
+}
+
+void ColumnWithType(List<SqlNode> list) :
+{
+    SqlIdentifier id;
+    SqlDataTypeSpec type;
+    boolean nullable = true;
+    final Span s = Span.of();
+}
+{
+    id = CompoundIdentifier()
+    type = DataType()
+    [
+        <NOT> <NULL> {
+            nullable = false;
+        }
+    ]
+    {
+        list.add(SqlDdlNodes.column(s.add(id).end(this), id,
+            type.withNullable(nullable), null, null));
+    }
+}
+
+SqlCreate SqlCreateTable(Span s, boolean replace) :
+{
+    final TableCollectionType tableCollectionType;
+    final boolean volatile_;
+    final boolean ifNotExists;
+    final SqlIdentifier id;
+    final SqlNodeList columnList;
+    final SqlNode query;
+}
+{
+    tableCollectionType = TableCollectionTypeOpt()
+    volatile_ = VolatileOpt()
+    <TABLE>
+    ifNotExists = IfNotExistsOpt()
+    id = CompoundIdentifier()
+    (
+        columnList = ExtendColumnList()
+    |
+        { columnList = null; }
+    )
+    (
+        <AS> query = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY)
+    |
+        { query = null; }
+    )
+    {
+        return new SqlBabelCreateTable(s.end(this), replace,
+            tableCollectionType, volatile_, ifNotExists, id, columnList, query);
+    }
+}
+
+
 /* Extra operators */
 
 <DEFAULT, DQID, BTID> TOKEN :
diff --git a/babel/src/main/java/org/apache/calcite/sql/babel/Babel.java b/babel/src/main/java/org/apache/calcite/sql/babel/Babel.java
deleted file mode 100644
index 5b1760a..0000000
--- a/babel/src/main/java/org/apache/calcite/sql/babel/Babel.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.calcite.sql.babel;
-
-/** SQL parser that accepts a wide variety of dialects. */
-@SuppressWarnings("unused")
-public class Babel {
-  // This class is currently a place-holder. Javadoc gets upset
-  // if there are no classes in babel/java/main.
-}
diff --git a/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java b/babel/src/main/java/org/apache/calcite/sql/babel/SqlBabelCreateTable.java
similarity index 59%
copy from core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
copy to babel/src/main/java/org/apache/calcite/sql/babel/SqlBabelCreateTable.java
index 0773004..511bef3 100644
--- a/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
+++ b/babel/src/main/java/org/apache/calcite/sql/babel/SqlBabelCreateTable.java
@@ -14,48 +14,50 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.calcite.sql.ddl;
+package org.apache.calcite.sql.babel;
 
-import org.apache.calcite.sql.SqlCreate;
 import org.apache.calcite.sql.SqlIdentifier;
-import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.ddl.SqlCreateTable;
 import org.apache.calcite.sql.parser.SqlParserPos;
-import org.apache.calcite.util.ImmutableNullableList;
-
-import java.util.List;
-import java.util.Objects;
 
 /**
- * Parse tree for {@code CREATE TABLE} statement.
+ * Parse tree for {@code CREATE TABLE} statement, with extensions for particular
+ * SQL dialects supported by Babel.
  */
-public class SqlCreateTable extends SqlCreate {
-  public final SqlIdentifier name;
-  public final SqlNodeList columnList;
-  public final SqlNode query;
-
-  private static final SqlOperator OPERATOR =
-      new SqlSpecialOperator("CREATE TABLE", SqlKind.CREATE_TABLE);
-
-  /** Creates a SqlCreateTable. */
-  SqlCreateTable(SqlParserPos pos, boolean replace, boolean ifNotExists,
-      SqlIdentifier name, SqlNodeList columnList, SqlNode query) {
-    super(OPERATOR, pos, replace, ifNotExists);
-    this.name = Objects.requireNonNull(name);
-    this.columnList = columnList; // may be null
-    this.query = query; // for "CREATE TABLE ... AS query"; may be null
-  }
+public class SqlBabelCreateTable extends SqlCreateTable {
+  private final TableCollectionType tableCollectionType;
+  // CHECKSTYLE: IGNORE 2; can't use 'volatile' because it is a Java keyword
+  // but checkstyle does not like trailing '_'.
+  private final boolean volatile_;
 
-  public List<SqlNode> getOperandList() {
-    return ImmutableNullableList.of(name, columnList, query);
+  /** Creates a SqlBabelCreateTable. */
+  public SqlBabelCreateTable(SqlParserPos pos, boolean replace,
+      TableCollectionType tableCollectionType, boolean volatile_,
+      boolean ifNotExists, SqlIdentifier name, SqlNodeList columnList,
+      SqlNode query) {
+    super(pos, replace, ifNotExists, name, columnList, query);
+    this.tableCollectionType = tableCollectionType;
+    this.volatile_ = volatile_;
   }
 
   @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
     writer.keyword("CREATE");
+    switch (tableCollectionType) {
+    case SET:
+      writer.keyword("SET");
+      break;
+    case MULTISET:
+      writer.keyword("MULTISET");
+      break;
+    default:
+      break;
+    }
+    if (volatile_) {
+      writer.keyword("VOLATILE");
+    }
     writer.keyword("TABLE");
     if (ifNotExists) {
       writer.keyword("IF NOT EXISTS");
diff --git a/babel/src/main/java/org/apache/calcite/sql/babel/TableCollectionType.java b/babel/src/main/java/org/apache/calcite/sql/babel/TableCollectionType.java
new file mode 100644
index 0000000..df8b761
--- /dev/null
+++ b/babel/src/main/java/org/apache/calcite/sql/babel/TableCollectionType.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql.babel;
+
+/**
+ * Enumerates the collection type of a table: {@code MULTISET} allows duplicates
+ * and {@code SET} does not.
+ *
+ * <p>This feature is supported in Teradata, which originally required rows in a
+ * table to be unique, and later added the {@code MULTISET} keyword to
+ * its {@code CREATE TABLE} command to allow the duplicate rows.
+ *
+ * <p>In other databases and in the SQL standard, {@code MULTISET} is the only
+ * supported option, so there is no explicit syntax.
+ */
+public enum TableCollectionType {
+  /**
+   * Table collection type is not specified.
+   *
+   * <p>Defaults to {@code MULTISET} in ANSI mode,
+   * and {@code SET} in Teradata mode.
+   */
+  UNSPECIFIED,
+
+  /**
+   * Duplicate rows are not permitted.
+   */
+  SET,
+
+  /**
+   * Duplicate rows are permitted, in compliance with the ANSI SQL:2011 standard.
+   */
+  MULTISET,
+}
diff --git a/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java b/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
index cbd8277..b5d185f 100644
--- a/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
+++ b/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
@@ -255,4 +255,30 @@ class BabelParserTest extends SqlParserTest {
         + "FROM (VALUES (ROW(1, 2))) AS `TBL` (`X`, `Y`)";
     sql(sql).ok(expected);
   }
+
+  @Test void testCreateTableWithNoCollectionTypeSpecified() {
+    final String sql = "create table foo (bar integer not null, baz varchar(30))";
+    final String expected = "CREATE TABLE `FOO` (`BAR` INTEGER NOT NULL, `BAZ` VARCHAR(30))";
+    sql(sql).ok(expected);
+  }
+
+  @Test void testCreateSetTable() {
+    final String sql = "create set table foo (bar int not null, baz varchar(30))";
+    final String expected = "CREATE SET TABLE `FOO` (`BAR` INTEGER NOT NULL, `BAZ` VARCHAR(30))";
+    sql(sql).ok(expected);
+  }
+
+  @Test void testCreateMultisetTable() {
+    final String sql = "create multiset table foo (bar int not null, baz varchar(30))";
+    final String expected = "CREATE MULTISET TABLE `FOO` "
+        + "(`BAR` INTEGER NOT NULL, `BAZ` VARCHAR(30))";
+    sql(sql).ok(expected);
+  }
+
+  @Test void testCreateVolatileTable() {
+    final String sql = "create volatile table foo (bar int not null, baz varchar(30))";
+    final String expected = "CREATE VOLATILE TABLE `FOO` "
+        + "(`BAR` INTEGER NOT NULL, `BAZ` VARCHAR(30))";
+    sql(sql).ok(expected);
+  }
 }
diff --git a/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java b/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
index 0773004..6e03318 100644
--- a/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
@@ -42,7 +42,7 @@ public class SqlCreateTable extends SqlCreate {
       new SqlSpecialOperator("CREATE TABLE", SqlKind.CREATE_TABLE);
 
   /** Creates a SqlCreateTable. */
-  SqlCreateTable(SqlParserPos pos, boolean replace, boolean ifNotExists,
+  protected SqlCreateTable(SqlParserPos pos, boolean replace, boolean ifNotExists,
       SqlIdentifier name, SqlNodeList columnList, SqlNode query) {
     super(OPERATOR, pos, replace, ifNotExists);
     this.name = Objects.requireNonNull(name);