Skip to content

Commit

Permalink
[CALCITE-6022] Support "CREATE TABLE ... LIKE" DDL in server module
Browse files Browse the repository at this point in the history
  • Loading branch information
macroguo-ghy authored and JiajunBernoulli committed Oct 19, 2023
1 parent f996bc9 commit 23b7931
Show file tree
Hide file tree
Showing 10 changed files with 632 additions and 5 deletions.
6 changes: 5 additions & 1 deletion core/src/main/java/org/apache/calcite/sql/SqlKind.java
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -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,
Expand Down
121 changes: 121 additions & 0 deletions core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTableLike.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ public static SqlCreateTable createTable(SqlParserPos pos, boolean replace,
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) {
Expand Down
1 change: 1 addition & 0 deletions server/src/main/codegen/config.fmpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]

Expand Down
67 changes: 63 additions & 4 deletions server/src/main/codegen/includes/parserImpls.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
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;
Expand Down Expand Up @@ -102,6 +103,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import static org.apache.calcite.util.Static.RESOURCE;

Expand Down Expand Up @@ -559,6 +561,58 @@ public void execute(SqlCreateTable create,
}
}

/** 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) {
Expand Down Expand Up @@ -606,6 +660,51 @@ public void execute(SqlCreateView create,
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;
Expand Down
Loading

0 comments on commit 23b7931

Please sign in to comment.