You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by mm...@apache.org on 2018/07/25 17:23:45 UTC

calcite git commit: [CALCITE-2417] Fix ClassCastException in RelToSqlConverter with structs (Benoit Hanotte)

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 <b....@criteo.com>
Authored: Tue Jul 17 14:25:05 2018 +0200
Committer: Michael Mior <mm...@uwaterloo.ca>
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);