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

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


The following commit(s) were added to refs/heads/main by this push:
     new 23b7931c3e [CALCITE-6022] Support "CREATE TABLE ... LIKE" DDL in 
server module
23b7931c3e is described below

commit 23b7931c3e516bdb6cfedda956213f7fe06c6b24
Author: macroguo <macro...@tencent.com>
AuthorDate: Tue Sep 26 14:07:23 2023 +0800

    [CALCITE-6022] Support "CREATE TABLE ... LIKE" DDL in server module
---
 .../main/java/org/apache/calcite/sql/SqlKind.java  |   6 +-
 .../apache/calcite/sql/ddl/SqlCreateTableLike.java | 121 ++++++++++++++++
 .../org/apache/calcite/sql/ddl/SqlDdlNodes.java    |   8 ++
 server/src/main/codegen/config.fmpp                |   1 +
 server/src/main/codegen/includes/parserImpls.ftl   |  67 ++++++++-
 .../apache/calcite/server/ServerDdlExecutor.java   |  99 +++++++++++++
 .../org/apache/calcite/test/ServerParserTest.java  |  30 ++++
 .../java/org/apache/calcite/test/ServerTest.java   | 160 +++++++++++++++++++++
 server/src/test/resources/sql/table.iq             | 137 ++++++++++++++++++
 site/_docs/reference.md                            |   8 ++
 10 files changed, 632 insertions(+), 5 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java 
