calcite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From els...@apache.org
Subject [3/4] calcite-avatica git commit: [CALCITE-1050] Array support for Avatica
Date Mon, 24 Apr 2017 22:12:57 GMT
[CALCITE-1050] Array support for Avatica

As best as possible, works around the limitations of JDBC's
Array class to handle arbitrarily nested Arrays. Nested-array
support differs from DB to DB, so functionality is primarily
driven from structure, rather than metadata.

Also, fixes the implementation of Array.getArray(long, int).The
arguments are given in terms of one-offset and number of elements,
whereas the list.subList(int, int) call is expecting zero-offset and
exclusive end-offset.

Reduce the "API" changes to avoid downstream changes in Avatica. This
"restores" some weirdness in the implementations, but simplifies the
upgrade process (probably worth it).

Closes apache/calcite-avatica#2


Project: http://git-wip-us.apache.org/repos/asf/calcite-avatica/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite-avatica/commit/dd65a2b1
Tree: http://git-wip-us.apache.org/repos/asf/calcite-avatica/tree/dd65a2b1
Diff: http://git-wip-us.apache.org/repos/asf/calcite-avatica/diff/dd65a2b1

Branch: refs/heads/master
Commit: dd65a2b18b8c35cfccf1c47b6be87ea7db3ad658
Parents: 01ec883
Author: Josh Elser <elserj@apache.org>
Authored: Tue Jan 12 16:40:18 2016 -0500
Committer: Josh Elser <elserj@apache.org>
Committed: Mon Apr 24 17:59:17 2017 -0400

----------------------------------------------------------------------
 .../calcite/avatica/AvaticaConnection.java      |  29 +-
 .../calcite/avatica/AvaticaResultSet.java       |  39 +-
 .../org/apache/calcite/avatica/AvaticaSite.java |   1 +
 .../apache/calcite/avatica/AvaticaUtils.java    |   2 +-
 .../apache/calcite/avatica/ColumnMetaData.java  | 145 +++-
 .../java/org/apache/calcite/avatica/Meta.java   |  96 ++-
 .../org/apache/calcite/avatica/MetaImpl.java    |  45 +-
 .../apache/calcite/avatica/proto/Common.java    | 714 +++++++++++++++++--
 .../apache/calcite/avatica/remote/Service.java  |   8 +-
 .../calcite/avatica/remote/TypedValue.java      | 277 ++++++-
 .../calcite/avatica/util/AbstractCursor.java    | 137 +++-
 .../calcite/avatica/util/ArrayFactoryImpl.java  | 142 ++++
 .../apache/calcite/avatica/util/ArrayImpl.java  |  86 ++-
 .../calcite/avatica/util/PositionedCursor.java  |  15 +-
 .../org/apache/calcite/avatica/util/Unsafe.java |  11 +
 core/src/main/protobuf/common.proto             |   3 +
 .../AvaticaResultSetConversionsTest.java        |  13 -
 .../org/apache/calcite/avatica/FrameTest.java   |  28 +
 .../org/apache/calcite/avatica/RepTest.java     |  57 ++
 .../calcite/avatica/remote/TypedValueTest.java  |  29 +
 .../calcite/avatica/util/ArrayImplTest.java     | 193 +++++
 .../calcite/avatica/util/StructImplTest.java    |  92 +++
 .../apache/calcite/avatica/jdbc/JdbcMeta.java   |   6 +-
 .../calcite/avatica/jdbc/JdbcResultSet.java     |  92 ++-
 .../calcite/avatica/RemoteDriverTest.java       |   7 +-
 .../calcite/avatica/remote/ArrayTypeTest.java   | 626 ++++++++++++++++
 .../avatica/remote/AvaticaServersForTest.java   | 141 ++++
 .../calcite/avatica/remote/RemoteMetaTest.java  |  72 +-
 28 files changed, 2802 insertions(+), 304 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/dd65a2b1/core/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java b/core/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java
index 51649c1..21720cc 100644
--- a/core/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java
+++ b/core/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java
@@ -16,6 +16,8 @@
  */
 package org.apache.calcite.avatica;
 
+import org.apache.calcite.avatica.ColumnMetaData.AvaticaType;
+import org.apache.calcite.avatica.ColumnMetaData.Rep;
 import org.apache.calcite.avatica.Meta.ExecuteBatchResult;
 import org.apache.calcite.avatica.Meta.MetaResultSet;
 import org.apache.calcite.avatica.remote.KerberosConnection;
@@ -23,6 +25,7 @@ import org.apache.calcite.avatica.remote.Service;
 import org.apache.calcite.avatica.remote.Service.ErrorResponse;
 import org.apache.calcite.avatica.remote.Service.OpenConnectionRequest;
 import org.apache.calcite.avatica.remote.TypedValue;
+import org.apache.calcite.avatica.util.ArrayFactoryImpl;
 
 import java.sql.Array;
 import java.sql.Blob;
@@ -391,9 +394,29 @@ public abstract class AvaticaConnection implements Connection {
     throw helper.unsupported();
   }
 
-  public Array createArrayOf(String typeName, Object[] elements)
-      throws SQLException {
-    throw helper.unsupported();
+  public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
+    @SuppressWarnings("unchecked")
+    List<Object> elementList = (List<Object>) AvaticaUtils.primitiveList(elements);
+    SqlType type;
+    try {
+      type = SqlType.valueOf(typeName);
+    } catch (IllegalArgumentException e) {
+      throw new SQLException("Could not find JDBC type for '" + typeName + "'");
+    }
+    AvaticaType avaticaType = null;
+    switch (type) {
+    case ARRAY:
+      // TODO: Nested ARRAYs
+      throw helper.createException("Cannot create an ARRAY of ARRAY's");
+    case STRUCT:
+      // TODO: ARRAYs of STRUCTs
+      throw helper.createException("Cannot create an ARRAY of STRUCT's");
+    default:
+      // This is an ARRAY, we need to use Objects, not primitives (nullable).
+      avaticaType = ColumnMetaData.scalar(type.id, typeName, Rep.nonPrimitiveRepOf(type));
+    }
+    ArrayFactoryImpl arrayFactory = new ArrayFactoryImpl(getTimeZone());
+    return arrayFactory.createArray(avaticaType, elementList);
   }
 
   public Struct createStruct(String typeName, Object[] attributes)

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/dd65a2b1/core/src/main/java/org/apache/calcite/avatica/AvaticaResultSet.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/avatica/AvaticaResultSet.java b/core/src/main/java/org/apache/calcite/avatica/AvaticaResultSet.java
index 51bf8fa..e78b6e9 100644
--- a/core/src/main/java/org/apache/calcite/avatica/AvaticaResultSet.java
+++ b/core/src/main/java/org/apache/calcite/avatica/AvaticaResultSet.java
@@ -17,7 +17,7 @@
 package org.apache.calcite.avatica;
 
 import org.apache.calcite.avatica.remote.TypedValue;
-import org.apache.calcite.avatica.util.ArrayImpl;
+import org.apache.calcite.avatica.util.ArrayFactoryImpl;
 import org.apache.calcite.avatica.util.Cursor;
 
 import java.io.InputStream;
@@ -39,6 +39,7 @@ import java.sql.SQLXML;
 import java.sql.Time;
 import java.sql.Timestamp;
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -48,7 +49,7 @@ import java.util.TimeZone;
  * Implementation of {@link java.sql.ResultSet}
  * for the Avatica engine.
  */
