You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by za...@apache.org on 2019/03/28 06:59:08 UTC

[calcite-avatica] branch master updated: [CALCITE-2776] Wrong value when accessing struct types with one attribute

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 5a305f1  [CALCITE-2776] Wrong value when accessing struct types with one attribute
5a305f1 is described below

commit 5a305f166cf06419a0c28e496a9bf853ba540047
Author: Stamatis Zampetakis <za...@gmail.com>
AuthorDate: Tue Jan 8 10:37:27 2019 +0100

    [CALCITE-2776] Wrong value when accessing struct types with one attribute
    
    1. Remove special treatment for struct types with one attribute in AbstractCursor#createAccessor.
    
    2. Add parameterized unit test for testing struct accessors using multiple internal representations for struct types.
---
 .../calcite/avatica/util/AbstractCursor.java       |   6 +-
 .../calcite/avatica/util/StructImplTest.java       | 196 +++++++++++++++------
 2 files changed, 147 insertions(+), 55 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java b/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
index 9372bfb..5559f60 100644
--- a/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
+++ b/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
@@ -202,12 +202,8 @@ public abstract class AbstractCursor implements Cursor {
             (ColumnMetaData.StructType) columnMetaData.type;
         List<Accessor> accessors = new ArrayList<>();
         for (ColumnMetaData column : structType.columns) {
-          final Getter fieldGetter =
-              structType.columns.size() == 1
-                  ? getter
-                  : new StructGetter(getter, column);
           accessors.add(
-              createAccessor(column, fieldGetter, localCalendar, factory));
+              createAccessor(column, new StructGetter(getter, column), localCalendar, factory));
         }
         return new StructAccessor(getter, accessors);
       default:
diff --git a/core/src/test/java/org/apache/calcite/avatica/util/StructImplTest.java b/core/src/test/java/org/apache/calcite/avatica/util/StructImplTest.java
index 625c538..1058125 100644
--- a/core/src/test/java/org/apache/calcite/avatica/util/StructImplTest.java
+++ b/core/src/test/java/org/apache/calcite/avatica/util/StructImplTest.java
@@ -17,74 +17,170 @@
 package org.apache.calcite.avatica.util;
 
 import org.apache.calcite.avatica.ColumnMetaData;
-import org.apache.calcite.avatica.ColumnMetaData.StructType;
 import org.apache.calcite.avatica.MetaImpl;
 import org.apache.calcite.avatica.util.Cursor.Accessor;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+import java.sql.SQLException;
 import java.sql.Struct;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertArrayEquals;
 
 /**
  * Test class for StructImpl.
  */