b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
index b2b5556c5e..7688cae915 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -1161,6 +1161,9 @@ public enum SqlKind {
   /** {@code CREATE TABLE} DDL statement. */
   CREATE_TABLE,
 
+  /** {@code CREATE TABLE LIKE} DDL statement. */
+  CREATE_TABLE_LIKE,
+
   /** {@code ALTER TABLE} DDL statement. */
   ALTER_TABLE,
 
@@ -1281,7 +1284,8 @@ public enum SqlKind {
   public static final EnumSet<SqlKind> DDL =
       EnumSet.of(COMMIT, ROLLBACK, ALTER_SESSION,
           CREATE_SCHEMA, CREATE_FOREIGN_SCHEMA, DROP_SCHEMA,
-          CREATE_TABLE, ALTER_TABLE, DROP_TABLE, TRUNCATE_TABLE,
+          CREATE_TABLE, CREATE_TABLE_LIKE,
+          ALTER_TABLE, DROP_TABLE, TRUNCATE_TABLE,
           CREATE_FUNCTION, DROP_FUNCTION,
           CREATE_VIEW, ALTER_VIEW, DROP_VIEW,
           CREATE_MATERIALIZED_VIEW, ALTER_MATERIALIZED_VIEW,
diff --git 
a/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTableLike.java 
b/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTableLike.java
new file mode 100644
index 0000000000..c291de2fe3
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTableLike.java
@@ -0,0 +1,121 @@
+/*
+ * 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.ddl;
+
+import org.apache.calcite.sql.SqlCreate;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
+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.Symbolizable;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.util.ImmutableNullableList;
+
+import com.google.common.base.Preconditions;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Parse tree for {@code CREATE TABLE LIKE} statement.
+ */
+public class SqlCreateTableLike extends SqlCreate {
+  private static final SqlOperator OPERATOR =
+      new SqlSpecialOperator("CREATE TABLE LIKE", SqlKind.CREATE_TABLE_LIKE);
+
+  /**
+   * The LikeOption specify which additional properties of the original table 
to copy.
+   */
+  public enum LikeOption implements Symbolizable {
+    ALL,
+    DEFAULTS,
+    GENERATED
+  }
+
+  public final SqlIdentifier name;
+  public final SqlIdentifier sourceTable;
+  public final SqlNodeList includingOptions;
+  public final SqlNodeList excludingOptions;
+
+
+  public SqlCreateTableLike(SqlParserPos pos, boolean replace, boolean 
ifNotExists,
+      SqlIdentifier name, SqlIdentifier sourceTable,
+      SqlNodeList includingOptions, SqlNodeList excludingOptions) {
+    super(OPERATOR, pos, replace, ifNotExists);
+    this.name = name;
+    this.sourceTable = sourceTable;
+    this.includingOptions = includingOptions;
+    this.excludingOptions = excludingOptions;
+
+    // validate like options
+    if (includingOptions.contains(LikeOption.ALL.symbol(SqlParserPos.ZERO))) {
+      Preconditions.checkArgument(
+          includingOptions.size() == 1 && excludingOptions.isEmpty(),
+          "ALL cannot be used with other options");
+    } else if 
(excludingOptions.contains(LikeOption.ALL.symbol(SqlParserPos.ZERO))) {
+      Preconditions.checkArgument(
+          excludingOptions.size() == 1 && includingOptions.isEmpty(),
+          "ALL cannot be used with other options");
+    }
+
+    includingOptions.forEach(option -> {
+      Preconditions.checkArgument(
+          !excludingOptions.contains(option),
+          "Cannot include and exclude option %s at same time", 
option.toString());
+    });
+  }
+
+  @Override public List<SqlNode> getOperandList() {
+    return ImmutableNullableList.of(name, sourceTable, includingOptions, 
excludingOptions);
+  }
+
+  public Set<LikeOption> options() {
+    return includingOptions.stream()
+        .map(c -> ((SqlLiteral) c).symbolValue(LikeOption.class))
+        .collect(Collectors.toSet());
+  }
+
+  @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) 
{
+    writer.keyword("CREATE");
+    writer.keyword("TABLE");
+    if (ifNotExists) {
+      writer.keyword("IF NOT EXISTS");
+    }
+    name.unparse(writer, leftPrec, rightPrec);
+    writer.keyword("LIKE");
+    sourceTable.unparse(writer, leftPrec, rightPrec);
+    for (SqlNode c : new HashSet<>(includingOptions)) {
+      LikeOption likeOption = ((SqlLiteral) c).getValueAs(LikeOption.class);
+      writer.newlineAndIndent();
+      writer.keyword("INCLUDING");
+      writer.keyword(likeOption.name());
+    }
+
+    for (SqlNode c : new HashSet<>(excludingOptions)) {
+      LikeOption likeOption = ((SqlLiteral) c).getValueAs(LikeOption.class);
+      writer.newlineAndIndent();
+      writer.keyword("EXCLUDING");
+      writer.keyword(likeOption.name());
+    }
+  }
+}
diff --git a/core/src/main/java/org/apache/calcite/sql/ddl/SqlDdlNodes.java 
b/core/src/main/java/org/apache/calcite/sql/ddl/SqlDdlNodes.java
index acc2fd6d3b..46ed8ed080 100644
--- a/core/src/main/java/org/apache/calcite/sql/ddl/SqlDdlNodes.java
+++ b/core/src/main/java/org/apache/calcite/sql/ddl/SqlDdlNodes.java
@@ -61,6 +61,14 @@ public class SqlDdlNodes {
         query);
   }
 
+  /** Creates a CREATE TABLE LIKE. */
+  public static SqlCreateTableLike createTableLike(SqlParserPos pos, boolean 
replace,
+      boolean ifNotExists, SqlIdentifier name, SqlIdentifier sourceTable,
+      SqlNodeList including, SqlNodeList excluding) {
+    return new SqlCreateTableLike(pos, replace, ifNotExists, name,
+        sourceTable, including, excluding);
+  }
+
   /** Creates a CREATE VIEW. */
   public static SqlCreateView createView(SqlParserPos pos, boolean replace,
       SqlIdentifier name, SqlNodeList columnList, SqlNode query) {
diff --git a/server/src/main/codegen/config.fmpp 
b/server/src/main/codegen/config.fmpp
index 731d569cb2..e5c3eaf4d2 100644
--- a/server/src/main/codegen/config.fmpp
+++ b/server/src/main/codegen/config.fmpp
@@ -30,6 +30,7 @@ data: {
       "org.apache.calcite.sql.SqlCreate"
       "org.apache.calcite.sql.SqlDrop"
       "org.apache.calcite.sql.SqlTruncate"
+      "org.apache.calcite.sql.ddl.SqlCreateTableLike"
       "org.apache.calcite.sql.ddl.SqlDdlNodes"
     ]
 
diff --git a/server/src/main/codegen/includes/parserImpls.ftl 
b/server/src/main/codegen/includes/parserImpls.ftl
index 00e1f386e9..8449b9d4b6 100644
--- a/server/src/main/codegen/includes/parserImpls.ftl
+++ b/server/src/main/codegen/includes/parserImpls.ftl
@@ -247,14 +247,73 @@ SqlCreate SqlCreateTable(Span s, boolean replace) :
     final SqlIdentifier id;
     SqlNodeList tableElementList = null;
     SqlNode query = null;
+
+    SqlCreate createTableLike = null;
 }
 {
     <TABLE> ifNotExists = IfNotExistsOpt() id = CompoundIdentifier()
-    [ tableElementList = TableElementList() ]
-    [ <AS> query = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) ]
+    (
+        <LIKE> createTableLike = SqlCreateTableLike(s, replace, ifNotExists, 
id) {
+            return createTableLike;
+        }
+    |
+        [ tableElementList = TableElementList() ]
+        [ <AS> query = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) ]
+        {
+            return SqlDdlNodes.createTable(s.end(this), replace, ifNotExists, 
id, tableElementList, query);
+        }
+    )
+}
+
+SqlCreate SqlCreateTableLike(Span s, boolean replace, boolean ifNotExists, 
SqlIdentifier id) :
+{
+    final SqlIdentifier sourceTable;
+    final boolean likeOptions;
+    final SqlNodeList including = new SqlNodeList(getPos());
+    final SqlNodeList excluding = new SqlNodeList(getPos());
+}
+{
+    sourceTable = CompoundIdentifier()
+    [ LikeOptions(including, excluding) ]
+    {
+        return SqlDdlNodes.createTableLike(s.end(this), replace, ifNotExists, 
id, sourceTable, including, excluding);
+    }
+}
+
+void LikeOptions(SqlNodeList including, SqlNodeList excluding) :
+{
+}
+{
+    LikeOption(including, excluding)
+    (
+        LikeOption(including, excluding)
+    )*
+}
+
+void LikeOption(SqlNodeList includingOptions, SqlNodeList excludingOptions) :
+{
+    boolean including = false;
+    SqlCreateTableLike.LikeOption option;
+}
+{
+    (
+        <INCLUDING> { including = true; }
+    |
+        <EXCLUDING> { including = false; }
+    )
+    (
+        <ALL> { option = SqlCreateTableLike.LikeOption.ALL; }
+    |
+        <DEFAULTS> { option = SqlCreateTableLike.LikeOption.DEFAULTS; }
+    |
+        <GENERATED> { option = SqlCreateTableLike.LikeOption.GENERATED; }
+    )
     {
-        return SqlDdlNodes.createTable(s.end(this), replace, ifNotExists, id,
-            tableElementList, query);
+        if (including) {
+            includingOptions.add(option.symbol(getPos()));
+        } else {
+            excludingOptions.add(option.symbol(getPos()));
+        }
     }
 }
 
