Repository: calcite Updated Branches: refs/heads/master dc69a4515 -> 3c40d866a
[CALCITE-2417] Fix ClassCastException in RelToSqlConverter with structs (Benoit Hanotte) Trying to convert the pysical plan of a select * query on a table with a nested struct throws ClassCastException as RexInputRef cannot be cast to RexCorrelVariable. This is due to the cast to RexCorrelVariable not being done on the referenced expression of RexInputRef accessible through RexFieldAccess.getReferenceExpr() Close apache/calcite#761 Project: http://git-wip-us.apache.org/repos/asf/calcite/repo Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/3c40d866 Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/3c40d866 Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/3c40d866 Branch: refs/heads/master Commit: 3c40d866a43f8a74f35a6b31fadf145c4d1d9f94 Parents: dc69a45 Author: Benoit Hanotte <[email protected]> Authored: Tue Jul 17 14:25:05 2018 +0200 Committer: Michael Mior <[email protected]> Committed: Wed Jul 25 13:23:09 2018 -0400 ---------------------------------------------------------------------- .../calcite/rel/rel2sql/SqlImplementor.java | 33 ++- .../rel2sql/RelToSqlConverterStructsTest.java | 206 +++++++++++++++++++ .../rel/rel2sql/RelToSqlConverterTest.java | 29 ++- 3 files changed, 254 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/calcite/blob/3c40d866/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java index 6508525..b26f29b 100644 --- a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java +++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java @@ -79,8 +79,10 @@ import com.google.common.collect.Iterables; import java.math.BigDecimal; import java.util.AbstractList; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -441,11 +443,32 @@ public abstract class SqlImplementor { return field(((RexInputRef) rex).getIndex()); case FIELD_ACCESS: - RexFieldAccess access = (RexFieldAccess) rex; - final RexCorrelVariable variable = - (RexCorrelVariable) access.getReferenceExpr(); - final Context aliasContext = correlTableMap.get(variable.id); - return aliasContext.field(access.getField().getIndex()); + final Deque<RexFieldAccess> accesses = new ArrayDeque<>(); + RexNode referencedExpr = rex; + while (referencedExpr.getKind() == SqlKind.FIELD_ACCESS) { + accesses.offerLast((RexFieldAccess) referencedExpr); + referencedExpr = ((RexFieldAccess) referencedExpr).getReferenceExpr(); + } + SqlIdentifier sqlIdentifier; + switch (referencedExpr.getKind()) { + case CORREL_VARIABLE: + final RexCorrelVariable variable = (RexCorrelVariable) referencedExpr; + final Context correlAliasContext = correlTableMap.get(variable.id); + final RexFieldAccess lastAccess = accesses.pollLast(); + assert lastAccess != null; + sqlIdentifier = (SqlIdentifier) correlAliasContext + .field(lastAccess.getField().getIndex()); + break; + default: + sqlIdentifier = (SqlIdentifier) toSql(program, referencedExpr); + } + + int nameIndex = sqlIdentifier.names.size(); + RexFieldAccess access; + while ((access = accesses.pollLast()) != null) { + sqlIdentifier = sqlIdentifier.add(nameIndex++, access.getField().getName(), POS); + } + return sqlIdentifier; case PATTERN_INPUT_REF: final RexPatternFieldRef ref = (RexPatternFieldRef) rex; http://git-wip-us.apache.org/repos/asf/calcite/blob/3c40d866/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterStructsTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterStructsTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterStructsTest.java new file mode 100644 index 0000000..aef7993 --- /dev/null +++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterStructsTest.java @@ -0,0 +1,206 @@ +/* + * 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.rel.rel2sql; + +import org.apache.calcite.config.CalciteConnectionConfig; +import org.apache.calcite.jdbc.CalciteSchema; +import org.apache.calcite.linq4j.tree.Expression; +import org.apache.calcite.rel.RelCollation; +import org.apache.calcite.rel.RelDistribution; +import org.apache.calcite.rel.RelReferentialConstraint; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.rel.type.RelProtoDataType; +import org.apache.calcite.schema.Function; +import org.apache.calcite.schema.Schema; +import org.apache.calcite.schema.SchemaPlus; +import org.apache.calcite.schema.SchemaVersion; +import org.apache.calcite.schema.Statistic; +import org.apache.calcite.schema.Table; +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.dialect.CalciteSqlDialect; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.util.ImmutableBitSet; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +import org.junit.Test; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * Tests for {@link RelToSqlConverter} on a schema that has nested structures of multiple + * levels. + */ +public class RelToSqlConverterStructsTest { + + private static final Schema SCHEMA = new Schema() { + @Override public Table getTable(String name) { + return TABLE; + } + + @Override public Set<String> getTableNames() { + return ImmutableSet.of("myTable"); + } + + @Override public RelProtoDataType getType(String name) { + return null; + } + + @Override public Set<String> getTypeNames() { + return ImmutableSet.of(); + } + + @Override public Collection<Function> getFunctions(String name) { + return null; + } + + @Override public Set<String> getFunctionNames() { + return ImmutableSet.of(); + } + + @Override public Schema getSubSchema(String name) { + return null; + } + + @Override public Set<String> getSubSchemaNames() { + return ImmutableSet.of(); + } + + @Override public Expression getExpression(SchemaPlus parentSchema, String name) { + return null; + } + + @Override public boolean isMutable() { + return false; + } + + @Override public Schema snapshot(SchemaVersion version) { + return null; + } + }; + + // Table schema is as following: + // { a: INT, n1: { n11: { b INT }, n12: {c: Int } }, n2: { d: Int }, e: Int } + private static final Table TABLE = new Table() { + @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) { + final RelDataType aType = typeFactory.createSqlType(SqlTypeName.BIGINT); + final RelDataType bType = typeFactory.createSqlType(SqlTypeName.BIGINT); + final RelDataType cType = typeFactory.createSqlType(SqlTypeName.BIGINT); + final RelDataType dType = typeFactory.createSqlType(SqlTypeName.BIGINT); + final RelDataType eType = typeFactory.createSqlType(SqlTypeName.BIGINT); + final RelDataType n11Type = typeFactory + .createStructType(ImmutableList.of(bType), ImmutableList.of("b")); + final RelDataType n12Type = typeFactory + .createStructType(ImmutableList.of(cType), ImmutableList.of("c")); + final RelDataType n1Type = typeFactory + .createStructType(ImmutableList.of(n11Type, n12Type), ImmutableList.of("n11", "n12")); + final RelDataType n2Type = typeFactory + .createStructType(ImmutableList.of(dType), ImmutableList.of("d")); + return typeFactory.createStructType( + ImmutableList.of(aType, n1Type, n2Type, eType), + ImmutableList.of("a", "n1", "n2", "e")); + } + + @Override public Statistic getStatistic() { + return STATS; + } + + @Override public Schema.TableType getJdbcTableType() { + return null; + } + + @Override public boolean isRolledUp(String column) { + return false; + } + + @Override public boolean rolledUpColumnValidInsideAgg(String column, + SqlCall call, + SqlNode parent, + CalciteConnectionConfig config) { + return false; + } + }; + + private static final Statistic STATS = new Statistic() { + @Override public Double getRowCount() { + return 0D; + } + + @Override public boolean isKey(ImmutableBitSet columns) { + return false; + } + + @Override public List<RelReferentialConstraint> getReferentialConstraints() { + return ImmutableList.of(); + } + + @Override public List<RelCollation> getCollations() { + return ImmutableList.of(); + } + + @Override public RelDistribution getDistribution() { + return null; + } + }; + + private static final SchemaPlus ROOT_SCHEMA = CalciteSchema + .createRootSchema(false).add("myDb", SCHEMA).plus(); + + private RelToSqlConverterTest.Sql sql(String sql) { + return new RelToSqlConverterTest.Sql(ROOT_SCHEMA, sql, + CalciteSqlDialect.DEFAULT, RelToSqlConverterTest.DEFAULT_REL_CONFIG, + ImmutableList.of()); + } + + @Test public void testNestedSchemaSelectStar() { + String query = "SELECT * FROM \"myTable\""; + String expected = "SELECT \"a\", " + + "\"n1\".\"n11\".\"b\" AS \"n1\", " + + "\"n1\".\"n12\".\"c\" AS \"n12\", " + + "\"n2\".\"d\" AS \"n2\", " + + "\"e\"\n" + + "FROM \"myDb\".\"myTable\""; + sql(query).ok(expected); + } + + @Test public void testNestedSchemaRootColumns() { + String query = "SELECT \"a\", \"e\" FROM \"myTable\""; + String expected = "SELECT \"a\", " + + "\"e\"\n" + + "FROM \"myDb\".\"myTable\""; + sql(query).ok(expected); + } + + @Test public void testNestedSchemaNestedColumns() { + String query = "SELECT \"a\", \"e\", " + + "\"myTable\".\"n1\".\"n11\".\"b\", " + + "\"myTable\".\"n2\".\"d\" " + + "FROM \"myTable\""; + String expected = "SELECT \"a\", " + + "\"e\", " + + "\"n1\".\"n11\".\"b\", " + + "\"n2\".\"d\"\n" + + "FROM \"myDb\".\"myTable\""; + sql(query).ok(expected); + } +} +// End RelToSqlConverterStructsTest.java http://git-wip-us.apache.org/repos/asf/calcite/blob/3c40d866/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java index ec216a1..572f5fc 100644 --- a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java +++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java @@ -84,12 +84,12 @@ public class RelToSqlConverterTest { } private static Planner getPlanner(List<RelTraitDef> traitDefs, - SqlParser.Config parserConfig, CalciteAssert.SchemaSpec schemaSpec, + SqlParser.Config parserConfig, SchemaPlus schema, SqlToRelConverter.Config sqlToRelConf, Program... programs) { final SchemaPlus rootSchema = Frameworks.createRootSchema(true); final FrameworkConfig config = Frameworks.newConfigBuilder() .parserConfig(parserConfig) - .defaultSchema(CalciteAssert.addSchema(rootSchema, schemaSpec)) + .defaultSchema(schema) .traitDefs(traitDefs) .sqlToRelConverterConfig(sqlToRelConf) .programs(programs) @@ -2566,8 +2566,8 @@ public class RelToSqlConverterTest { } /** Fluid interface to run tests. */ - private static class Sql { - private CalciteAssert.SchemaSpec schemaSpec; + static class Sql { + private final SchemaPlus schema; private final String sql; private final SqlDialect dialect; private final List<Function<RelNode, RelNode>> transforms; @@ -2576,7 +2576,18 @@ public class RelToSqlConverterTest { Sql(CalciteAssert.SchemaSpec schemaSpec, String sql, SqlDialect dialect, SqlToRelConverter.Config config, List<Function<RelNode, RelNode>> transforms) { - this.schemaSpec = schemaSpec; + final SchemaPlus rootSchema = Frameworks.createRootSchema(true); + this.schema = CalciteAssert.addSchema(rootSchema, schemaSpec); + this.sql = sql; + this.dialect = dialect; + this.transforms = ImmutableList.copyOf(transforms); + this.config = config; + } + + Sql(SchemaPlus schema, String sql, SqlDialect dialect, + SqlToRelConverter.Config config, + List<Function<RelNode, RelNode>> transforms) { + this.schema = schema; this.sql = sql; this.dialect = dialect; this.transforms = ImmutableList.copyOf(transforms); @@ -2584,7 +2595,7 @@ public class RelToSqlConverterTest { } Sql dialect(SqlDialect dialect) { - return new Sql(schemaSpec, sql, dialect, config, transforms); + return new Sql(schema, sql, dialect, config, transforms); } Sql withDb2() { @@ -2620,11 +2631,11 @@ public class RelToSqlConverterTest { } Sql config(SqlToRelConverter.Config config) { - return new Sql(schemaSpec, sql, dialect, config, transforms); + return new Sql(schema, sql, dialect, config, transforms); } Sql optimize(final RuleSet ruleSet, final RelOptPlanner relOptPlanner) { - return new Sql(schemaSpec, sql, dialect, config, + return new Sql(schema, sql, dialect, config, FlatLists.append(transforms, r -> { Program program = Programs.of(ruleSet); return program.run(relOptPlanner, r, r.getTraitSet(), @@ -2650,7 +2661,7 @@ public class RelToSqlConverterTest { String exec() { final Planner planner = - getPlanner(null, SqlParser.Config.DEFAULT, schemaSpec, config); + getPlanner(null, SqlParser.Config.DEFAULT, schema, config); try { SqlNode parse = planner.parse(sql); SqlNode validate = planner.validate(parse);
