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 7fafef95272e91f97db2a40189fde352720c16e4 Author: Julian Hyde <[email protected]> AuthorDate: Sun Oct 6 16:03:54 2019 -0700 Allow "CREATE TABLE ... AS SELECT ..." in Quidem tests --- core/src/test/codegen/includes/parserImpls.ftl | 14 +- .../parserextensiontesting/SqlCreateTable.java | 149 +++++++++++++++++---- core/src/test/resources/sql/blank.iq | 16 +++ 3 files changed, 150 insertions(+), 29 deletions(-) diff --git a/core/src/test/codegen/includes/parserImpls.ftl b/core/src/test/codegen/includes/parserImpls.ftl index 6863c52..be718ab 100644 --- a/core/src/test/codegen/includes/parserImpls.ftl +++ b/core/src/test/codegen/includes/parserImpls.ftl @@ -40,10 +40,20 @@ SqlCreate SqlCreateTable(Span s, boolean replace) : { final SqlIdentifier id; final SqlNodeList columnList; + final SqlNode query; } { - <TABLE> id = CompoundIdentifier() columnList = ExtendList() { - return new SqlCreateTable(s.end(columnList), id, columnList); + <TABLE> id = CompoundIdentifier() + ( + columnList = ExtendList() + | { columnList = null; } + ) + ( + <AS> query = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) + | { query = null; } + ) + { + return new SqlCreateTable(s.end(this), id, columnList, query); } } diff --git a/core/src/test/java/org/apache/calcite/sql/parser/parserextensiontesting/SqlCreateTable.java b/core/src/test/java/org/apache/calcite/sql/parser/parserextensiontesting/SqlCreateTable.java index c2c101f..9717af0 100644 --- a/core/src/test/java/org/apache/calcite/sql/parser/parserextensiontesting/SqlCreateTable.java +++ b/core/src/test/java/org/apache/calcite/sql/parser/parserextensiontesting/SqlCreateTable.java @@ -25,13 +25,17 @@ import org.apache.calcite.linq4j.Linq4j; import org.apache.calcite.linq4j.QueryProvider; import org.apache.calcite.linq4j.Queryable; import org.apache.calcite.linq4j.tree.Expression; +import org.apache.calcite.rel.RelRoot; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.rel.type.RelDataTypeImpl; import org.apache.calcite.rel.type.RelProtoDataType; import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.schema.Schemas; +import org.apache.calcite.schema.TranslatableTable; import org.apache.calcite.schema.impl.AbstractTableQueryable; +import org.apache.calcite.schema.impl.ViewTable; +import org.apache.calcite.schema.impl.ViewTableMacro; import org.apache.calcite.sql.SqlCreate; import org.apache.calcite.sql.SqlDataTypeSpec; import org.apache.calcite.sql.SqlExecutableStatement; @@ -41,19 +45,35 @@ 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.SqlUtil; import org.apache.calcite.sql.SqlWriter; +import org.apache.calcite.sql.dialect.CalciteSqlDialect; +import org.apache.calcite.sql.parser.SqlParseException; import org.apache.calcite.sql.parser.SqlParserPos; +import org.apache.calcite.sql.pretty.SqlPrettyWriter; import org.apache.calcite.sql.validate.SqlValidator; import org.apache.calcite.test.JdbcTest; +import org.apache.calcite.tools.FrameworkConfig; +import org.apache.calcite.tools.Frameworks; +import org.apache.calcite.tools.Planner; +import org.apache.calcite.tools.RelConversionException; +import org.apache.calcite.tools.ValidationException; +import org.apache.calcite.util.ImmutableNullableList; import org.apache.calcite.util.Pair; import org.apache.calcite.util.Util; import com.google.common.collect.ImmutableList; import java.lang.reflect.Type; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; + +import static org.apache.calcite.util.Static.RESOURCE; /** * Simple test example of a CREATE TABLE statement. @@ -62,64 +82,139 @@ public class SqlCreateTable extends SqlCreate implements SqlExecutableStatement { private final SqlIdentifier name; private final SqlNodeList columnList; + private final SqlNode query; private static final SqlOperator OPERATOR = new SqlSpecialOperator("CREATE TABLE", SqlKind.CREATE_TABLE); /** Creates a SqlCreateTable. */ public SqlCreateTable(SqlParserPos pos, SqlIdentifier name, - SqlNodeList columnList) { + SqlNodeList columnList, SqlNode query) { super(OPERATOR, pos, false, false); - this.name = name; - this.columnList = columnList; + this.name = Objects.requireNonNull(name); + this.columnList = columnList; // may be null + this.query = query; // for "CREATE TABLE ... AS query"; may be null } @Override public List<SqlNode> getOperandList() { - return ImmutableList.of(name, columnList); + return ImmutableNullableList.of(name, columnList, query); } @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) { writer.keyword("CREATE"); writer.keyword("TABLE"); name.unparse(writer, leftPrec, rightPrec); - SqlWriter.Frame frame = writer.startList("(", ")"); - for (Pair<SqlIdentifier, SqlDataTypeSpec> pair : nameTypes()) { - writer.sep(","); - pair.left.unparse(writer, leftPrec, rightPrec); // name - pair.right.unparse(writer, leftPrec, rightPrec); // type - if (Boolean.FALSE.equals(pair.right.getNullable())) { - writer.keyword("NOT NULL"); - } + if (columnList != null) { + SqlWriter.Frame frame = writer.startList("(", ")"); + nameTypes((name, typeSpec) -> { + writer.sep(","); + name.unparse(writer, leftPrec, rightPrec); + typeSpec.unparse(writer, leftPrec, rightPrec); + if (Boolean.FALSE.equals(typeSpec.getNullable())) { + writer.keyword("NOT NULL"); + } + }); + writer.endList(frame); + } + if (query != null) { + writer.keyword("AS"); + writer.newlineAndIndent(); + query.unparse(writer, 0, 0); } - writer.endList(frame); } - /** Creates a list of (name, type) pairs from {@link #columnList}, in which + /** Calls an action for each (name, type) pair from {@code columnList}, in which * they alternate. */ - private List<Pair<SqlIdentifier, SqlDataTypeSpec>> nameTypes() { + @SuppressWarnings({"unchecked"}) + private void nameTypes(BiConsumer<SqlIdentifier, SqlDataTypeSpec> consumer) { final List list = columnList.getList(); - //noinspection unchecked - return Pair.zip((List<SqlIdentifier>) Util.quotientList(list, 2, 0), - Util.quotientList((List<SqlDataTypeSpec>) list, 2, 1)); + Pair.forEach((List<SqlIdentifier>) Util.quotientList(list, 2, 0), + Util.quotientList((List<SqlDataTypeSpec>) list, 2, 1), consumer); } public void execute(CalcitePrepare.Context context) { - final List<String> path = context.getDefaultSchemaPath(); - CalciteSchema schema = context.getRootSchema(); - for (String p : path) { - schema = schema.getSubSchema(p, true); - } + final CalciteSchema schema = + Schemas.subSchema(context.getRootSchema(), + context.getDefaultSchemaPath()); final JavaTypeFactory typeFactory = context.getTypeFactory(); + final RelDataType queryRowType; + if (query != null) { + // A bit of a hack: pretend it's a view, to get its row type + final String sql = query.toSqlString(CalciteSqlDialect.DEFAULT).getSql(); + final ViewTableMacro viewTableMacro = + ViewTable.viewMacro(schema.plus(), sql, schema.path(null), + context.getObjectPath(), false); + final TranslatableTable x = viewTableMacro.apply(ImmutableList.of()); + queryRowType = x.getRowType(typeFactory); + + if (columnList != null + && queryRowType.getFieldCount() != columnList.size()) { + throw SqlUtil.newContextException(columnList.getParserPosition(), + RESOURCE.columnCountMismatch()); + } + } else { + queryRowType = null; + } final RelDataTypeFactory.Builder builder = typeFactory.builder(); - final SqlValidator validator = new ContextSqlValidator(context, false); - for (Pair<SqlIdentifier, SqlDataTypeSpec> pair : nameTypes()) { - builder.add(pair.left.getSimple(), - pair.right.deriveType(validator, true)); + if (columnList != null) { + final SqlValidator validator = new ContextSqlValidator(context, false); + nameTypes((name, typeSpec) -> + builder.add(name.getSimple(), typeSpec.deriveType(validator, true))); + } else { + if (queryRowType == null) { + // "CREATE TABLE t" is invalid; because there is no "AS query" we need + // a list of column names and types, "CREATE TABLE t (INT c)". + throw SqlUtil.newContextException(name.getParserPosition(), + RESOURCE.createTableRequiresColumnList()); + } + builder.addAll(queryRowType.getFieldList()); } final RelDataType rowType = builder.build(); schema.add(name.getSimple(), new MutableArrayTable(name.getSimple(), RelDataTypeImpl.proto(rowType))); + if (query != null) { + populate(name, query, context); + } + } + + /** Populates the table called {@code name} by executing {@code query}. */ + protected static void populate(SqlIdentifier name, SqlNode query, + CalcitePrepare.Context context) { + // Generate, prepare and execute an "INSERT INTO table query" statement. + // (It's a bit inefficient that we convert from SqlNode to SQL and back + // again.) + final FrameworkConfig config = Frameworks.newConfigBuilder() + .defaultSchema( + Objects.requireNonNull( + Schemas.subSchema(context.getRootSchema(), + context.getDefaultSchemaPath())).plus()) + .build(); + final Planner planner = Frameworks.getPlanner(config); + try { + final StringBuilder buf = new StringBuilder(); + final SqlPrettyWriter w = + new SqlPrettyWriter( + SqlPrettyWriter.config() + .withDialect(CalciteSqlDialect.DEFAULT) + .withAlwaysUseParentheses(false), + buf); + buf.append("INSERT INTO "); + name.unparse(w, 0, 0); + buf.append(" "); + query.unparse(w, 0, 0); + final String sql = buf.toString(); + final SqlNode query1 = planner.parse(sql); + final SqlNode query2 = planner.validate(query1); + final RelRoot r = planner.rel(query2); + final PreparedStatement prepare = context.getRelRunner().prepare(r.rel); + int rowCount = prepare.executeUpdate(); + Util.discard(rowCount); + prepare.close(); + } catch (SqlParseException | ValidationException + | RelConversionException | SQLException e) { + throw new RuntimeException(e); + } } /** Table backed by a Java list. */ diff --git a/core/src/test/resources/sql/blank.iq b/core/src/test/resources/sql/blank.iq index 763d58b..2d69aaf 100644 --- a/core/src/test/resources/sql/blank.iq +++ b/core/src/test/resources/sql/blank.iq @@ -41,6 +41,22 @@ select * from foo; !ok +create table bar as select i, i + j as k from foo; +(0 rows modified) + +!update + +select * from bar; ++---+---+ +| I | K | ++---+---+ +| 0 | 2 | +| 1 | 1 | ++---+---+ +(2 rows) + +!ok + # Correlated non-equi IN select * from foo as f where i in ( select j from foo where i > f.i);