diff --git 
a/server/src/main/java/org/apache/calcite/server/ServerDdlExecutor.java 
b/server/src/main/java/org/apache/calcite/server/ServerDdlExecutor.java
index d4dcf61c67..a0808c9f63 100644
--- a/server/src/main/java/org/apache/calcite/server/ServerDdlExecutor.java
+++ b/server/src/main/java/org/apache/calcite/server/ServerDdlExecutor.java
@@ -61,6 +61,7 @@ import org.apache.calcite.sql.ddl.SqlCreateFunction;
 import org.apache.calcite.sql.ddl.SqlCreateMaterializedView;
 import org.apache.calcite.sql.ddl.SqlCreateSchema;
 import org.apache.calcite.sql.ddl.SqlCreateTable;
+import org.apache.calcite.sql.ddl.SqlCreateTableLike;
 import org.apache.calcite.sql.ddl.SqlCreateType;
 import org.apache.calcite.sql.ddl.SqlCreateView;
 import org.apache.calcite.sql.ddl.SqlDropObject;
@@ -102,6 +103,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 import static org.apache.calcite.util.Static.RESOURCE;
 
@@ -559,6 +561,58 @@ public class ServerDdlExecutor extends DdlExecutorImpl {
     }
   }
 
+  /** Executes a {@code CREATE TABLE LIKE} command. */
+  public void execute(SqlCreateTableLike create,
+      CalcitePrepare.Context context) {
+    final Pair<CalciteSchema, String> pair = schema(context, true, 
create.name);
+    if (pair.left.plus().getTable(pair.right) != null) {
+      // Table exists.
+      if (create.ifNotExists) {
+        return;
+      }
+      if (!create.getReplace()) {
+        // They did not specify IF NOT EXISTS, so give error.
+        throw SqlUtil.newContextException(create.name.getParserPosition(),
+            RESOURCE.tableExists(pair.right));
+      }
+    }
+
+    final Pair<CalciteSchema, String> sourceTablePair =
+        schema(context, true, create.sourceTable);
+    final Table table = sourceTablePair.left
+        .getTable(sourceTablePair.right, context.config().caseSensitive())
+        .getTable();
+
+    InitializerExpressionFactory ief = 
NullInitializerExpressionFactory.INSTANCE;
+    if (table instanceof Wrapper) {
+      final InitializerExpressionFactory sourceIef =
+          ((Wrapper) table).unwrap(InitializerExpressionFactory.class);
+      if (sourceIef != null) {
+        final Set<SqlCreateTableLike.LikeOption> optionSet = create.options();
+        final boolean includingGenerated =
+            optionSet.contains(SqlCreateTableLike.LikeOption.GENERATED)
+                || optionSet.contains(SqlCreateTableLike.LikeOption.ALL);
+        final boolean includingDefaults =
+            optionSet.contains(SqlCreateTableLike.LikeOption.DEFAULTS)
+                || optionSet.contains(SqlCreateTableLike.LikeOption.ALL);
+
+        // initializes columns based on the source table 
InitializerExpressionFactory
+        // and like options.
+        ief =
+            new CopiedTableInitializerExpressionFactory(
+                includingGenerated, includingDefaults, sourceIef);
+      }
+    }
+
+    final JavaTypeFactory typeFactory = context.getTypeFactory();
+    final RelDataType rowType = table.getRowType(typeFactory);
+    // Table does not exist. Create it.
+    pair.left.add(pair.right,
+        new MutableArrayTable(pair.right,
+            RelDataTypeImpl.proto(rowType),
+            RelDataTypeImpl.proto(rowType), ief));
+  }
+
   /** Executes a {@code CREATE TYPE} command. */
   public void execute(SqlCreateType create,
       CalcitePrepare.Context context) {
@@ -606,6 +660,51 @@ public class ServerDdlExecutor extends DdlExecutorImpl {
     schemaPlus.add(pair.right, viewTableMacro);
   }
 
+  /**
+   * Initializes columns based on the source {@link 
InitializerExpressionFactory}
+   * and like options.
+   */
+  private static class CopiedTableInitializerExpressionFactory
+      extends NullInitializerExpressionFactory {
+
+    private final boolean includingGenerated;
+    private final boolean includingDefaults;
+    private final InitializerExpressionFactory sourceIef;
+
+    CopiedTableInitializerExpressionFactory(
+        boolean includingGenerated,
+        boolean includingDefaults,
+        InitializerExpressionFactory sourceIef) {
+      this.includingGenerated = includingGenerated;
+      this.includingDefaults = includingDefaults;
+      this.sourceIef = sourceIef;
+    }
+
+    @Override public ColumnStrategy generationStrategy(
+        RelOptTable table, int iColumn) {
+      final ColumnStrategy sourceStrategy = 
sourceIef.generationStrategy(table, iColumn);
+      if (includingGenerated
+          && (sourceStrategy == ColumnStrategy.STORED
+          || sourceStrategy == ColumnStrategy.VIRTUAL)) {
+        return sourceStrategy;
+      }
+      if (includingDefaults && sourceStrategy == ColumnStrategy.DEFAULT) {
+        return ColumnStrategy.DEFAULT;
+      }
+
+      return super.generationStrategy(table, iColumn);
+    }
+
+    @Override public RexNode newColumnDefaultValue(
+        RelOptTable table, int iColumn, InitializerContext context) {
+      if (includingDefaults || includingGenerated) {
+        return sourceIef.newColumnDefaultValue(table, iColumn, context);
+      } else {
+        return super.newColumnDefaultValue(table, iColumn, context);
+      }
+    }
+  }
+
   /** Column definition. */
   private static class ColumnDef {
     final SqlNode expr;
diff --git a/server/src/test/java/org/apache/calcite/test/ServerParserTest.java 
b/server/src/test/java/org/apache/calcite/test/ServerParserTest.java
index d3ed84549a..aeb1c07525 100644
--- a/server/src/test/java/org/apache/calcite/test/ServerParserTest.java
+++ b/server/src/test/java/org/apache/calcite/test/ServerParserTest.java
@@ -181,6 +181,36 @@ class ServerParserTest extends SqlParserTest {
     sql(sql).ok(expected);
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6022";>[CALCITE-6022]
+   * Support "CREATE TABLE ... LIKE" DDL in server module</a>. */
+  @Test void testCreateTableLike() {
+    final String sql = "create table x like y";
+    final String expected = "CREATE TABLE `X` LIKE `Y`";
+    sql(sql).ok(expected);
+  }
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6022";>[CALCITE-6022]
+   * Support "CREATE TABLE ... LIKE" DDL in server module</a>. */
+  @Test void testCreateTableLikeWithOptions() {
+    sql("create table x like y including all")
+        .ok("CREATE TABLE `X` LIKE `Y`\n"
+            + "INCLUDING ALL");
+
+    sql("create table s.x like s.y excluding defaults including generated")
+        .ok("CREATE TABLE `S`.`X` LIKE `S`.`Y`\n"
+            + "INCLUDING GENERATED\n"
+            + "EXCLUDING DEFAULTS");
+
+    sql("create table x like y excluding defaults including all")
+        .fails("ALL cannot be used with other options");
+
+    sql("create table x like y including defaults excluding defaults")
+        .fails("Cannot include and exclude option DEFAULTS at same time");
+
+  }
+
   @Test void testCreateView() {
     final String sql = "create or replace view v as\n"
         + "select * from (values (1, '2'), (3, '45')) as t (x, y)";
diff --git a/server/src/test/java/org/apache/calcite/test/ServerTest.java 
b/server/src/test/java/org/apache/calcite/test/ServerTest.java
index 3c9180970c..d48b1940de 100644
--- a/server/src/test/java/org/apache/calcite/test/ServerTest.java
+++ b/server/src/test/java/org/apache/calcite/test/ServerTest.java
@@ -29,6 +29,7 @@ import org.apache.calcite.sql.ddl.SqlCreateFunction;
 import org.apache.calcite.sql.ddl.SqlCreateMaterializedView;
 import org.apache.calcite.sql.ddl.SqlCreateSchema;
 import org.apache.calcite.sql.ddl.SqlCreateTable;
+import org.apache.calcite.sql.ddl.SqlCreateTableLike;
 import org.apache.calcite.sql.ddl.SqlCreateType;
 import org.apache.calcite.sql.ddl.SqlCreateView;
 import org.apache.calcite.sql.ddl.SqlDropFunction;
@@ -95,6 +96,7 @@ class ServerTest {
     executor.execute((SqlNode) o, context);
     executor.execute((SqlCreateFunction) o, context);
     executor.execute((SqlCreateTable) o, context);
+    executor.execute((SqlCreateTableLike) o, context);
     executor.execute((SqlCreateSchema) o, context);
     executor.execute((SqlCreateMaterializedView) o, context);
     executor.execute((SqlCreateView) o, context);
@@ -277,6 +279,164 @@ class ServerTest {
     }
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6022";>[CALCITE-6022]
+   * Support "CREATE TABLE ... LIKE" DDL in server module</a>. */
+  @Test void testCreateTableLike() throws Exception {
+    try (Connection c = connect();
+         Statement s = c.createStatement()) {
+      s.execute("create table t (i int not null)");
+      s.execute("create table t2 like t");
+      int x = s.executeUpdate("insert into t2 values 1");
+      assertThat(x, is(1));
+      x = s.executeUpdate("insert into t2 values 3");
+      assertThat(x, is(1));
+      try (ResultSet r = s.executeQuery("select sum(i) from t2")) {
+        assertThat(r.next(), is(true));
+        assertThat(r.getInt(1), is(4));
+        assertThat(r.next(), is(false));
+      }
+    }
+  }
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6022";>[CALCITE-6022]
+   * Support "CREATE TABLE ... LIKE" DDL in server module</a>. */
+  @Test void testCreateTableLikeWithStoredGeneratedColumn() throws Exception {
+    try (Connection c = connect();
+         Statement s = c.createStatement()) {
+      s.execute("create table t (\n"
+          + " h int not null,\n"
+          + " i int,\n"
+          + " j int as (i + 1) stored,\n"
+          + " k int default -1)\n");
+      s.execute("create table t2 like t including defaults including 
generated");
+
+      int x = s.executeUpdate("insert into t2 (h, i) values (3, 4)");
+      assertThat(x, is(1));
+
+      final String sql1 = "explain plan for\n"
+          + "insert into t2 (h, i) values (3, 4)";
+      try (ResultSet r = s.executeQuery(sql1)) {
+        assertThat(r.next(), is(true));
+        final String plan = ""
+            + "EnumerableTableModify(table=[[T2]], operation=[INSERT], 
flattened=[false])\n"
+            + "  EnumerableCalc(expr#0..1=[{inputs}], expr#2=[1], 
expr#3=[+($t1, $t2)], expr#4=[-1], proj#0..1=[{exprs}], J=[$t3], K=[$t4])\n"
+            + "    EnumerableValues(tuples=[[{ 3, 4 }]])\n";
+        assertThat(r.getString(1), isLinux(plan));
+        assertThat(r.next(), is(false));
+      }
+
+      try (ResultSet r = s.executeQuery("select * from t2")) {
+        assertThat(r.next(), is(true));
+        assertThat(r.getInt("H"), is(3));
+        assertThat(r.wasNull(), is(false));
+        assertThat(r.getInt("I"), is(4));
+        assertThat(r.getInt("J"), is(5)); // j = i + 1
+        assertThat(r.getInt("K"), is(-1)); // k = -1 (default)
+        assertThat(r.next(), is(false));
+      }
+
+      SQLException e =
+          assertThrows(
+              SQLException.class, () -> s.executeUpdate("insert into t2 values 
(3, 4, 5, 6)"));
+      assertThat(e.getMessage(), containsString("Cannot INSERT into generated 
column 'J'"));
+    }
+  }
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6022";>[CALCITE-6022]
+   * Support "CREATE TABLE ... LIKE" DDL in server module</a>. */
+  @Test void testCreateTableLikeWithVirtualGeneratedColumn() throws Exception {
+    try (Connection c = connect();
+         Statement s = c.createStatement()) {
+      s.execute("create table t (\n"
+          + " h int not null,\n"
+          + " i int,\n"
+          + " j int as (i + 1) virtual)\n");
+      s.execute("create table t2 like t including defaults including 
generated");
+
+      int x = s.executeUpdate("insert into t2 (h, i) values (3, 4)");
+      assertThat(x, is(1));
+
+      final String sql1 = "explain plan for\n"
+          + "insert into t (h, i) values (3, 4)";
+      try (ResultSet r = s.executeQuery(sql1)) {
+        final String sql2 = "explain plan for\n"
+            + "insert into t2 (h, i) values (3, 4)";
+        assertThat(r.next(), is(true));
+        final String plan = r.getString(1);
+        assertThat(r.next(), is(false));
+
+        ResultSet r2 = s.executeQuery(sql2);
+        assertThat(r2.next(), is(true));
+        assertEquals(plan, r2.getString(1).replace("T2", "T"));
+        assertThat(r2.next(), is(false));
+      }
+
+      try (ResultSet r = s.executeQuery("select * from t2")) {
+        assertThat(r.next(), is(true));
+        assertThat(r.getInt("H"), is(3));
+        assertThat(r.wasNull(), is(false));
+        assertThat(r.getInt("I"), is(4));
+        assertThat(r.getInt("J"), is(5)); // j = i + 1
+        assertThat(r.next(), is(false));
+      }
+
+      SQLException e =
+          assertThrows(
+              SQLException.class, () -> s.executeUpdate("insert into t2 values 
(3, 4, 5)"));
+      assertThat(e.getMessage(), containsString("Cannot INSERT into generated 
column 'J'"));
+    }
+  }
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6022";>[CALCITE-6022]
+   * Support "CREATE TABLE ... LIKE" DDL in server module</a>. */
+  @Test void testCreateTableLikeWithoutLikeOptions() throws Exception {
+    try (Connection c = connect();
+         Statement s = c.createStatement()) {
+      s.execute("create table t (\n"
+          + " h int not null,\n"
+          + " i int,\n"
+          + " j int as (i + 1) stored,\n"
+          + " k int default -1)");
+      // In table t2, only copy the column and type information from t,
+      // excluding generated expression and default expression
+      s.execute("create table t2 like t");
+
+      int x = s.executeUpdate("insert into t2 (h, i) values (3, 4)");
+      assertThat(x, is(1));
+
+      final String sql1 = "explain plan for\n"
+          + "insert into t2 (h, i) values (3, 4)";
+      try (ResultSet r = s.executeQuery(sql1)) {
+        assertThat(r.next(), is(true));
+        final String plan = ""
+            + "EnumerableTableModify(table=[[T2]], operation=[INSERT], 
flattened=[false])\n"
+            + "  EnumerableCalc(expr#0..1=[{inputs}], expr#2=[null:INTEGER], 
proj#0..2=[{exprs}], K=[$t2])\n"
+            + "    EnumerableValues(tuples=[[{ 3, 4 }]])\n";
+        assertThat(r.getString(1), isLinux(plan));
+        assertThat(r.next(), is(false));
+      }
+
+      try (ResultSet r = s.executeQuery("select * from t2")) {
+        assertThat(r.next(), is(true));
+        assertThat(r.getInt("H"), is(3));
+        assertThat(r.wasNull(), is(false));
+        assertThat(r.getInt("I"), is(4));
+        assertThat(r.getInt("J"), is(0)); // excluding generated column
+        assertThat(r.wasNull(), is(true));
+        assertThat(r.getInt("K"), is(0)); // excluding default column
+        assertThat(r.wasNull(), is(true));
+        assertThat(r.next(), is(false));
+      }
+
+      x = s.executeUpdate("insert into t2 values (3, 4, 5, 6)");
+      assertThat(x, is(1));
+    }
+  }
+
   @Test void testTruncateTable() throws Exception {
     try (Connection c = connect();
         Statement s = c.createStatement()) {
diff --git a/server/src/test/resources/sql/table.iq 
b/server/src/test/resources/sql/table.iq
index b696b2cf92..8726b2521d 100755
--- a/server/src/test/resources/sql/table.iq
+++ b/server/src/test/resources/sql/table.iq
@@ -50,6 +50,25 @@ select * from t;
 
 !ok
 
+create table t2 like t;
+(0 rows modified)
+
+!update
+
+select * from t2;
+I INTEGER(10)
+J INTEGER(10) NOT NULL
+!type
+
+select * from t2;
++---+---+
+| I | J |
++---+---+
++---+---+
+(0 rows)
+
+!ok
+
 
 truncate table t;
 (0 rows modified)
@@ -97,6 +116,42 @@ select * from t;
 
 !ok
 
+drop table t2;
+(0 rows modified)
+
+!update
+
+create table t2 like t including defaults;
+(0 rows modified)
+
+!update
+
+insert into t2 values (1, 2);
+(1 row modified)
+
+!update
+
+insert into t2 (i) values (3);
+(1 row modified)
+
+!update
+
+select * from t2;
++---+---+
+| I | J |
++---+---+
+| 1 | 2 |
+| 3 | 5 |
++---+---+
+(2 rows)
+
+!ok
+
+drop table t2;
+(0 rows modified)
+
+!update
+
 drop table t;
 (0 rows modified)
 
@@ -148,6 +203,88 @@ EnumerableCalc(expr#0..1=[{inputs}], expr#2=[+($t0, $t1)], 
expr#3=[2], expr#4=[+
   EnumerableTableScan(table=[[T]])
 !plan
 
+# Create a table with a STORED column
+create table t2 like t including all;
+(0 rows modified)
+
+!update
+
+insert into t2 values (1, 2, 3);
+Cannot INSERT into generated column 'J'
+!error
+
+insert into t2 (i, j) values (1, 2);
+Cannot INSERT into generated column 'J'
+!error
+
+insert into t2 (i, k) values (1, 3);
+(1 row modified)
+
+!update
+EnumerableTableModify(table=[[T2]], operation=[INSERT], flattened=[false])
+  EnumerableValues(tuples=[[{ 1, 3 }]])
+!plan
+
+insert into t2 (k, i) values (5, 2);
+(1 row modified)
+
+!update
+EnumerableTableModify(table=[[T2]], operation=[INSERT], flattened=[false])
+  EnumerableCalc(expr#0..1=[{inputs}], I=[$t1], K=[$t0])
+    EnumerableValues(tuples=[[{ 5, 2 }]])
+!plan
+
+select * from t2;
++---+---+---+
+| I | J | K |
++---+---+---+
+| 1 | 6 | 3 |
+| 2 | 9 | 5 |
++---+---+---+
+(2 rows)
+
+!ok
+EnumerableCalc(expr#0..1=[{inputs}], expr#2=[+($t0, $t1)], expr#3=[2], 
expr#4=[+($t2, $t3)], I=[$t0], J=[$t4], K=[$t1])
+  EnumerableTableScan(table=[[T2]])
+!plan
+
+drop table t2;
+(0 rows modified)
+
+!update
+
+# Create a table excluding virtual columns
+create table t2 like t;
+(0 rows modified)
+
+!update
+
+select * from t2;
+I INTEGER(10)
+J INTEGER(10)
+K INTEGER(10)
+!type
+
+insert into t2 values (1, 2, 3);
+(1 row modified)
+
+!update
+
+select * from t2;
++---+---+---+
+| I | J | K |
++---+---+---+
+| 1 | 2 | 3 |
++---+---+---+
+(1 row)
+
+!ok
+
+drop table t2;
+(0 rows modified)
+
+!update
+
 drop table if exists t;
 (0 rows modified)
 
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index ee84aa1ece..b2a2483f88 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -3378,6 +3378,7 @@ ddlStatement:
       createSchemaStatement
   |   createForeignSchemaStatement
   |   createTableStatement
+  |   createTableLikeStatement
   |   createViewStatement
   |   createMaterializedViewStatement
   |   createTypeStatement
@@ -3409,6 +3410,13 @@ createTableStatement:
       [ '(' tableElement [, tableElement ]* ')' ]
       [ AS query ]
 
+createTableLikeStatement:
+      CREATE TABLE [ IF NOT EXISTS ] name LIKE sourceTable
+      [ likeOption [, likeOption ]* ]
+
+likeOption:
+      { INCLUDING | EXCLUDING } { DEFAULTS | GENERATED | ALL }
+
 createTypeStatement:
       CREATE [ OR REPLACE ] TYPE name AS
       {

Reply via email to