-public class AvaticaResultSet implements ResultSet, ArrayImpl.Factory {
+public class AvaticaResultSet extends ArrayFactoryImpl implements ResultSet {
   protected final AvaticaStatement statement;
   protected final QueryState state;
   protected final Meta.Signature signature;
@@ -77,16 +78,25 @@ public class AvaticaResultSet implements ResultSet, ArrayImpl.Factory {
       ResultSetMetaData resultSetMetaData,
       TimeZone timeZone,
       Meta.Frame firstFrame) {
+    super(timeZone);
     this.statement = statement;
     this.state = state;
     this.signature = signature;
     this.firstFrame = firstFrame;
     this.columnMetaDataList = signature.columns;
-    this.type = statement.resultSetType;
-    this.concurrency = statement.resultSetConcurrency;
-    this.holdability = statement.resultSetHoldability;
-    this.fetchSize = statement.getFetchSize();
-    this.fetchDirection = statement.getFetchDirection();
+    if (null != statement) {
+      this.type = statement.resultSetType;
+      this.concurrency = statement.resultSetConcurrency;
+      this.holdability = statement.resultSetHoldability;
+      this.fetchSize = statement.getFetchSize();
+      this.fetchDirection = statement.getFetchDirection();
+    } else {
+      this.type = 0;
+      this.concurrency = 0;
+      this.holdability = 0;
+      this.fetchSize = AvaticaStatement.DEFAULT_FETCH_SIZE;
+      this.fetchDirection = 0;
+    }
     this.resultSetMetaData = resultSetMetaData;
     this.localCalendar = Calendar.getInstance(timeZone, Locale.ROOT);
   }
@@ -190,10 +200,9 @@ public class AvaticaResultSet implements ResultSet, ArrayImpl.Factory {
    * @throws SQLException if execute fails for some reason.
    */
   protected AvaticaResultSet execute() throws SQLException {
-    final List<TypedValue> parameterValues = statement.getBoundParameterValues();
     final Iterable<Object> iterable1 =
         statement.connection.meta.createIterable(statement.handle, state, signature,
-            parameterValues, firstFrame);
+            Collections.<TypedValue>emptyList(), firstFrame);
     this.cursor = MetaImpl.createCursor(signature.cursorFactory, iterable1);
     this.accessorList =
         cursor.createAccessors(columnMetaDataList, localCalendar, this);
@@ -202,7 +211,7 @@ public class AvaticaResultSet implements ResultSet, ArrayImpl.Factory {
     return this;
   }
 
-  protected AvaticaResultSet execute2(Cursor cursor,
+  public AvaticaResultSet execute2(Cursor cursor,
       List<ColumnMetaData> columnMetaDataList) {
     this.cursor = cursor;
     this.accessorList =
@@ -212,9 +221,11 @@ public class AvaticaResultSet implements ResultSet, ArrayImpl.Factory {
     return this;
   }
 
-  public ResultSet create(ColumnMetaData.AvaticaType elementType,
-      Iterable<Object> iterable) {
-    throw new UnsupportedOperationException();
+  /**
+   * Returns the calendar used by this result set. Not a jdbc method.
+   */
+  public Calendar getLocalCalendar() {
+    return localCalendar;
   }
 
   public boolean next() throws SQLException {
@@ -222,7 +233,7 @@ public class AvaticaResultSet implements ResultSet, ArrayImpl.Factory {
     if (isClosed()) {
       throw new SQLException("next() called on closed cursor");
     }
-    if (statement.cancelFlag.get()) {
+    if (null != statement && statement.cancelFlag.get()) {
       throw new SQLException("Statement canceled");
     }
     if (cursor.next()) {

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/dd65a2b1/core/src/main/java/org/apache/calcite/avatica/AvaticaSite.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/avatica/AvaticaSite.java b/core/src/main/java/org/apache/calcite/avatica/AvaticaSite.java
index 0e23866..ece9e04 100644
--- a/core/src/main/java/org/apache/calcite/avatica/AvaticaSite.java
+++ b/core/src/main/java/org/apache/calcite/avatica/AvaticaSite.java
@@ -398,6 +398,7 @@ public class AvaticaSite {
   }
 
   public void setArray(Array x) {
+    slots[index] = wrap(ColumnMetaData.Rep.ARRAY, x);
   }
 
   public void setNull(int sqlType, String typeName) {

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/dd65a2b1/core/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java b/core/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java
index 56a23c8..45e8236 100644
--- a/core/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java
+++ b/core/src/main/java/org/apache/calcite/avatica/AvaticaUtils.java
@@ -133,7 +133,7 @@ public class AvaticaUtils {
    */
   public static List<?> primitiveList(final Object array) {
     // REVIEW: A per-type list might be more efficient. (Or might not.)
-    return new AbstractList() {
+    return new AbstractList<Object>() {
       public Object get(int index) {
         return java.lang.reflect.Array.get(array, index);
       }

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/dd65a2b1/core/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java b/core/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java
index 401070e..0f00fd2 100644
--- a/core/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java
+++ b/core/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java
@@ -316,40 +316,41 @@ public class ColumnMetaData {
    * a {@link java.sql.Date} might be represented as a {@link #PRIMITIVE_INT}
    * if not nullable, or a {@link #JAVA_SQL_DATE}. */
   public enum Rep {
-    PRIMITIVE_BOOLEAN(boolean.class),
-    PRIMITIVE_BYTE(byte.class),
-    PRIMITIVE_CHAR(char.class),
-    PRIMITIVE_SHORT(short.class),
-    PRIMITIVE_INT(int.class),
-    PRIMITIVE_LONG(long.class),
-    PRIMITIVE_FLOAT(float.class),
-    PRIMITIVE_DOUBLE(double.class),
-    BOOLEAN(Boolean.class),
-    BYTE(Byte.class),
-    CHARACTER(Character.class),
-    SHORT(Short.class),
-    INTEGER(Integer.class),
-    LONG(Long.class),
-    FLOAT(Float.class),
-    DOUBLE(Double.class),
-    JAVA_SQL_TIME(Time.class),
-    JAVA_SQL_TIMESTAMP(Timestamp.class),
-    JAVA_SQL_DATE(java.sql.Date.class),
-    JAVA_UTIL_DATE(java.util.Date.class),
-    BYTE_STRING(ByteString.class),
-    STRING(String.class),
+    PRIMITIVE_BOOLEAN(boolean.class, Types.BOOLEAN),
+    PRIMITIVE_BYTE(byte.class, Types.TINYINT),
+    PRIMITIVE_CHAR(char.class, Types.CHAR),
+    PRIMITIVE_SHORT(short.class, Types.SMALLINT),
+    PRIMITIVE_INT(int.class, Types.INTEGER),
+    PRIMITIVE_LONG(long.class, Types.BIGINT),
+    PRIMITIVE_FLOAT(float.class, Types.FLOAT),
+    PRIMITIVE_DOUBLE(double.class, Types.DOUBLE),
+    BOOLEAN(Boolean.class, Types.BOOLEAN),
+    BYTE(Byte.class, Types.TINYINT),
+    CHARACTER(Character.class, Types.CHAR),
+    SHORT(Short.class, Types.SMALLINT),
+    INTEGER(Integer.class, Types.INTEGER),
+    LONG(Long.class, Types.BIGINT),
+    FLOAT(Float.class, Types.FLOAT),
+    DOUBLE(Double.class, Types.DOUBLE),
+    JAVA_SQL_TIME(Time.class, Types.TIME),
+    JAVA_SQL_TIMESTAMP(Timestamp.class, Types.TIMESTAMP),
+    JAVA_SQL_DATE(java.sql.Date.class, Types.DATE),
+    JAVA_UTIL_DATE(java.util.Date.class, Types.DATE),
+    BYTE_STRING(ByteString.class, Types.VARBINARY),
+    STRING(String.class, Types.VARCHAR),
 
     /** Values are represented as some sub-class of {@link Number}.
      * The JSON encoding does this. */
-    NUMBER(Number.class),
+    NUMBER(Number.class, Types.NUMERIC),
 
-    ARRAY(Array.class),
-    MULTISET(List.class),
-    STRUCT(Struct.class),
+    ARRAY(Array.class, Types.ARRAY),
+    MULTISET(List.class, Types.JAVA_OBJECT),
+    STRUCT(Struct.class, Types.JAVA_OBJECT),
 
-    OBJECT(Object.class);
+    OBJECT(Object.class, Types.JAVA_OBJECT);
 
     public final Class clazz;
+    public final int typeId;
 
     public static final Map<Class, Rep> VALUE_MAP;
 
@@ -358,11 +359,13 @@ public class ColumnMetaData {
       for (Rep rep : values()) {
         builder.put(rep.clazz, rep);
       }
+      builder.put(byte[].class, BYTE_STRING);
       VALUE_MAP = Collections.unmodifiableMap(builder);
     }
 
-    Rep(Class clazz) {
+    Rep(Class clazz, int typeId) {
       this.clazz = clazz;
+      this.typeId = typeId;
     }
 
     public static Rep of(Type clazz) {
@@ -429,7 +432,10 @@ public class ColumnMetaData {
     }
 
     public static Rep fromProto(Common.Rep proto) {
-      if (Common.Rep.BIG_DECIMAL == proto) {
+      if (Common.Rep.UNRECOGNIZED == proto) {
+        // Un-set in the message, treat it as null
+        return null;
+      } else if (Common.Rep.BIG_DECIMAL == proto) {
         // BIG_DECIMAL has to come back as a NUMBER
         return Rep.NUMBER;
       } else if (Common.Rep.NULL == proto) {
@@ -437,6 +443,68 @@ public class ColumnMetaData {
       }
       return Rep.valueOf(proto.name());
     }
+
+    /**
+     * Computes the given JDBC type for a primitive to the corresponding {@link Rep} for the
+     * equivalent Object type. If the provided type is not for a primitive, a {@link Rep} for the
+     * provided Object is returned.
+     *
+     * @param type The type of a value (based on {@link java.sql.Types}).
+     * @return The corresponding non-primitive {@link Rep} for the given {@code type}.
+     */
+    public static ColumnMetaData.Rep nonPrimitiveRepOf(SqlType type) {
+      if (null == type) {
+        throw new NullPointerException();
+      }
+      if (boolean.class == type.clazz) {
+        return ColumnMetaData.Rep.BOOLEAN;
+      } else if (byte.class == type.clazz) {
+        return ColumnMetaData.Rep.BYTE;
+      } else  if (char.class == type.clazz) {
+        return ColumnMetaData.Rep.CHARACTER;
+      } else if (short.class == type.clazz) {
+        return ColumnMetaData.Rep.SHORT;
+      } else if (int.class == type.clazz) {
+        return ColumnMetaData.Rep.INTEGER;
+      } else if (long.class == type.clazz) {
+        return ColumnMetaData.Rep.LONG;
+      } else if (float.class == type.clazz) {
+        return ColumnMetaData.Rep.FLOAT;
+      } else if (double.class == type.clazz) {
+        return ColumnMetaData.Rep.DOUBLE;
+      }
+      return ColumnMetaData.Rep.of(type.clazz);
+    }
+
+    /**
+     * Computes the given JDBC type into the {@link Rep} for the wire (serial) form of that type.
+     *
+     * @param type The type of a value (based on {@link java.sql.Types}).
+     * @return The corresponding {@link Rep} for the serial form of the {@code type}.
+     */
+    public static ColumnMetaData.Rep serialRepOf(SqlType type) {
+      if (null == type) {
+        throw new NullPointerException();
+      }
+      if (boolean.class == type.internal) {
+        return ColumnMetaData.Rep.BOOLEAN;
+      } else if (byte.class == type.internal) {
+        return ColumnMetaData.Rep.BYTE;
+      } else  if (char.class == type.internal) {
+        return ColumnMetaData.Rep.CHARACTER;
+      } else if (short.class == type.internal) {
+        return ColumnMetaData.Rep.SHORT;
+      } else if (int.class == type.internal) {
+        return ColumnMetaData.Rep.INTEGER;
+      } else if (long.class == type.internal) {
+        return ColumnMetaData.Rep.LONG;
+      } else if (float.class == type.internal) {
+        return ColumnMetaData.Rep.FLOAT;
+      } else if (double.class == type.internal) {
+        return ColumnMetaData.Rep.DOUBLE;
+      }
+      return ColumnMetaData.Rep.of(type.internal);
+    }
   }
 
   /** Base class for a column type. */
@@ -465,6 +533,10 @@ public class ColumnMetaData {
       return SqlType.valueOf(id).boxedClass().getName();
     }
 
+    public String getName() {
+      return name;
+    }
+
     public AvaticaType setRep(Rep rep) {
       throw new UnsupportedOperationException();
     }
@@ -563,7 +635,7 @@ public class ColumnMetaData {
 
   /** Array type. */
   public static class ArrayType extends AvaticaType {
-    public final AvaticaType component;
+    private AvaticaType component;
 
     /**
      * Not for public use. Use {@link ColumnMetaData#array(AvaticaType, String, Rep)}.
@@ -575,6 +647,19 @@ public class ColumnMetaData {
       this.component = component;
     }
 
+    /**
+     * Updates the component of {@code this} to the given value. This is necessary to provide as
+     * accurate-as-possible of an {@code ArrayType} in the {@code Signature}. It cannot be done
+     * at initial construction of this object.
+     */
+    public void updateComponentType(AvaticaType component) {
+      this.component = Objects.requireNonNull(component);
+    }
+
+    public AvaticaType getComponent() {
+      return component;
+    }
+
     @Override public Common.AvaticaType toProto() {
       Common.AvaticaType.Builder builder = Common.AvaticaType.newBuilder(super.toProto());
 

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/dd65a2b1/core/src/main/java/org/apache/calcite/avatica/Meta.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/avatica/Meta.java b/core/src/main/java/org/apache/calcite/avatica/Meta.java
index 7df9ade..a71214d 100644
--- a/core/src/main/java/org/apache/calcite/avatica/Meta.java
+++ b/core/src/main/java/org/apache/calcite/avatica/Meta.java
@@ -203,7 +203,7 @@ public interface Meta {
    * requires to be not null; derived classes may instead choose to execute the
    * relational expression in {@code signature}. */
   Iterable<Object> createIterable(StatementHandle stmt, QueryState state, Signature signature,
-      List<TypedValue> parameterValues, Frame firstFrame);
+      List<TypedValue> parameters, Frame firstFrame);
 
   /** Prepares a statement.
    *
@@ -929,47 +929,55 @@ public interface Meta {
           continue;
         }
 
+        final Common.Row.Builder rowBuilder = Common.Row.newBuilder();
         if (row instanceof Object[]) {
-          final Common.Row.Builder rowBuilder = Common.Row.newBuilder();
-
+          // If only Object[] was also Iterable.
           for (Object element : (Object[]) row) {
-            final Common.ColumnValue.Builder columnBuilder = Common.ColumnValue.newBuilder();
-
-            if (element instanceof List) {
-              columnBuilder.setHasArrayValue(true);
-              List<?> list = (List<?>) element;
-              // Add each element in the list/array to the column's value
-              for (Object listItem : list) {
-                final Common.TypedValue scalarListItem = serializeScalar(listItem);
-                columnBuilder.addArrayValue(scalarListItem);
-                // Add the deprecated 'value' repeated attribute for backwards compat
-                columnBuilder.addValue(scalarListItem);
-              }
-            } else {
-              // The default value, but still explicit.
-              columnBuilder.setHasArrayValue(false);
-              // Only one value for this column, a scalar.
-              final Common.TypedValue scalarVal = serializeScalar(element);
-              columnBuilder.setScalarValue(scalarVal);
-              // Add the deprecated 'value' repeated attribute for backwards compat
-              columnBuilder.addValue(scalarVal);
-            }
-
-            // Add value to row
-            rowBuilder.addValue(columnBuilder.build());
+            parseColumn(rowBuilder, element);
+          }
+        } else if (row instanceof Iterable) {
+          for (Object element : (Iterable<?>) row) {
+            parseColumn(rowBuilder, element);
           }
-
-          // Collect all rows
-          builder.addRows(rowBuilder.build());
         } else {
           // Can a "row" be a primitive? A struct? Only an Array?
           throw new RuntimeException("Only arrays are supported");
         }
+
+        // Collect all rows
+        builder.addRows(rowBuilder.build());
       }
 
       return builder.build();
     }
 
+    static void parseColumn(Common.Row.Builder rowBuilder, Object column) {
+      final Common.ColumnValue.Builder columnBuilder = Common.ColumnValue.newBuilder();
+
+      if (column instanceof List) {
+        columnBuilder.setHasArrayValue(true);
+        List<?> list = (List<?>) column;
+        // Add each element in the list/array to the column's value
+        for (Object listItem : list) {
+          final Common.TypedValue scalarListItem = serializeScalar(listItem);
+          columnBuilder.addArrayValue(scalarListItem);
+          // Add the deprecated 'value' repeated attribute for backwards compat
+          columnBuilder.addValue(scalarListItem);
+        }
+      } else {
+        // The default value, but still explicit.
+        columnBuilder.setHasArrayValue(false);
+        // Only one value for this column, a scalar.
+        final Common.TypedValue scalarVal = serializeScalar(column);
+        columnBuilder.setScalarValue(scalarVal);
+        // Add the deprecated 'value' repeated attribute for backwards compat
+        columnBuilder.addValue(scalarVal);
+      }
+
+      // Add value to row
+      rowBuilder.addValue(columnBuilder.build());
+    }
+
     static Common.TypedValue serializeScalar(Object element) {
       final Common.TypedValue.Builder valueBuilder = Common.TypedValue.newBuilder();
 
@@ -1046,10 +1054,17 @@ public interface Meta {
       validateColumnValue(column);
 
       if (!column.hasField(SCALAR_VALUE_DESCRIPTOR)) {
-        // Array
+        // The column in this row is an Array (has multiple values)
         List<Object> array = new ArrayList<>(column.getArrayValueCount());
         for (Common.TypedValue arrayValue : column.getArrayValueList()) {
-          array.add(deserializeScalarValue(arrayValue));
+          // Duplicative because of the ColumnValue/TypedValue difference.
+          if (Common.Rep.ARRAY == arrayValue.getType()) {
+            // Each element in this Array is an Array.
+            array.add(parseArray(arrayValue));
+          } else {
+            // The array element is a scalar.
+            array.add(deserializeScalarValue(arrayValue));
+          }
         }
         return array;
       } else {
@@ -1059,6 +1074,23 @@ public interface Meta {
     }
 
     /**
+     * Recursively parses a TypedValue while it is an array.
+     */
+    static Object parseArray(Common.TypedValue array) {
+      List<Object> convertedArray = new ArrayList<>(array.getArrayValueCount());
+      for (Common.TypedValue arrayElement : array.getArrayValueList()) {
+        if (Common.Rep.ARRAY == arrayElement.getType()) {
+          // Recurse
+          convertedArray.add(parseArray(arrayElement));
+        } else {
+          // The component type of this array is a scalar.
+          convertedArray.add(deserializeScalarValue(arrayElement));
+        }
+      }
+      return convertedArray;
+    }
+
+    /**
      * Verifies that a ColumnValue has only a scalar or array value, not both and not neither.
      *
      * @param column The protobuf ColumnValue object

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/dd65a2b1/core/src/main/java/org/apache/calcite/avatica/MetaImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/avatica/MetaImpl.java b/core/src/main/java/org/apache/calcite/avatica/MetaImpl.java
index 8870e0c..f663ebb 100644
--- a/core/src/main/java/org/apache/calcite/avatica/MetaImpl.java
+++ b/core/src/main/java/org/apache/calcite/avatica/MetaImpl.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.avatica;
 
+import org.apache.calcite.avatica.ColumnMetaData.AvaticaType;
 import org.apache.calcite.avatica.remote.TypedValue;
 import org.apache.calcite.avatica.util.ArrayIteratorCursor;
 import org.apache.calcite.avatica.util.Cursor;
@@ -222,11 +223,18 @@ public abstract class MetaImpl implements Meta {
         Frame.EMPTY);
   }
 
+  private static int intForColumnNullable(boolean nullable) {
+    return nullable ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls;
+  }
+
   public static ColumnMetaData columnMetaData(String name, int index,
       Class<?> type, boolean columnNullable) {
-    return columnMetaData(name, index, type, columnNullable
-        ? DatabaseMetaData.columnNullable
-        : DatabaseMetaData.columnNoNulls);
+    return columnMetaData(name, index, type, intForColumnNullable(columnNullable));
+  }
+
+  public static ColumnMetaData columnMetaData(String name, int index, AvaticaType type,
+      boolean columnNullable) {
+    return columnMetaData(name, index, type, intForColumnNullable(columnNullable));
   }
 
   public static ColumnMetaData columnMetaData(String name, int index,
@@ -236,12 +244,17 @@ public abstract class MetaImpl implements Meta {
         ColumnMetaData.Rep.VALUE_MAP.get(type);
     ColumnMetaData.AvaticaType scalarType =
         ColumnMetaData.scalar(pair.sqlType, pair.sqlTypeName, rep);
+    return columnMetaData(name, index, scalarType, columnNullable);
+  }
+
+  public static ColumnMetaData columnMetaData(String name, int index, AvaticaType type,
+      int columnNullable) {
     return new ColumnMetaData(
         index, false, true, false, false,
         columnNullable,
         true, -1, name, name, null,
-        0, 0, null, null, scalarType, true, false, false,
-        scalarType.columnClassName());
+        0, 0, null, null, type, true, false, false,
+        type.columnClassName());
   }
 
   protected static ColumnMetaData.StructType fieldMetaData(Class<?> clazz) {
@@ -1448,6 +1461,8 @@ public abstract class MetaImpl implements Meta {
 
   @Override public Iterable<Object> createIterable(StatementHandle handle, QueryState state,
       Signature signature, List<TypedValue> parameterValues, Frame firstFrame) {
+    // `parameterValues` is intentionally unusued (in method signature for historic reasons)
+    // Left to preserve API compatibility with Calcite
     if (firstFrame != null && firstFrame.done) {
       return firstFrame.rows;
     }
@@ -1457,8 +1472,7 @@ public abstract class MetaImpl implements Meta {
     } catch (SQLException e) {
       throw new RuntimeException(e);
     }
-    return new FetchIterable(stmt, state,
-        firstFrame, parameterValues);
+    return new FetchIterable(stmt, state, firstFrame);
   }
 
   public Frame fetch(AvaticaStatement stmt, List<TypedValue> parameterValues,
@@ -1527,18 +1541,15 @@ public abstract class MetaImpl implements Meta {
     private final AvaticaStatement stmt;
     private final QueryState state;
     private final Frame firstFrame;
-    private final List<TypedValue> parameterValues;
 
-    public FetchIterable(AvaticaStatement stmt, QueryState state, Frame firstFrame,
-        List<TypedValue> parameterValues) {
+    public FetchIterable(AvaticaStatement stmt, QueryState state, Frame firstFrame) {
       this.stmt = stmt;
       this.state = state;
       this.firstFrame = firstFrame;
-      this.parameterValues = parameterValues;
     }
 
     public Iterator<Object> iterator() {
-      return new FetchIterator(stmt, state, firstFrame, parameterValues);
+      return new FetchIterator(stmt, state, firstFrame);
     }
   }
 
@@ -1548,16 +1559,11 @@ public abstract class MetaImpl implements Meta {
     private final QueryState state;
     private Frame frame;
     private Iterator<Object> rows;
-    private List<TypedValue> parameterValues;
-    private List<TypedValue> originalParameterValues;
     private long currentOffset = 0;
 
-    public FetchIterator(AvaticaStatement stmt, QueryState state, Frame firstFrame,
-        List<TypedValue> parameterValues) {
+    public FetchIterator(AvaticaStatement stmt, QueryState state, Frame firstFrame) {
       this.stmt = stmt;
       this.state = state;
-      this.parameterValues = parameterValues;
-      this.originalParameterValues = parameterValues;
       if (firstFrame == null) {
         frame = Frame.MORE;
         rows = EmptyIterator.INSTANCE;
@@ -1620,7 +1626,6 @@ public abstract class MetaImpl implements Meta {
           // Kick back to the top to try to fetch again (in both branches)
           continue;
         }
-        parameterValues = null; // don't execute next time
         if (frame == null) {
           rows = null;
           break;
@@ -1632,8 +1637,6 @@ public abstract class MetaImpl implements Meta {
     }
 
     private void resetStatement() {
-      // If we have to reset the statement, we need to reset the parameterValues too
-      parameterValues = originalParameterValues;
       // Defer to the statement to reset itself
       stmt.resetStatement();
     }

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/dd65a2b1/core/src/main/java/org/apache/calcite/avatica/proto/Common.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/avatica/proto/Common.java b/core/src/main/java/org/apache/calcite/avatica/proto/Common.java
index 892608e..50450f4 100644
--- a/core/src/main/java/org/apache/calcite/avatica/proto/Common.java
+++ b/core/src/main/java/org/apache/calcite/avatica/proto/Common.java
@@ -14535,6 +14535,67 @@ public final class Common {
      * <code>optional bool null = 7;</code>
      */
     boolean getNull();
+
+    /**
+     * <pre>
+     * The Array
+     * </pre>
+     *
+     * <code>repeated .TypedValue array_value = 8;</code>
+     */
+    java.util.List<org.apache.calcite.avatica.proto.Common.TypedValue> 
+        getArrayValueList();
+    /**
+     * <pre>
+     * The Array
+     * </pre>
+     *
+     * <code>repeated .TypedValue array_value = 8;</code>
+     */
+    org.apache.calcite.avatica.proto.Common.TypedValue getArrayValue(int index);
+    /**
+     * <pre>
+     * The Array
+     * </pre>
+     *
+     * <code>repeated .TypedValue array_value = 8;</code>
+     */
+    int getArrayValueCount();
+    /**
+     * <pre>
+     * The Array
+     * </pre>
+     *
+     * <code>repeated .TypedValue array_value = 8;</code>
+     */
+    java.util.List<? extends org.apache.calcite.avatica.proto.Common.TypedValueOrBuilder> 
+        getArrayValueOrBuilderList();
+    /**
+     * <pre>
+     * The Array
+     * </pre>
+     *
+     * <code>repeated .TypedValue array_value = 8;</code>
+     */
+    org.apache.calcite.avatica.proto.Common.TypedValueOrBuilder getArrayValueOrBuilder(
+        int index);
+
+    /**
+     * <pre>
+     * If an Array, the representation for the array values
+     * </pre>
+     *
+     * <code>optional .Rep component_type = 9;</code>
+     */
+    int getComponentTypeValue();
+    /**
+     * <pre>
+     * If an Array, the representation for the array values
+     * </pre>
+     *
+     * <code>optional .Rep component_type = 9;</code>
+     */
+    org.apache.calcite.avatica.proto.Common.Rep getComponentType();
   }
   /**
    * <pre>
@@ -14559,6 +14620,8 @@ public final class Common {
       bytesValue_ = com.google.protobuf.ByteString.EMPTY;
       doubleValue_ = 0D;
       null_ = false;
+      arrayValue_ = java.util.Collections.emptyList();
+      componentType_ = 0;
     }
 
     @java.lang.Override
@@ -14623,6 +14686,21 @@ public final class Common {
               null_ = input.readBool();
               break;
             }
+            case 66: {
+              if (!((mutable_bitField0_ & 0x00000080) == 0x00000080)) {
+                arrayValue_ = new java.util.ArrayList<org.apache.calcite.avatica.proto.Common.TypedValue>();
+                mutable_bitField0_ |= 0x00000080;
+              }
+              arrayValue_.add(
+                  input.readMessage(org.apache.calcite.avatica.proto.Common.TypedValue.parser(), extensionRegistry));
+              break;
+            }
+            case 72: {
+              int rawValue = input.readEnum();
+
+              componentType_ = rawValue;
+              break;
+            }
           }
         }
       } catch (com.google.protobuf.InvalidProtocolBufferException e) {
@@ -14631,6 +14709,9 @@ public final class Common {
         throw new com.google.protobuf.InvalidProtocolBufferException(
             e).setUnfinishedMessage(this);
       } finally {
+        if (((mutable_bitField0_ & 0x00000080) == 0x00000080)) {
+          arrayValue_ = java.util.Collections.unmodifiableList(arrayValue_);
+        }
         makeExtensionsImmutable();
       }
     }
@@ -14646,6 +14727,7 @@ public final class Common {
               org.apache.calcite.avatica.proto.Common.TypedValue.class, org.apache.calcite.avatica.proto.Common.TypedValue.Builder.class);
     }
 
+    private int bitField0_;
     public static final int TYPE_FIELD_NUMBER = 1;
     private int type_;
     /**
@@ -14777,6 +14859,85 @@ public final class Common {
       return null_;
     }
 
+    public static final int ARRAY_VALUE_FIELD_NUMBER = 8;
+    private java.util.List<org.apache.calcite.avatica.proto.Common.TypedValue> arrayValue_;
+    /**
+     * <pre>
+     * The Array
+     * </pre>
+     *
+     * <code>repeated .TypedValue array_value = 8;</code>
+     */
+    public java.util.List<org.apache.calcite.avatica.proto.Common.TypedValue> getArrayValueList() {
+      return arrayValue_;
+    }
+    /**
+     * <pre>
+     * The Array
+     * </pre>
+     *
+     * <code>repeated .TypedValue array_value = 8;</code>
+     */
+    public java.util.List<? extends org.apache.calcite.avatica.proto.Common.TypedValueOrBuilder> 
+        getArrayValueOrBuilderList() {
+      return arrayValue_;
+    }
+    /**
+     * <pre>
+     * The Array
+     * </pre>
+     *
+     * <code>repeated .TypedValue array_value = 8;</code>
+     */
+    public int getArrayValueCount() {
+      return arrayValue_.size();
+    }
+    /**
+     * <pre>
+     * The Array
+     * </pre>
+     *
+     * <code>repeated .TypedValue array_value = 8;</code>
+     */
+    public org.apache.calcite.avatica.proto.Common.TypedValue getArrayValue(int index) {
+      return arrayValue_.get(index);
+    }
+    /**
+     * <pre>
+     * The Array
+     * </pre>
+     *
+     * <code>repeated .TypedValue array_value = 8;</code>
+     */
+    public org.apache.calcite.avatica.proto.Common.TypedValueOrBuilder getArrayValueOrBuilder(
+        int index) {
+      return arrayValue_.get(index);
+    }
+
+    public static final int COMPONENT_TYPE_FIELD_NUMBER = 9;
+    private int componentType_;
+    /**
+     * <pre>
+     * If an Array, the representation for the array values
+     * </pre>
+     *
+     * <code>optional .Rep component_type = 9;</code>
+     */
+    public int getComponentTypeValue() {
+      return componentType_;
+    }
+    /**
+     * <pre>
+     * If an Array, the representation for the array values
+     * </pre>
+     *
+     * <code>optional .Rep component_type = 9;</code>
+     */
+    public org.apache.calcite.avatica.proto.Common.Rep getComponentType() {
+      org.apache.calcite.avatica.proto.Common.Rep result = org.apache.calcite.avatica.proto.Common.Rep.valueOf(componentType_);
+      return result == null ? org.apache.calcite.avatica.proto.Common.Rep.UNRECOGNIZED : result;
+    }
+
     private byte memoizedIsInitialized = -1;
     public final boolean isInitialized() {
       byte isInitialized = memoizedIsInitialized;
@@ -14810,6 +14971,12 @@ public final class Common {
       if (null_ != false) {
         output.writeBool(7, null_);
       }
+      for (int i = 0; i < arrayValue_.size(); i++) {
+        output.writeMessage(8, arrayValue_.get(i));
+      }
+      if (componentType_ != org.apache.calcite.avatica.proto.Common.Rep.PRIMITIVE_BOOLEAN.getNumber()) {
+        output.writeEnum(9, componentType_);
+      }
     }
 
     public int getSerializedSize() {
@@ -14844,6 +15011,14 @@ public final class Common {
         size += com.google.protobuf.CodedOutputStream
           .computeBoolSize(7, null_);
       }
+      for (int i = 0; i < arrayValue_.size(); i++) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(8, arrayValue_.get(i));
+      }
+      if (componentType_ != org.apache.calcite.avatica.proto.Common.Rep.PRIMITIVE_BOOLEAN.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(9, componentType_);
+      }
       memoizedSize = size;
       return size;
     }
@@ -14875,6 +15050,9 @@ public final class Common {
               other.getDoubleValue()));
       result = result && (getNull()
           == other.getNull());
+      result = result && getArrayValueList()
+          .equals(other.getArrayValueList());
+      result = result && componentType_ == other.componentType_;
       return result;
     }
 
@@ -14903,6 +15081,12 @@ public final class Common {
       hash = (37 * hash) + NULL_FIELD_NUMBER;
       hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
           getNull());
+      if (getArrayValueCount() > 0) {
+        hash = (37 * hash) + ARRAY_VALUE_FIELD_NUMBER;
+        hash = (53 * hash) + getArrayValueList().hashCode();
+      }
+      hash = (37 * hash) + COMPONENT_TYPE_FIELD_NUMBER;
+      hash = (53 * hash) + componentType_;
       hash = (29 * hash) + unknownFields.hashCode();
       memoizedHashCode = hash;
       return hash;
@@ -15021,6 +15205,7 @@ public final class Common {
       private void maybeForceBuilderInitialization() {
         if (com.google.protobuf.GeneratedMessageV3
                 .alwaysUseFieldBuilders) {
+          getArrayValueFieldBuilder();
         }
       }
       public Builder clear() {
@@ -15039,6 +15224,14 @@ public final class Common {
 
         null_ = false;
 
+        if (arrayValueBuilder_ == null) {
+          arrayValue_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000080);
+        } else {
+          arrayValueBuilder_.clear();
+        }
+        componentType_ = 0;
+
         return this;
       }
 
@@ -15061,6 +15254,8 @@ public final class Common {
 
       public org.apache.calcite.avatica.proto.Common.TypedValue buildPartial() {
         org.apache.calcite.avatica.proto.Common.TypedValue result = new org.apache.calcite.avatica.proto.Common.TypedValue(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
         result.type_ = type_;
         result.boolValue_ = boolValue_;
         result.stringValue_ = stringValue_;
@@ -15068,6 +15263,17 @@ public final class Common {
         result.bytesValue_ = bytesValue_;
         result.doubleValue_ = doubleValue_;
         result.null_ = null_;
+        if (arrayValueBuilder_ == null) {
+          if (((bitField0_ & 0x00000080) == 0x00000080)) {
+            arrayValue_ = java.util.Collections.unmodifiableList(arrayValue_);
+            bitField0_ = (bitField0_ & ~0x00000080);
+          }
+          result.arrayValue_ = arrayValue_;
+        } else {
+          result.arrayValue_ = arrayValueBuilder_.build();
+        }
+        result.componentType_ = componentType_;
+        result.bitField0_ = to_bitField0_;
         onBuilt();
         return result;
       }
@@ -15131,6 +15337,35 @@ public final class Common {
         if (other.getNull() != false) {
           setNull(other.getNull());
         }
+        if (arrayValueBuilder_ == null) {
+          if (!other.arrayValue_.isEmpty()) {
+            if (arrayValue_.isEmpty()) {
+              arrayValue_ = other.arrayValue_;
+              bitField0_ = (bitField0_ & ~0x00000080);
+            } else {
+              ensureArrayValueIsMutable();
+              arrayValue_.addAll(other.arrayValue_);
+            }
+            onChanged();
+          }
+        } else {
+          if (!other.arrayValue_.isEmpty()) {
+            if (arrayValueBuilder_.isEmpty()) {
+              arrayValueBuilder_.dispose();
+              arrayValueBuilder_ = null;
+              arrayValue_ = other.arrayValue_;
+              bitField0_ = (bitField0_ & ~0x00000080);
+              arrayValueBuilder_ = 
+                com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                   getArrayValueFieldBuilder() : null;
+            } else {
+              arrayValueBuilder_.addAllMessages(other.arrayValue_);
+            }
+          }
+        }
+        if (other.componentType_ != 0) {
+          setComponentTypeValue(other.getComponentTypeValue());
+        }
         onChanged();
         return this;
       }
@@ -15156,6 +15391,7 @@ public final class Common {
         }
         return this;
       }
+      private int bitField0_;
 
       private int type_ = 0;
       /**
@@ -15502,6 +15738,382 @@ public final class Common {
         onChanged();
         return this;
       }
+
+      private java.util.List<org.apache.calcite.avatica.proto.Common.TypedValue> arrayValue_ =
+        java.util.Collections.emptyList();
+      private void ensureArrayValueIsMutable() {
+        if (!((bitField0_ & 0x00000080) == 0x00000080)) {
+          arrayValue_ = new java.util.ArrayList<org.apache.calcite.avatica.proto.Common.TypedValue>(arrayValue_);
+          bitField0_ |= 0x00000080;
+         }
+      }
+
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          org.apache.calcite.avatica.proto.Common.TypedValue, org.apache.calcite.avatica.proto.Common.TypedValue.Builder, org.apache.calcite.avatica.proto.Common.TypedValueOrBuilder> arrayValueBuilder_;
+
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public java.util.List<org.apache.calcite.avatica.proto.Common.TypedValue> getArrayValueList() {
+        if (arrayValueBuilder_ == null) {
+          return java.util.Collections.unmodifiableList(arrayValue_);
+        } else {
+          return arrayValueBuilder_.getMessageList();
+        }
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public int getArrayValueCount() {
+        if (arrayValueBuilder_ == null) {
+          return arrayValue_.size();
+        } else {
+          return arrayValueBuilder_.getCount();
+        }
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public org.apache.calcite.avatica.proto.Common.TypedValue getArrayValue(int index) {
+        if (arrayValueBuilder_ == null) {
+          return arrayValue_.get(index);
+        } else {
+          return arrayValueBuilder_.getMessage(index);
+        }
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public Builder setArrayValue(
+          int index, org.apache.calcite.avatica.proto.Common.TypedValue value) {
+        if (arrayValueBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureArrayValueIsMutable();
+          arrayValue_.set(index, value);
+          onChanged();
+        } else {
+          arrayValueBuilder_.setMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public Builder setArrayValue(
+          int index, org.apache.calcite.avatica.proto.Common.TypedValue.Builder builderForValue) {
+        if (arrayValueBuilder_ == null) {
+          ensureArrayValueIsMutable();
+          arrayValue_.set(index, builderForValue.build());
+          onChanged();
+        } else {
+          arrayValueBuilder_.setMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public Builder addArrayValue(org.apache.calcite.avatica.proto.Common.TypedValue value) {
+        if (arrayValueBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureArrayValueIsMutable();
+          arrayValue_.add(value);
+          onChanged();
+        } else {
+          arrayValueBuilder_.addMessage(value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public Builder addArrayValue(
+          int index, org.apache.calcite.avatica.proto.Common.TypedValue value) {
+        if (arrayValueBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureArrayValueIsMutable();
+          arrayValue_.add(index, value);
+          onChanged();
+        } else {
+          arrayValueBuilder_.addMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public Builder addArrayValue(
+          org.apache.calcite.avatica.proto.Common.TypedValue.Builder builderForValue) {
+        if (arrayValueBuilder_ == null) {
+          ensureArrayValueIsMutable();
+          arrayValue_.add(builderForValue.build());
+          onChanged();
+        } else {
+          arrayValueBuilder_.addMessage(builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public Builder addArrayValue(
+          int index, org.apache.calcite.avatica.proto.Common.TypedValue.Builder builderForValue) {
+        if (arrayValueBuilder_ == null) {
+          ensureArrayValueIsMutable();
+          arrayValue_.add(index, builderForValue.build());
+          onChanged();
+        } else {
+          arrayValueBuilder_.addMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public Builder addAllArrayValue(
+          java.lang.Iterable<? extends org.apache.calcite.avatica.proto.Common.TypedValue> values) {
+        if (arrayValueBuilder_ == null) {
+          ensureArrayValueIsMutable();
+          com.google.protobuf.AbstractMessageLite.Builder.addAll(
+              values, arrayValue_);
+          onChanged();
+        } else {
+          arrayValueBuilder_.addAllMessages(values);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public Builder clearArrayValue() {
+        if (arrayValueBuilder_ == null) {
+          arrayValue_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000080);
+          onChanged();
+        } else {
+          arrayValueBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public Builder removeArrayValue(int index) {
+        if (arrayValueBuilder_ == null) {
+          ensureArrayValueIsMutable();
+          arrayValue_.remove(index);
+          onChanged();
+        } else {
+          arrayValueBuilder_.remove(index);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public org.apache.calcite.avatica.proto.Common.TypedValue.Builder getArrayValueBuilder(
+          int index) {
+        return getArrayValueFieldBuilder().getBuilder(index);
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public org.apache.calcite.avatica.proto.Common.TypedValueOrBuilder getArrayValueOrBuilder(
+          int index) {
+        if (arrayValueBuilder_ == null) {
+          return arrayValue_.get(index);  } else {
+          return arrayValueBuilder_.getMessageOrBuilder(index);
+        }
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public java.util.List<? extends org.apache.calcite.avatica.proto.Common.TypedValueOrBuilder> 
+           getArrayValueOrBuilderList() {
+        if (arrayValueBuilder_ != null) {
+          return arrayValueBuilder_.getMessageOrBuilderList();
+        } else {
+          return java.util.Collections.unmodifiableList(arrayValue_);
+        }
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public org.apache.calcite.avatica.proto.Common.TypedValue.Builder addArrayValueBuilder() {
+        return getArrayValueFieldBuilder().addBuilder(
+            org.apache.calcite.avatica.proto.Common.TypedValue.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public org.apache.calcite.avatica.proto.Common.TypedValue.Builder addArrayValueBuilder(
+          int index) {
+        return getArrayValueFieldBuilder().addBuilder(
+            index, org.apache.calcite.avatica.proto.Common.TypedValue.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * The Array
+       * </pre>
+       *
+       * <code>repeated .TypedValue array_value = 8;</code>
+       */
+      public java.util.List<org.apache.calcite.avatica.proto.Common.TypedValue.Builder> 
+           getArrayValueBuilderList() {
+        return getArrayValueFieldBuilder().getBuilderList();
+      }
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          org.apache.calcite.avatica.proto.Common.TypedValue, org.apache.calcite.avatica.proto.Common.TypedValue.Builder, org.apache.calcite.avatica.proto.Common.TypedValueOrBuilder> 
+          getArrayValueFieldBuilder() {
+        if (arrayValueBuilder_ == null) {
+          arrayValueBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+              org.apache.calcite.avatica.proto.Common.TypedValue, org.apache.calcite.avatica.proto.Common.TypedValue.Builder, org.apache.calcite.avatica.proto.Common.TypedValueOrBuilder>(
+                  arrayValue_,
+                  ((bitField0_ & 0x00000080) == 0x00000080),
+                  getParentForChildren(),
+                  isClean());
+          arrayValue_ = null;
+        }
+        return arrayValueBuilder_;
+      }
+
+      private int componentType_ = 0;
+      /**
+       * <pre>
+       * If an Array, the representation for the array values
+       * </pre>
+       *
+       * <code>optional .Rep component_type = 9;</code>
+       */
+      public int getComponentTypeValue() {
+        return componentType_;
+      }
+      /**
+       * <pre>
+       * If an Array, the representation for the array values
+       * </pre>
+       *
+       * <code>optional .Rep component_type = 9;</code>
+       */
+      public Builder setComponentTypeValue(int value) {
+        componentType_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * If an Array, the representation for the array values
+       * </pre>
+       *
+       * <code>optional .Rep component_type = 9;</code>
+       */
+      public org.apache.calcite.avatica.proto.Common.Rep getComponentType() {
+        org.apache.calcite.avatica.proto.Common.Rep result = org.apache.calcite.avatica.proto.Common.Rep.valueOf(componentType_);
+        return result == null ? org.apache.calcite.avatica.proto.Common.Rep.UNRECOGNIZED : result;
+      }
+      /**
+       * <pre>
+       * If an Array, the representation for the array values
+       * </pre>
+       *
+       * <code>optional .Rep component_type = 9;</code>
+       */
+      public Builder setComponentType(org.apache.calcite.avatica.proto.Common.Rep value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        componentType_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * If an Array, the representation for the array values
+       * </pre>
+       *
+       * <code>optional .Rep component_type = 9;</code>
+       */
+      public Builder clearComponentType() {
+        
+        componentType_ = 0;
+        onChanged();
+        return this;
+      }
       public final Builder setUnknownFields(
           final com.google.protobuf.UnknownFieldSet unknownFields) {
         return this;
@@ -18158,58 +18770,60 @@ public final class Common {
       " \001(\014\"\207\001\n\013ColumnValue\022\032\n\005value\030\001 \003(\0132\013.Ty",
       "pedValue\022 \n\013array_value\030\002 \003(\0132\013.TypedVal" +
       "ue\022\027\n\017has_array_value\030\003 \001(\010\022!\n\014scalar_va" +
-      "lue\030\004 \001(\0132\013.TypedValue\"\231\001\n\nTypedValue\022\022\n" +
+      "lue\030\004 \001(\0132\013.TypedValue\"\331\001\n\nTypedValue\022\022\n" +
       "\004type\030\001 \001(\0162\004.Rep\022\022\n\nbool_value\030\002 \001(\010\022\024\n" +
       "\014string_value\030\003 \001(\t\022\024\n\014number_value\030\004 \001(" +
       "\022\022\023\n\013bytes_value\030\005 \001(\014\022\024\n\014double_value\030\006" +
-      " \001(\001\022\014\n\004null\030\007 \001(\010\"\246\002\n\031MetaDataOperation" +
-      "Argument\022\024\n\014string_value\030\001 \001(\t\022\022\n\nbool_v" +
-      "alue\030\002 \001(\010\022\021\n\tint_value\030\003 \001(\021\022\033\n\023string_" +
-      "array_values\030\004 \003(\t\022\030\n\020int_array_values\030\005",
-      " \003(\021\0225\n\004type\030\006 \001(\0162\'.MetaDataOperationAr" +
-      "gument.ArgumentType\"^\n\014ArgumentType\022\n\n\006S" +
-      "TRING\020\000\022\010\n\004BOOL\020\001\022\007\n\003INT\020\002\022\023\n\017REPEATED_S" +
-      "TRING\020\003\022\020\n\014REPEATED_INT\020\004\022\010\n\004NULL\020\005\"\260\001\n\n" +
-      "QueryState\022\030\n\004type\030\001 \001(\0162\n.StateType\022\013\n\003" +
-      "sql\030\002 \001(\t\022\036\n\002op\030\003 \001(\0162\022.MetaDataOperatio" +
-      "n\022(\n\004args\030\004 \003(\0132\032.MetaDataOperationArgum" +
-      "ent\022\020\n\010has_args\030\005 \001(\010\022\017\n\007has_sql\030\006 \001(\010\022\016" +
-      "\n\006has_op\030\007 \001(\010*\237\001\n\rStatementType\022\n\n\006SELE" +
-      "CT\020\000\022\n\n\006INSERT\020\001\022\n\n\006UPDATE\020\002\022\n\n\006DELETE\020\003",
-      "\022\n\n\006UPSERT\020\004\022\t\n\005MERGE\020\005\022\r\n\tOTHER_DML\020\006\022\n" +
-      "\n\006CREATE\020\007\022\010\n\004DROP\020\010\022\t\n\005ALTER\020\t\022\r\n\tOTHER" +
-      "_DDL\020\n\022\010\n\004CALL\020\013*\342\003\n\003Rep\022\025\n\021PRIMITIVE_BO" +
-      "OLEAN\020\000\022\022\n\016PRIMITIVE_BYTE\020\001\022\022\n\016PRIMITIVE" +
-      "_CHAR\020\002\022\023\n\017PRIMITIVE_SHORT\020\003\022\021\n\rPRIMITIV" +
-      "E_INT\020\004\022\022\n\016PRIMITIVE_LONG\020\005\022\023\n\017PRIMITIVE" +
-      "_FLOAT\020\006\022\024\n\020PRIMITIVE_DOUBLE\020\007\022\013\n\007BOOLEA" +
-      "N\020\010\022\010\n\004BYTE\020\t\022\r\n\tCHARACTER\020\n\022\t\n\005SHORT\020\013\022" +
-      "\013\n\007INTEGER\020\014\022\010\n\004LONG\020\r\022\t\n\005FLOAT\020\016\022\n\n\006DOU" +
-      "BLE\020\017\022\017\n\013BIG_INTEGER\020\031\022\017\n\013BIG_DECIMAL\020\032\022",
-      "\021\n\rJAVA_SQL_TIME\020\020\022\026\n\022JAVA_SQL_TIMESTAMP" +
-      "\020\021\022\021\n\rJAVA_SQL_DATE\020\022\022\022\n\016JAVA_UTIL_DATE\020" +
-      "\023\022\017\n\013BYTE_STRING\020\024\022\n\n\006STRING\020\025\022\n\n\006NUMBER" +
-      "\020\026\022\n\n\006OBJECT\020\027\022\010\n\004NULL\020\030\022\t\n\005ARRAY\020\033\022\n\n\006S" +
-      "TRUCT\020\034\022\014\n\010MULTISET\020\035*^\n\010Severity\022\024\n\020UNK" +
-      "NOWN_SEVERITY\020\000\022\022\n\016FATAL_SEVERITY\020\001\022\022\n\016E" +
-      "RROR_SEVERITY\020\002\022\024\n\020WARNING_SEVERITY\020\003*\327\004" +
-      "\n\021MetaDataOperation\022\022\n\016GET_ATTRIBUTES\020\000\022" +
-      "\033\n\027GET_BEST_ROW_IDENTIFIER\020\001\022\020\n\014GET_CATA" +
-      "LOGS\020\002\022\036\n\032GET_CLIENT_INFO_PROPERTIES\020\003\022\031",
-      "\n\025GET_COLUMN_PRIVILEGES\020\004\022\017\n\013GET_COLUMNS" +
-      "\020\005\022\027\n\023GET_CROSS_REFERENCE\020\006\022\025\n\021GET_EXPOR" +
-      "TED_KEYS\020\007\022\030\n\024GET_FUNCTION_COLUMNS\020\010\022\021\n\r" +
-      "GET_FUNCTIONS\020\t\022\025\n\021GET_IMPORTED_KEYS\020\n\022\022" +
-      "\n\016GET_INDEX_INFO\020\013\022\024\n\020GET_PRIMARY_KEYS\020\014" +
-      "\022\031\n\025GET_PROCEDURE_COLUMNS\020\r\022\022\n\016GET_PROCE" +
-      "DURES\020\016\022\026\n\022GET_PSEUDO_COLUMNS\020\017\022\017\n\013GET_S" +
-      "CHEMAS\020\020\022\031\n\025GET_SCHEMAS_WITH_ARGS\020\021\022\024\n\020G" +
-      "ET_SUPER_TABLES\020\022\022\023\n\017GET_SUPER_TYPES\020\023\022\030" +
-      "\n\024GET_TABLE_PRIVILEGES\020\024\022\016\n\nGET_TABLES\020\025",
-      "\022\023\n\017GET_TABLE_TYPES\020\026\022\021\n\rGET_TYPE_INFO\020\027" +
-      "\022\014\n\010GET_UDTS\020\030\022\027\n\023GET_VERSION_COLUMNS\020\031*" +
-      "\"\n\tStateType\022\007\n\003SQL\020\000\022\014\n\010METADATA\020\001B\"\n o" +
-      "rg.apache.calcite.avatica.protob\006proto3"
+      " \001(\001\022\014\n\004null\030\007 \001(\010\022 \n\013array_value\030\010 \003(\0132" +
+      "\013.TypedValue\022\034\n\016component_type\030\t \001(\0162\004.R" +
+      "ep\"\246\002\n\031MetaDataOperationArgument\022\024\n\014stri" +
+      "ng_value\030\001 \001(\t\022\022\n\nbool_value\030\002 \001(\010\022\021\n\tin",
+      "t_value\030\003 \001(\021\022\033\n\023string_array_values\030\004 \003" +
+      "(\t\022\030\n\020int_array_values\030\005 \003(\021\0225\n\004type\030\006 \001" +
+      "(\0162\'.MetaDataOperationArgument.ArgumentT" +
+      "ype\"^\n\014ArgumentType\022\n\n\006STRING\020\000\022\010\n\004BOOL\020" +
+      "\001\022\007\n\003INT\020\002\022\023\n\017REPEATED_STRING\020\003\022\020\n\014REPEA" +
+      "TED_INT\020\004\022\010\n\004NULL\020\005\"\260\001\n\nQueryState\022\030\n\004ty" +
+      "pe\030\001 \001(\0162\n.StateType\022\013\n\003sql\030\002 \001(\t\022\036\n\002op\030" +
+      "\003 \001(\0162\022.MetaDataOperation\022(\n\004args\030\004 \003(\0132" +
+      "\032.MetaDataOperationArgument\022\020\n\010has_args\030" +
+      "\005 \001(\010\022\017\n\007has_sql\030\006 \001(\010\022\016\n\006has_op\030\007 \001(\010*\237",
+      "\001\n\rStatementType\022\n\n\006SELECT\020\000\022\n\n\006INSERT\020\001" +
+      "\022\n\n\006UPDATE\020\002\022\n\n\006DELETE\020\003\022\n\n\006UPSERT\020\004\022\t\n\005" +
+      "MERGE\020\005\022\r\n\tOTHER_DML\020\006\022\n\n\006CREATE\020\007\022\010\n\004DR" +
+      "OP\020\010\022\t\n\005ALTER\020\t\022\r\n\tOTHER_DDL\020\n\022\010\n\004CALL\020\013" +
+      "*\342\003\n\003Rep\022\025\n\021PRIMITIVE_BOOLEAN\020\000\022\022\n\016PRIMI" +
+      "TIVE_BYTE\020\001\022\022\n\016PRIMITIVE_CHAR\020\002\022\023\n\017PRIMI" +
+      "TIVE_SHORT\020\003\022\021\n\rPRIMITIVE_INT\020\004\022\022\n\016PRIMI" +
+      "TIVE_LONG\020\005\022\023\n\017PRIMITIVE_FLOAT\020\006\022\024\n\020PRIM" +
+      "ITIVE_DOUBLE\020\007\022\013\n\007BOOLEAN\020\010\022\010\n\004BYTE\020\t\022\r\n" +
+      "\tCHARACTER\020\n\022\t\n\005SHORT\020\013\022\013\n\007INTEGER\020\014\022\010\n\004",
+      "LONG\020\r\022\t\n\005FLOAT\020\016\022\n\n\006DOUBLE\020\017\022\017\n\013BIG_INT" +
+      "EGER\020\031\022\017\n\013BIG_DECIMAL\020\032\022\021\n\rJAVA_SQL_TIME" +
+      "\020\020\022\026\n\022JAVA_SQL_TIMESTAMP\020\021\022\021\n\rJAVA_SQL_D" +
+      "ATE\020\022\022\022\n\016JAVA_UTIL_DATE\020\023\022\017\n\013BYTE_STRING" +
+      "\020\024\022\n\n\006STRING\020\025\022\n\n\006NUMBER\020\026\022\n\n\006OBJECT\020\027\022\010" +
+      "\n\004NULL\020\030\022\t\n\005ARRAY\020\033\022\n\n\006STRUCT\020\034\022\014\n\010MULTI" +
+      "SET\020\035*^\n\010Severity\022\024\n\020UNKNOWN_SEVERITY\020\000\022" +
+      "\022\n\016FATAL_SEVERITY\020\001\022\022\n\016ERROR_SEVERITY\020\002\022" +
+      "\024\n\020WARNING_SEVERITY\020\003*\327\004\n\021MetaDataOperat" +
+      "ion\022\022\n\016GET_ATTRIBUTES\020\000\022\033\n\027GET_BEST_ROW_",
+      "IDENTIFIER\020\001\022\020\n\014GET_CATALOGS\020\002\022\036\n\032GET_CL" +
+      "IENT_INFO_PROPERTIES\020\003\022\031\n\025GET_COLUMN_PRI" +
+      "VILEGES\020\004\022\017\n\013GET_COLUMNS\020\005\022\027\n\023GET_CROSS_" +
+      "REFERENCE\020\006\022\025\n\021GET_EXPORTED_KEYS\020\007\022\030\n\024GE" +
+      "T_FUNCTION_COLUMNS\020\010\022\021\n\rGET_FUNCTIONS\020\t\022" +
+      "\025\n\021GET_IMPORTED_KEYS\020\n\022\022\n\016GET_INDEX_INFO" +
+      "\020\013\022\024\n\020GET_PRIMARY_KEYS\020\014\022\031\n\025GET_PROCEDUR" +
+      "E_COLUMNS\020\r\022\022\n\016GET_PROCEDURES\020\016\022\026\n\022GET_P" +
+      "SEUDO_COLUMNS\020\017\022\017\n\013GET_SCHEMAS\020\020\022\031\n\025GET_" +
+      "SCHEMAS_WITH_ARGS\020\021\022\024\n\020GET_SUPER_TABLES\020",
+      "\022\022\023\n\017GET_SUPER_TYPES\020\023\022\030\n\024GET_TABLE_PRIV" +
+      "ILEGES\020\024\022\016\n\nGET_TABLES\020\025\022\023\n\017GET_TABLE_TY" +
+      "PES\020\026\022\021\n\rGET_TYPE_INFO\020\027\022\014\n\010GET_UDTS\020\030\022\027" +
+      "\n\023GET_VERSION_COLUMNS\020\031*\"\n\tStateType\022\007\n\003" +
+      "SQL\020\000\022\014\n\010METADATA\020\001B\"\n org.apache.calcit" +
+      "e.avatica.protob\006proto3"
     };
     com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
         new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
@@ -18300,7 +18914,7 @@ public final class Common {
     internal_static_TypedValue_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
         internal_static_TypedValue_descriptor,
-        new java.lang.String[] { "Type", "BoolValue", "StringValue", "NumberValue", "BytesValue", "DoubleValue", "Null", });
+        new java.lang.String[] { "Type", "BoolValue", "StringValue", "NumberValue", "BytesValue", "DoubleValue", "Null", "ArrayValue", "ComponentType", });
     internal_static_MetaDataOperationArgument_descriptor =
       getDescriptor().getMessageTypes().get(13);
     internal_static_MetaDataOperationArgument_fieldAccessorTable = new

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/dd65a2b1/core/src/main/java/org/apache/calcite/avatica/remote/Service.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/avatica/remote/Service.java b/core/src/main/java/org/apache/calcite/avatica/remote/Service.java
index bcbc557..8178ecd 100644
--- a/core/src/main/java/org/apache/calcite/avatica/remote/Service.java
+++ b/core/src/main/java/org/apache/calcite/avatica/remote/Service.java
@@ -1045,7 +1045,11 @@ public interface Service {
       if (msg.getHasParameterValues()) {
         values = new ArrayList<>(msg.getParameterValuesCount());
         for (Common.TypedValue valueProto : msg.getParameterValuesList()) {
-          values.add(TypedValue.fromProto(valueProto));
+          if (TypedValue.NULL_PROTO.equals(valueProto)) {
+            values.add(null);
+          } else {
+            values.add(TypedValue.fromProto(valueProto));
+          }
         }
       }
 
@@ -1072,7 +1076,7 @@ public interface Service {
         builder.setHasParameterValues(true);
         for (TypedValue paramValue : parameterValues) {
           if (paramValue == null) {
-            builder.addParameterValues(TypedValue.NULL.toProto());
+            builder.addParameterValues(TypedValue.NULL_PROTO);
           } else {
             builder.addParameterValues(paramValue.toProto());
           }

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/dd65a2b1/core/src/main/java/org/apache/calcite/avatica/remote/TypedValue.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/avatica/remote/TypedValue.java b/core/src/main/java/org/apache/calcite/avatica/remote/TypedValue.java
index 7f1c752..04268bb 100644
--- a/core/src/main/java/org/apache/calcite/avatica/remote/TypedValue.java
+++ b/core/src/main/java/org/apache/calcite/avatica/remote/TypedValue.java
@@ -16,9 +16,13 @@
  */
 package org.apache.calcite.avatica.remote;
 
+import org.apache.calcite.avatica.AvaticaUtils;
 import org.apache.calcite.avatica.ColumnMetaData;
+import org.apache.calcite.avatica.ColumnMetaData.AvaticaType;
 import org.apache.calcite.avatica.ColumnMetaData.Rep;
+import org.apache.calcite.avatica.SqlType;
 import org.apache.calcite.avatica.proto.Common;
+import org.apache.calcite.avatica.util.ArrayFactoryImpl;
 import org.apache.calcite.avatica.util.Base64;
 import org.apache.calcite.avatica.util.ByteString;
 import org.apache.calcite.avatica.util.DateTimeUtils;
@@ -30,6 +34,11 @@ import com.google.protobuf.UnsafeByteOperations;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.sql.Array;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
@@ -101,6 +110,10 @@ import static java.nio.charset.StandardCharsets.UTF_8;
  *     <td>DECIMAL</td>
  *                   <td>BigDecimal</td> <td>Number</td> <td>BigDecimal</td>
  *   </tr>
+ *   <tr>
+ *     <td>ARRAY</td>
+ *                  <td>Array</td> <td>List&lt;Object&gt;</td> <td>List&lt;Object&gt;</td>
+ *   </tr>
  * </table>
  *
  * Note:
@@ -134,6 +147,7 @@ public class TypedValue {
 
   public static final TypedValue NULL =
       new TypedValue(ColumnMetaData.Rep.OBJECT, null);
+  public static final Common.TypedValue NULL_PROTO = NULL.toProto();
 
   /** Type of the value. */
   public final ColumnMetaData.Rep type;
@@ -144,8 +158,16 @@ public class TypedValue {
    * For example, byte arrays are represented as String. */
   public final Object value;
 
+  /** Non-null for ARRAYs, the type of the values stored in the ARRAY. Null for all other cases. */
+  public final ColumnMetaData.Rep componentType;
+
   private TypedValue(ColumnMetaData.Rep rep, Object value) {
+    this(rep, null, value);
+  }
+
+  private TypedValue(ColumnMetaData.Rep rep, ColumnMetaData.Rep componentType, Object value) {
     this.type = rep;
+    this.componentType = componentType;
     this.value = value;
     assert isSerial(rep, value) : "rep: " + rep + ", value: " + value;
   }
@@ -194,7 +216,27 @@ public class TypedValue {
     if (value == null) {
       return NULL;
     }
-    return new TypedValue(rep, jdbcToSerial(rep, value, calendar));
+    final Object serialValue;
+    if (ColumnMetaData.Rep.ARRAY == rep) {
+      // Sanity check that we were given an Array
+      if (null != value && !(value instanceof Array)) {
+        throw new IllegalArgumentException("Provided Rep was ARRAY, but the value was "
+            + value.getClass());
+      }
+      final Array array = (Array) value;
+      try {
+        SqlType type = SqlType.valueOf(array.getBaseType());
+        serialValue = jdbcToSerial(rep, array, calendar, type);
+        // Because an Array may have null entries, we must always return the non-primitive type
+        // variants of the array values.
+        return new TypedValue(rep, Rep.nonPrimitiveRepOf(type), serialValue);
+      } catch (SQLException e) {
+        throw new RuntimeException("Could not extract Array component type", e);
+      }
+    } else {
+      serialValue = jdbcToSerial(rep, value, calendar);
+    }
+    return new TypedValue(rep, serialValue);
   }
 
   /** Creates a TypedValue from a value in JDBC representation,
@@ -251,6 +293,9 @@ public class TypedValue {
           : new BigDecimal(((Number) value).longValue());
     case BYTE_STRING:
       return ByteString.ofBase64((String) value);
+    case ARRAY:
+      //List<Object>
+      return value;
     default:
       throw new IllegalArgumentException("cannot convert " + value + " ("
           + value.getClass() + ") to " + rep);
@@ -266,7 +311,7 @@ public class TypedValue {
     if (value == null) {
       return null;
     }
-    return serialToJdbc(type, value, calendar);
+    return serialToJdbc(type, componentType, value, calendar);
   }
 
   /**
@@ -277,7 +322,8 @@ public class TypedValue {
    * @param calendar A calendar instance
    * @return The JDBC representation of the value.
    */
-  private static Object serialToJdbc(ColumnMetaData.Rep type, Object value, Calendar calendar) {
+  private static Object serialToJdbc(ColumnMetaData.Rep type, ColumnMetaData.Rep componentRep,
+      Object value, Calendar calendar) {
     switch (type) {
     case BYTE_STRING:
       return ByteString.ofBase64((String) value).getBytes();
@@ -291,6 +337,27 @@ public class TypedValue {
       return new java.sql.Time(adjust((Number) value, calendar));
     case JAVA_SQL_TIMESTAMP:
       return new java.sql.Timestamp(adjust((Number) value, calendar));
+    case ARRAY:
+      if (null == value) {
+        return null;
+      }
+      final List<?> list = (List<?>) value;
+      final List<Object> copy = new ArrayList<>(list.size());
+      // Copy the list from the serial representation to a JDBC representation
+      for (Object o : list) {
+        if (null == o) {
+          copy.add(null);
+        } else if (o instanceof TypedValue) {
+          // Protobuf can maintain the TypedValue hierarchy to simplify things
+          copy.add(((TypedValue) o).toJdbc(calendar));
+        } else {
+          // We can't get the above recursion with the JSON serialization
+          copy.add(serialToJdbc(componentRep, null, o, calendar));
+        }
+      }
+      AvaticaType elementType = new AvaticaType(componentRep.typeId, componentRep.name(),
+          componentRep);
+      return new ArrayFactoryImpl(calendar.getTimeZone()).createArray(elementType, copy);
     default:
       return serialToLocal(type, value);
     }
@@ -304,10 +371,18 @@ public class TypedValue {
     return t;
   }
 
+  private static Object jdbcToSerial(ColumnMetaData.Rep rep, Object value,
+      Calendar calendar) {
+    return jdbcToSerial(rep, value, calendar, null);
+  }
+
   /** Converts a value from JDBC format to a type that can be serialized as
    * JSON. */
   private static Object jdbcToSerial(ColumnMetaData.Rep rep, Object value,
-      Calendar calendar) {
+      Calendar calendar, SqlType componentType) {
+    if (null == value) {
+      return null;
+    }
     switch (rep) {
     case BYTE_STRING:
       return new ByteString((byte[]) value).toBase64String();
@@ -327,6 +402,58 @@ public class TypedValue {
       default:
         return t;
       }
+    case ARRAY:
+      Array array = (Array) value;
+      Objects.requireNonNull(componentType, "Component Type must not be null for ARRAYs");
+      try {
+        switch (componentType) {
+        case BINARY:
+        case VARBINARY:
+        case LONGVARBINARY:
+          Object[] byteStrings = (Object[]) array.getArray();
+          List<String> convertedStrings = new ArrayList<>(byteStrings.length);
+          for (Object byteString : byteStrings) {
+            convertedStrings.add(
+                (String) jdbcToSerial(Rep.BYTE_STRING, byteString, calendar, null));
+          }
+          return convertedStrings;
+        case DATE:
+        case TIME:
+          Object[] dates = (Object[]) array.getArray();
+          List<Integer> serializedDates = new ArrayList<>(dates.length);
+          for (Object obj : dates) {
+            Date date = (Date) obj;
+            if (null == obj) {
+              serializedDates.add(null);
+            } else if (componentType == SqlType.DATE) {
+              serializedDates.add((int) jdbcToSerial(Rep.JAVA_SQL_DATE, date, calendar, null));
+            } else if (componentType == SqlType.TIME) {
+              serializedDates.add((int) jdbcToSerial(Rep.JAVA_SQL_TIME, date, calendar, null));
+            } else {
+              throw new RuntimeException("Unexpected type: " + componentType);
+            }
+          }
+          return serializedDates;
+        case TIMESTAMP:
+          Object[] timestamps = (Object[]) array.getArray();
+          List<Long> serializedTimestamps = new ArrayList<>(timestamps.length);
+          for (Object obj : timestamps) {
+            Timestamp timestamp = (Timestamp) obj;
+            if (null == obj) {
+              serializedTimestamps.add(null);
+            } else {
+              serializedTimestamps.add(
+                  (long) jdbcToSerial(Rep.JAVA_SQL_TIMESTAMP, timestamp, calendar, null));
+            }
+          }
+          return serializedTimestamps;
+        default:
+          // Either a primitive array or Object[], converted into List<Object>
+          return AvaticaUtils.primitiveList(array.getArray());
+        }
+      } catch (SQLException e) {
+        throw new RuntimeException("Could not obtain base array object", e);
+      }
     default:
       return value;
     }
@@ -363,6 +490,17 @@ public class TypedValue {
     // Protobuf has an explicit BIG_DECIMAL representation enum value.
     if (Common.Rep.NUMBER == protoRep && value instanceof BigDecimal) {
       protoRep = Common.Rep.BIG_DECIMAL;
+    } else if (Common.Rep.ARRAY == protoRep) {
+      // This column value is an Array (many TypedValue's)
+      builder.setType(Common.Rep.ARRAY);
+      // Get the array component's type
+      Common.Rep protoComponentRep = componentType.toProto();
+      // Set the array's component on the message
+      builder.setComponentType(protoComponentRep);
+      // Serialize that array into the builder
+      @SuppressWarnings("unchecked")
+      List<Object> list = (List<Object>) value;
+      return serializeArray(list, builder, protoComponentRep);
     }
 
     // Serialize the type into the protobuf
@@ -371,6 +509,33 @@ public class TypedValue {
     return builder.build();
   }
 
+  Common.TypedValue serializeArray(List<Object> list, Common.TypedValue.Builder builder,
+      Common.Rep protoArrayComponentRep) {
+    for (Object element : list) {
+      if (element instanceof List) {
+        // We have a list of lists: recursively build up the protobuf
+        @SuppressWarnings("unchecked")
+        List<Object> subList = (List<Object>) element;
+        Common.TypedValue.Builder subListBuilder = Common.TypedValue.newBuilder();
+        // This is "technically" an array, but we just persist the underlying component type
+        subListBuilder.setType(protoArrayComponentRep);
+        Common.TypedValue protoSubList = serializeArray(subList, subListBuilder,
+            protoArrayComponentRep);
+        builder.addArrayValue(protoSubList);
+      } else {
+        // We have a list of "scalars", just serialize the value
+        Common.TypedValue.Builder elementBuilder = Common.TypedValue.newBuilder();
+        if (null == element) {
+          writeToProtoWithType(elementBuilder, null, Common.Rep.NULL);
+        } else {
+          writeToProtoWithType(elementBuilder, element, protoArrayComponentRep);
+        }
+        builder.addArrayValue(elementBuilder.build());
+      }
+    }
+    return builder.build();
+  }
+
   private static void writeToProtoWithType(Common.TypedValue.Builder builder, Object o,
       Common.Rep type) {
     builder.setType(type);
@@ -429,13 +594,31 @@ public class TypedValue {
       return;
     case JAVA_SQL_DATE:
     case JAVA_SQL_TIME:
-      // Persisted as integers
-      builder.setNumberValue(Integer.valueOf((int) o).longValue());
+      long sqlDateOrTime;
+      if (o instanceof java.sql.Date) {
+        sqlDateOrTime = ((java.sql.Date) o).getTime();
+      } else if (o instanceof java.sql.Time) {
+        sqlDateOrTime = ((java.sql.Time) o).getTime();
+      } else if (o instanceof Integer) {
+        sqlDateOrTime = ((Integer) o).longValue();
+      } else {
+        sqlDateOrTime = (long) o;
+      }
+      // Persisted as numbers
+      builder.setNumberValue(sqlDateOrTime);
       return;
     case JAVA_SQL_TIMESTAMP:
     case JAVA_UTIL_DATE:
+      long sqlTimestampOrUtilDate;
+      if (o instanceof java.sql.Timestamp) {
+        sqlTimestampOrUtilDate = ((java.sql.Timestamp) o).getTime();
+      } else if (o instanceof java.util.Date) {
+        sqlTimestampOrUtilDate = ((java.util.Date) o).getTime();
+      } else {
+        sqlTimestampOrUtilDate = (long) o;
+      }
       // Persisted as longs
-      builder.setNumberValue((long) o);
+      builder.setNumberValue(sqlTimestampOrUtilDate);
       return;
     case BIG_INTEGER:
       byte[] byteRep = ((BigInteger) o).toByteArray();
@@ -476,9 +659,10 @@ public class TypedValue {
    */
   public static TypedValue fromProto(Common.TypedValue proto) {
     ColumnMetaData.Rep rep = ColumnMetaData.Rep.fromProto(proto.getType());
+    ColumnMetaData.Rep componentRep = ColumnMetaData.Rep.fromProto(proto.getComponentType());
     Object value = getSerialFromProto(proto);
 
-    return new TypedValue(rep, value);
+    return new TypedValue(rep, componentRep, value);
   }
 
   /**
@@ -547,6 +731,16 @@ public class TypedValue {
       return Long.valueOf(protoValue.getNumberValue());
     case NULL:
       return null;
+    case ARRAY:
+      final List<Common.TypedValue> protoList = protoValue.getArrayValueList();
+      final List<Object> list = new ArrayList<>(protoList.size());
+      for (Common.TypedValue protoElement : protoList) {
+        // Deserialize the TypedValue protobuf into the JDBC type
+        TypedValue listElement = TypedValue.fromProto(protoElement);
+        // Must preserve the TypedValue so serial/jdbc/local conversion can work as expected
+        list.add(listElement);
+      }
+      return list;
     case OBJECT:
       if (protoValue.getNull()) {
         return null;
@@ -569,35 +763,97 @@ public class TypedValue {
    * @param builder The TypedValue protobuf builder
    * @param o The object (value)
    */
-  public static void toProto(Common.TypedValue.Builder builder, Object o) {
+  public static Common.Rep toProto(Common.TypedValue.Builder builder, Object o) {
     // Numbers
     if (o instanceof Byte) {
       writeToProtoWithType(builder, o, Common.Rep.BYTE);
+      return Common.Rep.BYTE;
     } else if (o instanceof Short) {
       writeToProtoWithType(builder, o, Common.Rep.SHORT);
+      return Common.Rep.SHORT;
     } else if (o instanceof Integer) {
       writeToProtoWithType(builder, o, Common.Rep.INTEGER);
+      return Common.Rep.INTEGER;
     } else if (o instanceof Long) {
       writeToProtoWithType(builder, o, Common.Rep.LONG);
+      return Common.Rep.LONG;
     } else if (o instanceof Double) {
       writeToProtoWithType(builder, o, Common.Rep.DOUBLE);
+      return Common.Rep.DOUBLE;
     } else if (o instanceof Float) {
       writeToProtoWithType(builder, ((Float) o).longValue(), Common.Rep.FLOAT);
+      return Common.Rep.FLOAT;
     } else if (o instanceof BigDecimal) {
       writeToProtoWithType(builder, o, Common.Rep.BIG_DECIMAL);
+      return Common.Rep.BIG_DECIMAL;
     // Strings
     } else if (o instanceof String) {
       writeToProtoWithType(builder, o, Common.Rep.STRING);
+      return Common.Rep.STRING;
     } else if (o instanceof Character) {
       writeToProtoWithType(builder, o.toString(), Common.Rep.CHARACTER);
+      return Common.Rep.CHARACTER;
     // Bytes
     } else if (o instanceof byte[]) {
       writeToProtoWithType(builder, o, Common.Rep.BYTE_STRING);
+      return Common.Rep.BYTE_STRING;
     // Boolean
     } else if (o instanceof Boolean) {
       writeToProtoWithType(builder, o, Common.Rep.BOOLEAN);
+      return Common.Rep.BOOLEAN;
+    } else if (o instanceof Timestamp) {
+      writeToProtoWithType(builder, o, Common.Rep.JAVA_SQL_TIMESTAMP);
+      return Common.Rep.JAVA_SQL_TIMESTAMP;
+    } else if (o instanceof Date) {
+      writeToProtoWithType(builder, o, Common.Rep.JAVA_SQL_DATE);
+      return Common.Rep.JAVA_SQL_DATE;
+    } else if (o instanceof Time) {
+      writeToProtoWithType(builder, o, Common.Rep.JAVA_SQL_TIME);
+      return Common.Rep.JAVA_SQL_TIME;
+    } else if (o instanceof List) {
+      // Treat a List as an Array
+      builder.setType(Common.Rep.ARRAY);
+      builder.setComponentType(Common.Rep.OBJECT);
+      boolean setComponentType = false;
+      for (Object listElement : (List<?>) o) {
+        Common.TypedValue.Builder listElementBuilder = Common.TypedValue.newBuilder();
+        // Recurse on each list element
+        Common.Rep componentRep = toProto(listElementBuilder, listElement);
+        if (!setComponentType) {
+          if (Common.Rep.NULL != componentRep) {
+            builder.setComponentType(componentRep);
+          }
+          setComponentType = true;
+        }
+        builder.addArrayValue(listElementBuilder.build());
+      }
+      return Common.Rep.ARRAY;
+    } else if (o instanceof Array) {
+      builder.setType(Common.Rep.ARRAY);
+      Array a = (Array) o;
+      try {
+        ResultSet rs = a.getResultSet();
+        builder.setComponentType(Common.Rep.OBJECT);
+        boolean setComponentType = false;
+        while (rs.next()) {
+          Common.TypedValue.Builder listElementBuilder = Common.TypedValue.newBuilder();
+          Object arrayValue = rs.getObject(2);
+          Common.Rep componentRep = toProto(listElementBuilder, arrayValue);
+          if (!setComponentType) {
+            if (Common.Rep.NULL != componentRep) {
+              builder.setComponentType(componentRep);
+            }
+            setComponentType = true;
+          }
+          builder.addArrayValue(listElementBuilder.build());
+        }
+      } catch (SQLException e) {
+        throw new RuntimeException("Could not serialize ARRAY", e);
+      }
+      return Common.Rep.ARRAY;
     } else if (null == o) {
       writeToProtoWithType(builder, o, Common.Rep.NULL);
+      return Common.Rep.NULL;
     // Unhandled
     } else {
       throw new RuntimeException("Unhandled type in Frame: " + o.getClass());
@@ -617,8 +873,7 @@ public class TypedValue {
     if (null == o) {
       return o;
     }
-    return serialToJdbc(Rep.fromProto(protoValue.getType()), o, calendar);
-    //return protoSerialToJdbc(protoValue.getType(), o, Objects.requireNonNull(calendar));
+    return serialToJdbc(Rep.fromProto(protoValue.getType()), null, o, calendar);
   }
 
   @Override public int hashCode() {


Mime
View raw message