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 2020/06/20 17:28:03 UTC

[calcite] branch master updated: [CALCITE-4063] Unnest an array of single-item structs causes ClassCastException

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 797b487  [CALCITE-4063] Unnest an array of single-item structs causes ClassCastException
797b487 is described below

commit 797b48754d074d53e853a67bf249c155f51b71dc
Author: rubenada <ru...@gmail.com>
AuthorDate: Wed Jun 17 17:13:39 2020 +0200

    [CALCITE-4063] Unnest an array of single-item structs causes ClassCastException
---
 .../adapter/enumerable/EnumerableUncollect.java    |  18 +-
 .../org/apache/calcite/runtime/SqlFunctions.java   |  40 +++++
 .../org/apache/calcite/util/BuiltInMethod.java     |   1 +
 .../test/enumerable/EnumerableUncollectTest.java   | 189 +++++++++++++++++++++
 4 files changed, 244 insertions(+), 4 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollect.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollect.java
index 6608dda..0f7d709 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollect.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableUncollect.java
@@ -95,6 +95,7 @@ public class EnumerableUncollect extends Uncollect implements EnumerableRel {
     final List<Integer> fieldCounts = new ArrayList<>();
     final List<FlatProductInputType> inputTypes = new ArrayList<>();
 
+    Expression lambdaForStructWithSingleItem = null;
     for (RelDataTypeField field : child.getRowType().getFieldList()) {
       final RelDataType type = field.getType();
       if (type instanceof MapSqlType) {
@@ -103,8 +104,16 @@ public class EnumerableUncollect extends Uncollect implements EnumerableRel {
       } else {
         final RelDataType elementType = type.getComponentType();
         if (elementType.isStruct()) {
-          fieldCounts.add(elementType.getFieldCount());
-          inputTypes.add(FlatProductInputType.LIST);
+          if (elementType.getFieldCount() == 1 && child.getRowType().getFieldList().size() == 1
+              && !withOrdinality) {
+            // Solves CALCITE-4063: if we are processing a single field, which is a struct with a
+            // single item inside, and no ordinality; the result must be a scalar, hence use a
+            // special lambda that does not return lists, but the (single) items within those lists
+            lambdaForStructWithSingleItem = Expressions.call(BuiltInMethod.FLAT_LIST.method);
+          } else {
+            fieldCounts.add(elementType.getFieldCount());
+            inputTypes.add(FlatProductInputType.LIST);
+          }
         } else {
           fieldCounts.add(-1);
           inputTypes.add(FlatProductInputType.SCALAR);
@@ -112,8 +121,9 @@ public class EnumerableUncollect extends Uncollect implements EnumerableRel {
       }
     }
 
-    final Expression lambda =
-        Expressions.call(BuiltInMethod.FLAT_PRODUCT.method,
+    final Expression lambda = lambdaForStructWithSingleItem != null
+        ? lambdaForStructWithSingleItem
+        : Expressions.call(BuiltInMethod.FLAT_PRODUCT.method,
             Expressions.constant(Ints.toArray(fieldCounts)),
             Expressions.constant(withOrdinality),
             Expressions.constant(
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index b4fca2c..18c9312 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -2628,6 +2628,46 @@ public class SqlFunctions {
     return resultCollection;
   }
 
+  /**
+   * Function that, given a certain List containing single-item structs (i.e. arrays / lists with
+   * a single item), builds an Enumerable that returns those single items inside the structs.
+   */
+  public static Function1<Object, Enumerable<Comparable>> flatList() {
+    return inputObject -> {
+      final List list = (List) inputObject;
+      final Enumerator<List<Object>> enumerator = Linq4j.enumerator(list);
+      return new AbstractEnumerable<Comparable>() {
+        public Enumerator<Comparable> enumerator() {
+          return new Enumerator<Comparable>() {
+
+            @Override public boolean moveNext() {
+              return enumerator.moveNext();
+            }
+
+            @Override public Comparable current() {
+              final Object element = enumerator.current();
+              final Comparable comparable;
+              if (element.getClass().isArray()) {
+                comparable = (Comparable) ((Object[]) element)[0];
+              } else {
+                comparable = (Comparable) ((List) element).get(0);
+              }
+              return comparable;
+            }
+
+            @Override public void reset() {
+              enumerator.reset();
+            }
+
+            @Override public void close() {
+              enumerator.close();
+            }
+          };
+        }
+      };
+    };
+  }
+
   public static Function1<Object, Enumerable<ComparableList<Comparable>>> flatProduct(
       final int[] fieldCounts, final boolean withOrdinality,
       final FlatProductInputType[] inputTypes) {
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index f4f18f0..b7e6176 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -245,6 +245,7 @@ public enum BuiltInMethod {
   ARRAY(SqlFunctions.class, "array", Object[].class),
   FLAT_PRODUCT(SqlFunctions.class, "flatProduct", int[].class, boolean.class,
       FlatProductInputType[].class),
+  FLAT_LIST(SqlFunctions.class, "flatList"),
   LIST_N(FlatLists.class, "copyOf", Comparable[].class),
   LIST2(FlatLists.class, "of", Object.class, Object.class),
   LIST3(FlatLists.class, "of", Object.class, Object.class, Object.class),
diff --git a/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableUncollectTest.java b/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableUncollectTest.java
new file mode 100644
index 0000000..d898575
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableUncollectTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.test.enumerable;
+
+import org.apache.calcite.config.CalciteConnectionProperty;
+import org.apache.calcite.config.Lex;
+import org.apache.calcite.test.CalciteAssert;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+class EnumerableUncollectTest {
+
+  @Test void simpleUnnestArray() {
+    final String sql = "select * from UNNEST(array[3, 4]) as T2(y)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "y=3",
+            "y=4");
+  }
+
+  @Test void simpleUnnestArrayOfArrays() {
+    final String sql = "select * from UNNEST(array[array[3], array[4]]) as T2(y)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "y=[3]",
+            "y=[4]");
+  }
+
+  @Test void simpleUnnestArrayOfArrays2() {
+    final String sql = "select * from UNNEST(array[array[3, 4], array[4, 5]]) as T2(y)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "y=[3, 4]",
+            "y=[4, 5]");
+  }
+
+  @Test void simpleUnnestArrayOfArrays3() {
+    final String sql = "select * from UNNEST("
+        + "array[array[array[3,4], array[4,5]], array[array[7,8], array[9,10]]]) as T2(y)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "y=[[3, 4], [4, 5]]",
+            "y=[[7, 8], [9, 10]]");
+  }
+
+  @Test void simpleUnnestArrayOfRows() {
+    final String sql = "select * from UNNEST(array[ROW(3), ROW(4)]) as T2(y)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "y=3",
+            "y=4");
+  }
+
+  @Test void simpleUnnestArrayOfRows2() {
+    final String sql = "select * from UNNEST(array[ROW(3, 5), ROW(4, 6)]) as T2(y, z)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "y=3; z=5",
+            "y=4; z=6");
+  }
+
+  @Test void simpleUnnestArrayOfRows3() {
+    final String sql = "select * from UNNEST(array[ROW(3), ROW(4)]) WITH ORDINALITY as T2(y, o)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "y=3; o=1",
+            "y=4; o=2");
+  }
+
+  @Disabled("CALCITE-4064")
+  @Test void simpleUnnestArrayOfRows4() {
+    final String sql = "select * from UNNEST(array[ROW(1, ROW(5, 10)), ROW(2, ROW(6, 12))])";
+    tester()
+        .query(sql)
+        .returnsUnordered("");
+  }
+
+  @Test void chainedUnnestArray() {
+    final String sql = "select * from (values (1), (2)) T1(x),"
+        + "UNNEST(array[3, 4]) as T2(y)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "x=1; y=3",
+            "x=1; y=4",
+            "x=2; y=3",
+            "x=2; y=4");
+  }
+
+  @Test void chainedUnnestArrayOfArrays() {
+    final String sql = "select * from (values (1), (2)) T1(x),"
+        + "UNNEST(array[array[3], array[4]]) as T2(y)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "x=1; y=[3]",
+            "x=1; y=[4]",
+            "x=2; y=[3]",
+            "x=2; y=[4]");
+  }
+
+  @Test void chainedUnnestArrayOfArrays2() {
+    final String sql = "select * from (values (1), (2)) T1(x),"
+        + "UNNEST(array[array[3, 4], array[4, 5]]) as T2(y)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "x=1; y=[3, 4]",
+            "x=1; y=[4, 5]",
+            "x=2; y=[3, 4]",
+            "x=2; y=[4, 5]");
+  }
+
+  @Test void chainedUnnestArrayOfArrays3() {
+    final String sql = "select * from (values (1), (2)) T1(x),"
+        + "UNNEST(array[array[array[3,4], array[4,5]], array[array[7,8], array[9,10]]]) as T2(y)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "x=1; y=[[3, 4], [4, 5]]",
+            "x=1; y=[[7, 8], [9, 10]]",
+            "x=2; y=[[3, 4], [4, 5]]",
+            "x=2; y=[[7, 8], [9, 10]]");
+  }
+
+  @Test void chainedUnnestArrayOfRows() {
+    final String sql = "select * from (values (1), (2)) T1(x),"
+        + "UNNEST(array[ROW(3), ROW(4)]) as T2(y)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "x=1; y=3",
+            "x=1; y=4",
+            "x=2; y=3",
+            "x=2; y=4");
+  }
+
+  @Test void chainedUnnestArrayOfRows2() {
+    final String sql = "select * from (values (1), (2)) T1(x),"
+        + "UNNEST(array[ROW(3, 5), ROW(4, 6)]) as T2(y, z)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "x=1; y=3; z=5",
+            "x=1; y=4; z=6",
+            "x=2; y=3; z=5",
+            "x=2; y=4; z=6");
+  }
+
+  @Test void chainedUnnestArrayOfRows3() {
+    final String sql = "select * from (values (1), (2)) T1(x),"
+        + "UNNEST(array[ROW(3), ROW(4)]) WITH ORDINALITY as T2(y, o)";
+    tester()
+        .query(sql)
+        .returnsUnordered(
+            "x=1; y=3; o=1",
+            "x=1; y=4; o=2",
+            "x=2; y=3; o=1",
+            "x=2; y=4; o=2");
+  }
+
+  private CalciteAssert.AssertThat tester() {
+    return CalciteAssert.that()
+        .with(CalciteConnectionProperty.LEX, Lex.JAVA)
+        .with(CalciteConnectionProperty.FORCE_DECORRELATE, false);
+  }
+}