+@RunWith(Parameterized.class)
 public class StructImplTest {
 
-  @Test public void structAccessor() throws Exception {
-    // Define the struct type we're creating
-    ColumnMetaData intMetaData = MetaImpl.columnMetaData("MY_INT", 1, int.class, false);
-    ColumnMetaData stringMetaData = MetaImpl.columnMetaData("MY_STRING", 2, String.class, true);
-    StructType structType = ColumnMetaData.struct(Arrays.asList(intMetaData, stringMetaData));
-    // Create some structs
-    Struct struct1 = new StructImpl(Arrays.<Object>asList(1, "one"));
-    Struct struct2 = new StructImpl(Arrays.<Object>asList(2, "two"));
-    Struct struct3 = new StructImpl(Arrays.<Object>asList(3));
-    Struct struct4 = new StructImpl(Arrays.<Object>asList(4, "four", "ignored"));
-    ColumnMetaData structMetaData = MetaImpl.columnMetaData("MY_STRUCT", 1, structType, false);
-    List<List<Object>> rows = Arrays.asList(Collections.<Object>singletonList(struct1),
-        Collections.<Object>singletonList(struct2), Collections.<Object>singletonList(struct3),
-        Collections.<Object>singletonList(struct4));
-    // Create four rows, each with one (struct) column
+  /**
+   * A class holding the necessary information for performing tests related with one table column.
+   *
+   * @param <T> the type of the expected values for the column
+   */
+  private static class ColumnInputBundle<T> {
+    private final ColumnMetaData metaData;
+    /**
+     * The input values for the given column.
+     *
+     * These values are used to construct the rows of a ResultSet.
+     */
+    private final List<Object> inputValues;
+    /**
+     * The expected values for the given column.
+     *
+     * These values are used to verify that the result obtained from a ResultSet is correct. Note,
+     * that inputValues and expectedValues are not necessarily equal.
+     */
+    private final List<T> expectedValues;
+
+    ColumnInputBundle(List<Object> inputValues, List<T> expected, ColumnMetaData metaData) {
+      this.inputValues = inputValues;
+      this.expectedValues = expected;
+      this.metaData = metaData;
+    }
+
+    @Override public String toString() {
+      return "column=" + metaData.columnName + ", inputType=" + inputValues.get(0).getClass();
+    }
+  }
+
+  private final ColumnInputBundle<Struct> columnInputBundle;
+
+  public StructImplTest(ColumnInputBundle<Struct> columnInputBundle) {
+    this.columnInputBundle = columnInputBundle;
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static Collection<ColumnInputBundle<Struct>> data() throws SQLException {
+    List<ColumnInputBundle<Struct>> data = new ArrayList<>();
+    final int numRows = 5;
+
+    ColumnMetaData oneAttrStructMeta = MetaImpl.columnMetaData(
+        "ONE_ATTRIBUTE_STRUCT",
+        0,
+        ColumnMetaData.struct(
+            Arrays.asList(
+                MetaImpl.columnMetaData("INT", 0, int.class, false)
+            )
+        ),
+        false);
+
+    ColumnMetaData twoAttrStructMeta = MetaImpl.columnMetaData(
+        "TWO_ATTRIBUTES_STRUCT",
+        0,
+        ColumnMetaData.struct(
+            Arrays.asList(
+                MetaImpl.columnMetaData("INT", 0, int.class, false),
+                MetaImpl.columnMetaData("INT", 1, int.class, false)
+            )
+        ),
+        false);
+
+    List<Struct> oneAttrStructData = new ArrayList<>(numRows);
+    List<Struct> twoAttrStructData = new ArrayList<>(numRows);
+    for (int i = 0; i < numRows; i++) {
+      oneAttrStructData.add(new StructImpl(Arrays.asList(i)));
+      twoAttrStructData.add(new StructImpl(Arrays.asList(i, i)));
+    }
+
+    // A struct has various internal representations. The most common are the following:
+    Class[] structTypes = new Class[]{Object[].class, Struct.class, List.class};
+    // Generate column bundles for every possible representation that is supported currently.
+    for (Class<?> type : structTypes) {
+      data.add(newStructColBundle(oneAttrStructData, type, oneAttrStructMeta));
+      data.add(newStructColBundle(twoAttrStructData, type, twoAttrStructMeta));
+    }
+
+    return data;
+  }
+
+  private static ColumnInputBundle newStructColBundle(
+      List<Struct> data, Class<?> structType, ColumnMetaData meta) throws SQLException {
+    List<Object> input = new ArrayList<>();
+    List<Struct> expected = new ArrayList<>();
+    for (Struct struct : data) {
+      input.add(structOf(structType, struct));
+      // The result obtained from StructAccessor is always of type Struct.class
+      expected.add(structOf(Struct.class, struct));
+    }
+    return new ColumnInputBundle(input, expected, meta);
+  }
+
+  /**
+   * Creates a struct in the representation dictated by the <code>structClass</code> parameter.
+   */
+  private static <T> T structOf(Class<T> structClass, Struct structData) throws SQLException {
+    Object[] fieldValues = structData.getAttributes();
+    if (structClass.equals(Object[].class)) {
+      Object[] arrayStruct = new Object[fieldValues.length];
+      for (int i = 0; i < fieldValues.length; i++) {
+        arrayStruct[i] = fieldValues[i];
+      }
+      return (T) arrayStruct;
+    } else if (structClass.equals(Struct.class)) {
+      List<Object> listStruct = new ArrayList<>();
+      for (int i = 0; i < fieldValues.length; i++) {
+        listStruct.add(fieldValues[i]);
+      }
+      return (T) new StructImpl(listStruct);
+    } else if (structClass.equals(List.class)) {
+      List<Object> listStruct = new ArrayList<>();
+      for (int i = 0; i < fieldValues.length; i++) {
+        listStruct.add(fieldValues[i]);
+      }
+      return (T) listStruct;
+    } else {
+      throw new IllegalStateException("Cannot create struct of type" + structClass);
+    }
+  }
+
+  @Test public void testStructAccessor() throws Exception {
+    // Create rows based on the inputValues data
+    List<List<Object>> rows = new ArrayList<>();
+    for (Object o : columnInputBundle.inputValues) {
+      rows.add(Collections.singletonList(o));
+    }
     try (Cursor cursor = new ListIteratorCursor(rows.iterator())) {
-      List<Accessor> accessors = cursor.createAccessors(Collections.singletonList(structMetaData),
-          Unsafe.localCalendar(), null);
-      assertEquals(1, accessors.size());
+      List<Accessor> accessors =
+          cursor.createAccessors(
+              Collections.singletonList(columnInputBundle.metaData), Unsafe.localCalendar(), null);
       Accessor accessor = accessors.get(0);
-
-      assertTrue(cursor.next());
-      Struct s = accessor.getObject(Struct.class);
-      Object[] structData = s.getAttributes();
-      assertEquals(2, structData.length);
-      assertEquals(1, structData[0]);
-      assertEquals("one", structData[1]);
-
-      assertTrue(cursor.next());
-      s = accessor.getObject(Struct.class);
-      structData = s.getAttributes();
-      assertEquals(2, structData.length);
-      assertEquals(2, structData[0]);
-      assertEquals("two", structData[1]);
-
-      assertTrue(cursor.next());
-      s = accessor.getObject(Struct.class);
-      structData = s.getAttributes();
-      assertEquals(1, structData.length);
-      assertEquals(3, structData[0]);
-
-      assertTrue(cursor.next());
-      s = accessor.getObject(Struct.class);
-      structData = s.getAttributes();
-      assertEquals(3, structData.length);
-      assertEquals(4, structData[0]);
-      assertEquals("four", structData[1]);
-      // We didn't provide metadata, but we still expect to see it.
-      assertEquals("ignored", structData[2]);
+      int i = 0;
+      while (cursor.next()) {
+        Struct s = accessor.getObject(Struct.class);
+        Object[] expectedStructAttributes = columnInputBundle.expectedValues.get(i).getAttributes();
+        Object[] actualStructAttributes = s.getAttributes();
+        assertArrayEquals(expectedStructAttributes, actualStructAttributes);
+        i++;
+      }
     }
   }
 }