http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/remote/TypedValue.java
----------------------------------------------------------------------
diff --git 
a/avatica/core/src/main/java/org/apache/calcite/avatica/remote/TypedValue.java 
b/avatica/core/src/main/java/org/apache/calcite/avatica/remote/TypedValue.java
new file mode 100644
index 0000000..d96293b
--- /dev/null
+++ 
b/avatica/core/src/main/java/org/apache/calcite/avatica/remote/TypedValue.java
@@ -0,0 +1,533 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.avatica.remote;
+
+import org.apache.calcite.avatica.ColumnMetaData;
+import org.apache.calcite.avatica.proto.Common;
+import org.apache.calcite.avatica.util.ByteString;
+import org.apache.calcite.avatica.util.DateTimeUtils;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.protobuf.HBaseZeroCopyByteString;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+/** Value and type.
+ *
+ * <p>There are 3 representations:
+ * <ul>
+ *   <li>JDBC - the representation used by JDBC get and set methods
+ *   <li>Serial - suitable for serializing using JSON
+ *   <li>Local - used by Calcite for efficient computation
+ * </ul>
+ *
+ * <p>The following table shows the Java type(s) that may represent each SQL
+ * type in each representation.
+ *
+ * <table>
+ *   <caption>SQL types and their representations</caption>
+ *   <tr>
+ *     <th>Type</th> <th>JDBC</th> <th>Serial</th> <th>Local</th>
+ *   </tr>
+ *   <tr>
+ *     <td>BOOLEAN</td> <td>boolean</td> <td>boolean</td> <td>boolean</td>
+ *   </tr>
+ *   <tr>
+ *     <td>BINARY, VARBINARY</td> <td>byte[]</td>
+ *                    <td>String (base64)</td> <td>{@link ByteString}</td>
+ *   </tr>
+ *   <tr>
+ *     <td>DATE</td> <td>{@link java.sql.Date}</td>
+ *                                   <td>int</td> <td>int</td>
+ *   </tr>
+ *   <tr>
+ *     <td>TIME</td> <td>{@link java.sql.Time}</td>
+ *                                   <td>int</td> <td>int</td>
+ *   </tr>
+ *   <tr>
+ *     <td>DATE</td> <td>{@link java.sql.Timestamp}</td>
+ *                                   <td>long</td> <td>long</td>
+ *   </tr>
+ *   <tr>
+ *     <td>CHAR, VARCHAR</td>
+ *                   <td>String</td> <td>String</td> <td>String</td>
+ *   </tr>
+ *   <tr>
+ *     <td>TINYINT</td> <td>byte</td> <td>Number</td> <td>byte</td>
+ *   </tr>
+ *   <tr>
+ *     <td>SMALLINT</td> <td>short</td> <td>Number</td> <td>short</td>
+ *   </tr>
+ *   <tr>
+ *     <td>INTEGER</td> <td>int</td> <td>Number</td> <td>int</td>
+ *   </tr>
+ *   <tr>
+ *     <td>BIGINT</td> <td>long</td> <td>Number</td> <td>long</td>
+ *   </tr>
+ *   <tr>
+ *     <td>REAL</td> <td>float</td> <td>Number</td> <td>float</td>
+ *   </tr>
+ *   <tr>
+ *     <td>FLOAT, DOUBLE</td>
+ *                   <td>double</td> <td>Number</td> <td>double</td>
+ *   </tr>
+ *   <tr>
+ *     <td>DECIMAL</td>
+ *                   <td>BigDecimal</td> <td>Number</td> <td>BigDecimal</td>
+ *   </tr>
+ * </table>
+ *
+ * Note:
+ * <ul>
+ *   <li>The various numeric types (TINYINT, SMALLINT, INTEGER, BIGINT, REAL,
+ *   FLOAT, DOUBLE) are represented by {@link Number} in serial format because
+ *   JSON numbers are not strongly typed. A {@code float} value {@code 3.0} is
+ *   transmitted as {@code 3}, and is therefore decoded as an {@code int}.
+ *
+ *   <li>The date-time types (DATE, TIME, TIMESTAMP) are represented in JDBC as
+ *   {@link java.sql.Date}, {@link java.sql.Time}, {@link java.sql.Timestamp},
+ *   all sub-classes of {@link java.util.Date}. When they are passed to and
+ *   from the server, they are interpreted in terms of a time zone, by default
+ *   the current connection's time zone. Their serial and local representations
+ *   as {@code int} (days since 1970-01-01 for DATE, milliseconds since
+ *   00:00:00.000 for TIME), and long (milliseconds since 1970-01-01
+ *   00:00:00.000 for TIMESTAMP) are easier to work with, because it is clear
+ *   that time zone is not involved.
+ *
+ *   <li>BINARY and VARBINARY values are represented as base64-encoded strings
+ *   for serialization over JSON.
+ * </ul>
+ */
+public class TypedValue {
+  public static final TypedValue NULL =
+      new TypedValue(ColumnMetaData.Rep.OBJECT, null);
+
+  /** Type of the value. */
+  public final ColumnMetaData.Rep type;
+
+  /** Value.
+   *
+   * <p>Always in a form that can be serialized to JSON by Jackson.
+   * For example, byte arrays are represented as String. */
+  public final Object value;
+
+  private TypedValue(ColumnMetaData.Rep rep, Object value) {
+    this.type = rep;
+    this.value = value;
+    assert isSerial(rep, value) : "rep: " + rep + ", value: " + value;
+  }
+
+  private boolean isSerial(ColumnMetaData.Rep rep, Object value) {
+    if (value == null) {
+      return true;
+    }
+    switch (rep) {
+    case BYTE_STRING:
+      return value instanceof String;
+    case JAVA_SQL_DATE:
+    case JAVA_SQL_TIME:
+      return value instanceof Integer;
+    case JAVA_SQL_TIMESTAMP:
+    case JAVA_UTIL_DATE:
+      return value instanceof Long;
+    default:
+      return true;
+    }
+  }
+
+  @JsonCreator
+  public static TypedValue create(@JsonProperty("type") String type,
+      @JsonProperty("value") Object value) {
+    if (value == null) {
+      return NULL;
+    }
+    ColumnMetaData.Rep rep = ColumnMetaData.Rep.valueOf(type);
+    return ofLocal(rep, serialToLocal(rep, value));
+  }
+
+  /** Creates a TypedValue from a value in local representation. */
+  public static TypedValue ofLocal(ColumnMetaData.Rep rep, Object value) {
+    return new TypedValue(rep, localToSerial(rep, value));
+  }
+
+  /** Creates a TypedValue from a value in serial representation. */
+  public static TypedValue ofSerial(ColumnMetaData.Rep rep, Object value) {
+    return new TypedValue(rep, value);
+  }
+
+  /** Creates a TypedValue from a value in JDBC representation. */
+  public static TypedValue ofJdbc(ColumnMetaData.Rep rep, Object value,
+      Calendar calendar) {
+    if (value == null) {
+      return NULL;
+    }
+    return new TypedValue(rep, jdbcToSerial(rep, value, calendar));
+  }
+
+  /** Creates a TypedValue from a value in JDBC representation,
+   * deducing its type. */
+  public static TypedValue ofJdbc(Object value, Calendar calendar) {
+    if (value == null) {
+      return NULL;
+    }
+    final ColumnMetaData.Rep rep = ColumnMetaData.Rep.of(value.getClass());
+    return new TypedValue(rep, jdbcToSerial(rep, value, calendar));
+  }
+
+  /** Converts the value into the local representation.
+   *
+   * <p>For example, a byte string is represented as a {@link ByteString};
+   * a long is represented as a {@link Long} (not just some {@link Number}).
+   */
+  public Object toLocal() {
+    if (value == null) {
+      return null;
+    }
+    return serialToLocal(type, value);
+  }
+
+  /** Converts a value to the exact type required for the given
+   * representation. */
+  private static Object serialToLocal(ColumnMetaData.Rep rep, Object value) {
+    assert value != null;
+    if (value.getClass() == rep.clazz) {
+      return value;
+    }
+    switch (rep) {
+    case BYTE:
+      return ((Number) value).byteValue();
+    case SHORT:
+      return ((Number) value).shortValue();
+    case INTEGER:
+    case JAVA_SQL_DATE:
+    case JAVA_SQL_TIME:
+      return ((Number) value).intValue();
+    case LONG:
+    case JAVA_UTIL_DATE:
+    case JAVA_SQL_TIMESTAMP:
+      return ((Number) value).longValue();
+    case FLOAT:
+      return ((Number) value).floatValue();
+    case DOUBLE:
+      return ((Number) value).doubleValue();
+    case NUMBER:
+      return value instanceof BigDecimal ? value
+          : value instanceof BigInteger ? new BigDecimal((BigInteger) value)
+          : value instanceof Double ? new BigDecimal((Double) value)
+          : value instanceof Float ? new BigDecimal((Float) value)
+          : new BigDecimal(((Number) value).longValue());
+    case BYTE_STRING:
+      return ByteString.ofBase64((String) value);
+    default:
+      throw new IllegalArgumentException("cannot convert " + value + " ("
+          + value.getClass() + ") to " + rep);
+    }
+  }
+
+  /** Converts the value into the JDBC representation.
+   *
+   * <p>For example, a byte string is represented as a {@link ByteString};
+   * a long is represented as a {@link Long} (not just some {@link Number}).
+   */
+  public Object toJdbc(Calendar calendar) {
+    if (value == null) {
+      return null;
+    }
+    return serialToJdbc(type, value, calendar);
+  }
+
+  private static Object serialToJdbc(ColumnMetaData.Rep type, Object value,
+      Calendar calendar) {
+    switch (type) {
+    case BYTE_STRING:
+      return ByteString.ofBase64((String) value).getBytes();
+    case JAVA_UTIL_DATE:
+      return new java.util.Date(adjust((Number) value, calendar));
+    case JAVA_SQL_DATE:
+      return new java.sql.Date(
+          adjust(((Number) value).longValue() * DateTimeUtils.MILLIS_PER_DAY,
+              calendar));
+    case JAVA_SQL_TIME:
+      return new java.sql.Time(adjust((Number) value, calendar));
+    case JAVA_SQL_TIMESTAMP:
+      return new java.sql.Timestamp(adjust((Number) value, calendar));
+    default:
+      return serialToLocal(type, value);
+    }
+  }
+
+  private static long adjust(Number number, Calendar calendar) {
+    long t = number.longValue();
+    if (calendar != null) {
+      t -= calendar.getTimeZone().getOffset(t);
+    }
+    return t;
+  }
+
+  /** 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) {
+    switch (rep) {
+    case BYTE_STRING:
+      return new ByteString((byte[]) value).toBase64String();
+    case JAVA_UTIL_DATE:
+    case JAVA_SQL_TIMESTAMP:
+    case JAVA_SQL_DATE:
+    case JAVA_SQL_TIME:
+      long t = ((Date) value).getTime();
+      if (calendar != null) {
+        t += calendar.getTimeZone().getOffset(t);
+      }
+      switch (rep) {
+      case JAVA_SQL_DATE:
+        return (int) DateTimeUtils.floorDiv(t, DateTimeUtils.MILLIS_PER_DAY);
+      case JAVA_SQL_TIME:
+        return (int) DateTimeUtils.floorMod(t, DateTimeUtils.MILLIS_PER_DAY);
+      default:
+        return t;
+      }
+    default:
+      return value;
+    }
+  }
+
+  /** Converts a value from internal format to a type that can be serialized
+   * as JSON. */
+  private static Object localToSerial(ColumnMetaData.Rep rep, Object value) {
+    switch (rep) {
+    case BYTE_STRING:
+      return ((ByteString) value).toBase64String();
+    default:
+      return value;
+    }
+  }
+
+  /** Converts a list of {@code TypedValue} to a list of values. */
+  public static List<Object> values(List<TypedValue> typedValues) {
+    final List<Object> list = new ArrayList<>();
+    for (TypedValue typedValue : typedValues) {
+      list.add(typedValue.toLocal());
+    }
+    return list;
+  }
+
+  public Common.TypedValue toProto() {
+    final Common.TypedValue.Builder builder = Common.TypedValue.newBuilder();
+
+    Common.Rep protoRep = type.toProto();
+    builder.setType(protoRep);
+
+    // Serialize the type into the protobuf
+    switch (protoRep) {
+    case BOOLEAN:
+    case PRIMITIVE_BOOLEAN:
+      builder.setBoolValue((boolean) value);
+      break;
+    case BYTE_STRING:
+    case STRING:
+      builder.setStringValueBytes(HBaseZeroCopyByteString.wrap(((String) 
value).getBytes()));
+      break;
+    case PRIMITIVE_CHAR:
+    case CHARACTER:
+      builder.setStringValue(Character.toString((char) value));
+      break;
+    case BYTE:
+    case PRIMITIVE_BYTE:
+      builder.setNumberValue(Byte.valueOf((byte) value).longValue());
+      break;
+    case DOUBLE:
+    case PRIMITIVE_DOUBLE:
+      builder.setDoubleValue((double) value);
+      break;
+    case FLOAT:
+    case PRIMITIVE_FLOAT:
+      builder.setNumberValue(Float.floatToIntBits((float) value));
+      break;
+    case INTEGER:
+    case PRIMITIVE_INT:
+      builder.setNumberValue(Integer.valueOf((int) value).longValue());
+      break;
+    case PRIMITIVE_SHORT:
+    case SHORT:
+      builder.setNumberValue(Short.valueOf((short) value).longValue());
+      break;
+    case LONG:
+    case PRIMITIVE_LONG:
+      builder.setNumberValue((long) value);
+      break;
+    case JAVA_SQL_DATE:
+    case JAVA_SQL_TIME:
+      // Persisted as integers
+      builder.setNumberValue(Integer.valueOf((int) value).longValue());
+      break;
+    case JAVA_SQL_TIMESTAMP:
+    case JAVA_UTIL_DATE:
+      // Persisted as longs
+      builder.setNumberValue((long) value);
+      break;
+    case BIG_INTEGER:
+      byte[] bytes = ((BigInteger) value).toByteArray();
+      builder.setBytesValues(com.google.protobuf.ByteString.copyFrom(bytes));
+      break;
+    case BIG_DECIMAL:
+      final BigDecimal bigDecimal = (BigDecimal) value;
+      final int scale = bigDecimal.scale();
+      final BigInteger bigInt = bigDecimal.toBigInteger();
+      
builder.setBytesValues(com.google.protobuf.ByteString.copyFrom(bigInt.toByteArray()))
+        .setNumberValue(scale);
+      break;
+    case NUMBER:
+      builder.setNumberValue(((Number) value).longValue());
+      break;
+    case OBJECT:
+      if (null == value) {
+        // We can persist a null value through easily
+        builder.setNull(true);
+        break;
+      }
+      // Intentional fall-through to RTE because we can't serialize something 
we have no type
+      // insight into.
+    case UNRECOGNIZED:
+      // Fail?
+      throw new RuntimeException("Unhandled value: " + protoRep + " " + 
value.getClass());
+    default:
+      // Fail?
+      throw new RuntimeException("Unknown serialized type: " + protoRep);
+    }
+
+    return builder.build();
+  }
+
+  public static TypedValue fromProto(Common.TypedValue proto) {
+    ColumnMetaData.Rep rep = ColumnMetaData.Rep.fromProto(proto.getType());
+
+    Object value = null;
+
+    // Deserialize the value again
+    switch (proto.getType()) {
+    case BOOLEAN:
+    case PRIMITIVE_BOOLEAN:
+      value = proto.getBoolValue();
+      break;
+    case BYTE_STRING:
+    case STRING:
+      value = proto.getStringValue();
+      break;
+    case PRIMITIVE_CHAR:
+    case CHARACTER:
+      value = proto.getStringValue().charAt(0);
+      break;
+    case BYTE:
+    case PRIMITIVE_BYTE:
+      value = Long.valueOf(proto.getNumberValue()).byteValue();
+      break;
+    case DOUBLE:
+    case PRIMITIVE_DOUBLE:
+      value = proto.getDoubleValue();
+      break;
+    case FLOAT:
+    case PRIMITIVE_FLOAT:
+      value = Float.intBitsToFloat((int) proto.getNumberValue());
+      break;
+    case INTEGER:
+    case PRIMITIVE_INT:
+      value = Long.valueOf(proto.getNumberValue()).intValue();
+      break;
+    case PRIMITIVE_SHORT:
+    case SHORT:
+      value = Long.valueOf(proto.getNumberValue()).shortValue();
+      break;
+    case LONG:
+    case PRIMITIVE_LONG:
+      value = Long.valueOf(proto.getNumberValue());
+      break;
+    case JAVA_SQL_DATE:
+    case JAVA_SQL_TIME:
+      value = Long.valueOf(proto.getNumberValue()).intValue();
+      break;
+    case JAVA_SQL_TIMESTAMP:
+    case JAVA_UTIL_DATE:
+      value = proto.getNumberValue();
+      break;
+    case BIG_INTEGER:
+      value = new BigInteger(proto.getBytesValues().toByteArray());
+      break;
+    case BIG_DECIMAL:
+      BigInteger bigInt = new BigInteger(proto.getBytesValues().toByteArray());
+      value = new BigDecimal(bigInt, (int) proto.getNumberValue());
+      break;
+    case NUMBER:
+      value = Long.valueOf(proto.getNumberValue());
+      break;
+    case OBJECT:
+      if (proto.getNull()) {
+        value = null;
+        break;
+      }
+      // Intentional fall through to RTE. If we sent an object over the wire, 
it could only
+      // possibly be null (at this point). Anything else has to be an error.
+    case UNRECOGNIZED:
+      // Fail?
+      throw new RuntimeException("Unhandled type: " + proto.getType());
+    default:
+      // Fail?
+      throw new RuntimeException("Unknown type: " + proto.getType());
+    }
+
+    return new TypedValue(rep, value);
+  }
+
+  @Override public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((type == null) ? 0 : type.hashCode());
+    result = prime * result + ((value == null) ? 0 : value.hashCode());
+    return result;
+  }
+
+  @Override public boolean equals(Object o) {
+    if (o == this) {
+      return true;
+    }
+    if (o instanceof TypedValue) {
+      TypedValue other = (TypedValue) o;
+
+      if (type != other.type) {
+        return false;
+      }
+
+      if (null == value) {
+        if (null != other.value) {
+          return false;
+        }
+      }
+
+      return value.equals(other.value);
+    }
+
+    return false;
+  }
+}
+
+// End TypedValue.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/remote/package-info.java
----------------------------------------------------------------------
diff --git 
a/avatica/core/src/main/java/org/apache/calcite/avatica/remote/package-info.java
 
b/avatica/core/src/main/java/org/apache/calcite/avatica/remote/package-info.java
new file mode 100644
index 0000000..1cf3e14
--- /dev/null
+++ 
b/avatica/core/src/main/java/org/apache/calcite/avatica/remote/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * JDBC driver that uses remote procedure calls.
+ */
+@PackageMarker
+package org.apache.calcite.avatica.remote;
+
+import org.apache.calcite.avatica.util.PackageMarker;
+
+// End package-info.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
----------------------------------------------------------------------
diff --git 
a/avatica/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
 
b/avatica/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
new file mode 100644
index 0000000..70f87a7
--- /dev/null
+++ 
b/avatica/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
@@ -0,0 +1,1377 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.avatica.util;
+
+import org.apache.calcite.avatica.AvaticaSite;
+import org.apache.calcite.avatica.AvaticaUtils;
+import org.apache.calcite.avatica.ColumnMetaData;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.NClob;
+import java.sql.Ref;
+import java.sql.SQLException;
+import java.sql.SQLXML;
+import java.sql.Struct;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base class for implementing a cursor.
+ *
+ * <p>Derived class needs to provide {@link Getter} and can override
+ * {@link org.apache.calcite.avatica.util.Cursor.Accessor} implementations if 
it
+ * wishes.</p>
+ */
+public abstract class AbstractCursor implements Cursor {
+  /**
+   * Slot into which each accessor should write whether the
+   * value returned was null.
+   */
+  protected final boolean[] wasNull = {false};
+
+  protected AbstractCursor() {
+  }
+
+  public boolean wasNull() {
+    return wasNull[0];
+  }
+
+  public List<Accessor> createAccessors(List<ColumnMetaData> types,
+      Calendar localCalendar, ArrayImpl.Factory factory) {
+    List<Accessor> accessors = new ArrayList<>();
+    for (ColumnMetaData type : types) {
+      accessors.add(
+          createAccessor(type, accessors.size(), localCalendar, factory));
+    }
+    return accessors;
+  }
+
+  protected Accessor createAccessor(ColumnMetaData columnMetaData, int ordinal,
+      Calendar localCalendar, ArrayImpl.Factory factory) {
+    // Create an accessor appropriate to the underlying type; the accessor
+    // can convert to any type in the same family.
+    Getter getter = createGetter(ordinal);
+    return createAccessor(columnMetaData, getter, localCalendar, factory);
+  }
+
+  protected Accessor createAccessor(ColumnMetaData columnMetaData,
+      Getter getter, Calendar localCalendar, ArrayImpl.Factory factory) {
+    switch (columnMetaData.type.rep) {
+    case NUMBER:
+      switch (columnMetaData.type.id) {
+      case Types.TINYINT:
+      case Types.SMALLINT:
+      case Types.INTEGER:
+      case Types.BIGINT:
+      case Types.REAL:
+      case Types.FLOAT:
+      case Types.DOUBLE:
+      case Types.NUMERIC:
+      case Types.DECIMAL:
+        return new NumberAccessor(getter, columnMetaData.scale);
+      }
+    }
+    switch (columnMetaData.type.id) {
+    case Types.TINYINT:
+      return new ByteAccessor(getter);
+    case Types.SMALLINT:
+      return new ShortAccessor(getter);
+    case Types.INTEGER:
+      return new IntAccessor(getter);
+    case Types.BIGINT:
+      return new LongAccessor(getter);
+    case Types.BOOLEAN:
+      return new BooleanAccessor(getter);
+    case Types.REAL:
+      return new FloatAccessor(getter);
+    case Types.FLOAT:
+    case Types.DOUBLE:
+      return new DoubleAccessor(getter);
+    case Types.DECIMAL:
+      return new BigDecimalAccessor(getter);
+    case Types.CHAR:
+      switch (columnMetaData.type.rep) {
+      case PRIMITIVE_CHAR:
+      case CHARACTER:
+        return new StringFromCharAccessor(getter, columnMetaData.displaySize);
+      default:
+        return new FixedStringAccessor(getter, columnMetaData.displaySize);
+      }
+    case Types.VARCHAR:
+      return new StringAccessor(getter);
+    case Types.BINARY:
+    case Types.VARBINARY:
+      switch (columnMetaData.type.rep) {
+      case STRING:
+        return new BinaryFromStringAccessor(getter);
+      default:
+        return new BinaryAccessor(getter);
+      }
+    case Types.DATE:
+      switch (columnMetaData.type.rep) {
+      case PRIMITIVE_INT:
+      case INTEGER:
+      case NUMBER:
+        return new DateFromNumberAccessor(getter, localCalendar);
+      case JAVA_SQL_DATE:
+        return new DateAccessor(getter);
+      default:
+        throw new AssertionError("bad " + columnMetaData.type.rep);
+      }
+    case Types.TIME:
+      switch (columnMetaData.type.rep) {
+      case PRIMITIVE_INT:
+      case INTEGER:
+      case NUMBER:
+        return new TimeFromNumberAccessor(getter, localCalendar);
+      case JAVA_SQL_TIME:
+        return new TimeAccessor(getter);
+      default:
+        throw new AssertionError("bad " + columnMetaData.type.rep);
+      }
+    case Types.TIMESTAMP:
+      switch (columnMetaData.type.rep) {
+      case PRIMITIVE_LONG:
+      case LONG:
+      case NUMBER:
+        return new TimestampFromNumberAccessor(getter, localCalendar);
+      case JAVA_SQL_TIMESTAMP:
+        return new TimestampAccessor(getter);
+      case JAVA_UTIL_DATE:
+        return new TimestampFromUtilDateAccessor(getter, localCalendar);
+      default:
+        throw new AssertionError("bad " + columnMetaData.type.rep);
+      }
+    case Types.ARRAY:
+      final ColumnMetaData.ArrayType arrayType =
+          (ColumnMetaData.ArrayType) columnMetaData.type;
+      final SlotGetter componentGetter = new SlotGetter();
+      final Accessor componentAccessor =
+          createAccessor(ColumnMetaData.dummy(arrayType.component, true),
+              componentGetter, localCalendar, factory);
+      return new ArrayAccessor(getter, arrayType.component, componentAccessor,
+          componentGetter, factory);
+    case Types.STRUCT:
+      switch (columnMetaData.type.rep) {
+      case OBJECT:
+        final ColumnMetaData.StructType structType =
+            (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));
+        }
+        return new StructAccessor(getter, accessors);
+      default:
+        throw new AssertionError("bad " + columnMetaData.type.rep);
+      }
+    case Types.JAVA_OBJECT:
+    case Types.OTHER: // e.g. map
+      if (columnMetaData.type.name.startsWith("INTERVAL_")) {
+        int end = columnMetaData.type.name.indexOf("(");
+        if (end < 0) {
+          end = columnMetaData.type.name.length();
+        }
+        TimeUnitRange range =
+            TimeUnitRange.valueOf(
+                columnMetaData.type.name.substring("INTERVAL_".length(), end));
+        if (range.monthly()) {
+          return new IntervalYearMonthAccessor(getter, range);
+        } else {
+          return new IntervalDayTimeAccessor(getter, range,
+              columnMetaData.scale);
+        }
+      }
+      return new ObjectAccessor(getter);
+    default:
+      throw new RuntimeException("unknown type " + columnMetaData.type.id);
+    }
+  }
+
+  protected abstract Getter createGetter(int ordinal);
+
+  public abstract boolean next();
+
+  /** Accesses a timestamp value as a string.
+   * The timestamp is in SQL format (e.g. "2013-09-22 22:30:32"),
+   * not Java format ("2013-09-22 22:30:32.123"). */
+  private static String timestampAsString(long v, Calendar calendar) {
+    if (calendar != null) {
+      v -= calendar.getTimeZone().getOffset(v);
+    }
+    return DateTimeUtils.unixTimestampToString(v);
+  }
+
+  /** Accesses a date value as a string, e.g. "2013-09-22". */
+  private static String dateAsString(int v, Calendar calendar) {
+    AvaticaUtils.discard(calendar); // time zone shift doesn't make sense
+    return DateTimeUtils.unixDateToString(v);
+  }
+
+  /** Accesses a time value as a string, e.g. "22:30:32". */
+  private static String timeAsString(int v, Calendar calendar) {
+    if (calendar != null) {
+      v -= calendar.getTimeZone().getOffset(v);
+    }
+    return DateTimeUtils.unixTimeToString(v);
+  }
+
+  private static Date longToDate(long v, Calendar calendar) {
+    if (calendar != null) {
+      v -= calendar.getTimeZone().getOffset(v);
+    }
+    return new Date(v);
+  }
+
+  static Time intToTime(int v, Calendar calendar) {
+    if (calendar != null) {
+      v -= calendar.getTimeZone().getOffset(v);
+    }
+    return new Time(v);
+  }
+
+  static Timestamp longToTimestamp(long v, Calendar calendar) {
+    if (calendar != null) {
+      v -= calendar.getTimeZone().getOffset(v);
+    }
+    return new Timestamp(v);
+  }
+
+  /** Implementation of {@link Cursor.Accessor}. */
+  static class AccessorImpl implements Accessor {
+    protected final Getter getter;
+
+    public AccessorImpl(Getter getter) {
+      assert getter != null;
+      this.getter = getter;
+    }
+
+    public boolean wasNull() {
+      return getter.wasNull();
+    }
+
+    public String getString() {
+      final Object o = getObject();
+      return o == null ? null : o.toString();
+    }
+
+    public boolean getBoolean() {
+      return getLong() != 0L;
+    }
+
+    public byte getByte() {
+      return (byte) getLong();
+    }
+
+    public short getShort() {
+      return (short) getLong();
+    }
+
+    public int getInt() {
+      return (int) getLong();
+    }
+
+    public long getLong() {
+      throw cannotConvert("long");
+    }
+
+    public float getFloat() {
+      return (float) getDouble();
+    }
+
+    public double getDouble() {
+      throw cannotConvert("double");
+    }
+
+    public BigDecimal getBigDecimal() {
+      throw cannotConvert("BigDecimal");
+    }
+
+    public BigDecimal getBigDecimal(int scale) {
+      throw cannotConvert("BigDecimal with scale");
+    }
+
+    public byte[] getBytes() {
+      throw cannotConvert("byte[]");
+    }
+
+    public InputStream getAsciiStream() {
+      throw cannotConvert("InputStream (ascii)");
+    }
+
+    public InputStream getUnicodeStream() {
+      throw cannotConvert("InputStream (unicode)");
+    }
+
+    public InputStream getBinaryStream() {
+      throw cannotConvert("InputStream (binary)");
+    }
+
+    public Object getObject() {
+      return getter.getObject();
+    }
+
+    public Reader getCharacterStream() {
+      throw cannotConvert("Reader");
+    }
+
+    private RuntimeException cannotConvert(String targetType) {
+      return new RuntimeException("cannot convert to " + targetType + " ("
+          + this + ")");
+    }
+
+    public Object getObject(Map<String, Class<?>> map) {
+      throw cannotConvert("Object (with map)");
+    }
+
+    public Ref getRef() {
+      throw cannotConvert("Ref");
+    }
+
+    public Blob getBlob() {
+      throw cannotConvert("Blob");
+    }
+
+    public Clob getClob() {
+      throw cannotConvert("Clob");
+    }
+
+    public Array getArray() {
+      throw cannotConvert("Array");
+    }
+
+    public Struct getStruct() {
+      throw cannotConvert("Struct");
+    }
+
+    public Date getDate(Calendar calendar) {
+      throw cannotConvert("Date");
+    }
+
+    public Time getTime(Calendar calendar) {
+      throw cannotConvert("Time");
+    }
+
+    public Timestamp getTimestamp(Calendar calendar) {
+      throw cannotConvert("Timestamp");
+    }
+
+    public URL getURL() {
+      throw cannotConvert("URL");
+    }
+
+    public NClob getNClob() {
+      throw cannotConvert("NClob");
+    }
+
+    public SQLXML getSQLXML() {
+      throw cannotConvert("SQLXML");
+    }
+
+    public String getNString() {
+      throw cannotConvert("NString");
+    }
+
+    public Reader getNCharacterStream() {
+      throw cannotConvert("NCharacterStream");
+    }
+
+    public <T> T getObject(Class<T> type) {
+      throw cannotConvert("Object (with type)");
+    }
+  }
+
+  /**
+   * Accessor of exact numeric values. The subclass must implement the
+   * {@link #getLong()} method.
+   */
+  private abstract static class ExactNumericAccessor extends AccessorImpl {
+    public ExactNumericAccessor(Getter getter) {
+      super(getter);
+    }
+
+    public BigDecimal getBigDecimal(int scale) {
+      final long v = getLong();
+      if (v == 0 && getter.wasNull()) {
+        return null;
+      }
+      return BigDecimal.valueOf(v).setScale(scale, RoundingMode.DOWN);
+    }
+
+    public BigDecimal getBigDecimal() {
+      final long val = getLong();
+      if (val == 0 && getter.wasNull()) {
+        return null;
+      }
+      return BigDecimal.valueOf(val);
+    }
+
+    public double getDouble() {
+      return getLong();
+    }
+
+    public float getFloat() {
+      return getLong();
+    }
+
+    public abstract long getLong();
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@link Boolean};
+   * corresponds to {@link java.sql.Types#BOOLEAN}.
+   */
+  private static class BooleanAccessor extends ExactNumericAccessor {
+    public BooleanAccessor(Getter getter) {
+      super(getter);
+    }
+
+    public boolean getBoolean() {
+      Boolean o = (Boolean) getObject();
+      return o != null && o;
+    }
+
+    public long getLong() {
+      return getBoolean() ? 1 : 0;
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@link Byte};
+   * corresponds to {@link java.sql.Types#TINYINT}.
+   */
+  private static class ByteAccessor extends ExactNumericAccessor {
+    public ByteAccessor(Getter getter) {
+      super(getter);
+    }
+
+    public byte getByte() {
+      Byte o = (Byte) getObject();
+      return o == null ? 0 : o;
+    }
+
+    public long getLong() {
+      return getByte();
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@link Short};
+   * corresponds to {@link java.sql.Types#SMALLINT}.
+   */
+  private static class ShortAccessor extends ExactNumericAccessor {
+    public ShortAccessor(Getter getter) {
+      super(getter);
+    }
+
+    public short getShort() {
+      Short o = (Short) getObject();
+      return o == null ? 0 : o;
+    }
+
+    public long getLong() {
+      return getShort();
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is an {@link Integer};
+   * corresponds to {@link java.sql.Types#INTEGER}.
+   */
+  private static class IntAccessor extends ExactNumericAccessor {
+    public IntAccessor(Getter getter) {
+      super(getter);
+    }
+
+    public int getInt() {
+      Integer o = (Integer) super.getObject();
+      return o == null ? 0 : o;
+    }
+
+    public long getLong() {
+      return getInt();
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@link Long};
+   * corresponds to {@link java.sql.Types#BIGINT}.
+   */
+  private static class LongAccessor extends ExactNumericAccessor {
+    public LongAccessor(Getter getter) {
+      super(getter);
+    }
+
+    public long getLong() {
+      Long o = (Long) super.getObject();
+      return o == null ? 0 : o;
+    }
+  }
+
+  /**
+   * Accessor of values that are {@link Double} or null.
+   */
+  private abstract static class ApproximateNumericAccessor
+      extends AccessorImpl {
+    public ApproximateNumericAccessor(Getter getter) {
+      super(getter);
+    }
+
+    public BigDecimal getBigDecimal(int scale) {
+      final double v = getDouble();
+      if (v == 0d && getter.wasNull()) {
+        return null;
+      }
+      return BigDecimal.valueOf(v).setScale(scale, RoundingMode.DOWN);
+    }
+
+    public BigDecimal getBigDecimal() {
+      final double v = getDouble();
+      if (v == 0 && getter.wasNull()) {
+        return null;
+      }
+      return BigDecimal.valueOf(v);
+    }
+
+    public abstract double getDouble();
+
+    public long getLong() {
+      return (long) getDouble();
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@link Float};
+   * corresponds to {@link java.sql.Types#FLOAT}.
+   */
+  private static class FloatAccessor extends ApproximateNumericAccessor {
+    public FloatAccessor(Getter getter) {
+      super(getter);
+    }
+
+    public float getFloat() {
+      Float o = (Float) getObject();
+      return o == null ? 0f : o;
+    }
+
+    public double getDouble() {
+      return getFloat();
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@link Double};
+   * corresponds to {@link java.sql.Types#DOUBLE}.
+   */
+  private static class DoubleAccessor extends ApproximateNumericAccessor {
+    public DoubleAccessor(Getter getter) {
+      super(getter);
+    }
+
+    public double getDouble() {
+      Double o = (Double) getObject();
+      return o == null ? 0d : o;
+    }
+  }
+
+  /**
+   * Accessor of exact numeric values. The subclass must implement the
+   * {@link #getLong()} method.
+   */
+  private abstract static class BigNumberAccessor extends AccessorImpl {
+    public BigNumberAccessor(Getter getter) {
+      super(getter);
+    }
+
+    protected abstract Number getNumber();
+
+    public double getDouble() {
+      Number number = getNumber();
+      return number == null ? 0d : number.doubleValue();
+    }
+
+    public float getFloat() {
+      Number number = getNumber();
+      return number == null ? 0f : number.floatValue();
+    }
+
+    public long getLong() {
+      Number number = getNumber();
+      return number == null ? 0L : number.longValue();
+    }
+
+    public int getInt() {
+      Number number = getNumber();
+      return number == null ? 0 : number.intValue();
+    }
+
+    public short getShort() {
+      Number number = getNumber();
+      return number == null ? 0 : number.shortValue();
+    }
+
+    public byte getByte() {
+      Number number = getNumber();
+      return number == null ? 0 : number.byteValue();
+    }
+
+    public boolean getBoolean() {
+      Number number = getNumber();
+      return number != null && number.doubleValue() != 0;
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@link BigDecimal};
+   * corresponds to {@link java.sql.Types#DECIMAL}.
+   */
+  private static class BigDecimalAccessor extends BigNumberAccessor {
+    public BigDecimalAccessor(Getter getter) {
+      super(getter);
+    }
+
+    protected Number getNumber() {
+      return (Number) getObject();
+    }
+
+    public BigDecimal getBigDecimal(int scale) {
+      return (BigDecimal) getObject();
+    }
+
+    public BigDecimal getBigDecimal() {
+      return (BigDecimal) getObject();
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@link Number};
+   * corresponds to {@link java.sql.Types#NUMERIC}.
+   *
+   * <p>This is useful when numbers have been translated over JSON. JSON
+   * converts a 0L (0 long) value to the string "0" and back to 0 (0 int).
+   * So you cannot be sure that the source and target type are the same.
+   */
+  static class NumberAccessor extends BigNumberAccessor {
+    private final int scale;
+
+    public NumberAccessor(Getter getter, int scale) {
+      super(getter);
+      this.scale = scale;
+    }
+
+    protected Number getNumber() {
+      return (Number) super.getObject();
+    }
+
+    public BigDecimal getBigDecimal(int scale) {
+      Number n = getNumber();
+      if (n == null) {
+        return null;
+      }
+      BigDecimal decimal = AvaticaSite.toBigDecimal(n);
+      if (0 != scale) {
+        return decimal.setScale(scale, BigDecimal.ROUND_UNNECESSARY);
+      }
+      return decimal;
+    }
+
+    public BigDecimal getBigDecimal() {
+      return getBigDecimal(scale);
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@link String};
+   * corresponds to {@link java.sql.Types#CHAR}
+   * and {@link java.sql.Types#VARCHAR}.
+   */
+  private static class StringAccessor extends AccessorImpl {
+    public StringAccessor(Getter getter) {
+      super(getter);
+    }
+
+    public String getString() {
+      return (String) getObject();
+    }
+
+    @Override public byte[] getBytes() {
+      return super.getBytes();
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@link String};
+   * corresponds to {@link java.sql.Types#CHAR}.
+   */
+  private static class FixedStringAccessor extends StringAccessor {
+    protected final Spacer spacer;
+
+    public FixedStringAccessor(Getter getter, int length) {
+      super(getter);
+      this.spacer = new Spacer(length);
+    }
+
+    public String getString() {
+      String s = super.getString();
+      if (s == null) {
+        return null;
+      }
+      return spacer.padRight(s);
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@link String};
+   * corresponds to {@link java.sql.Types#CHAR}.
+   */
+  private static class StringFromCharAccessor extends FixedStringAccessor {
+    public StringFromCharAccessor(Getter getter, int length) {
+      super(getter, length);
+    }
+
+    public String getString() {
+      Character s = (Character) super.getObject();
+      if (s == null) {
+        return null;
+      }
+      return spacer.padRight(s.toString());
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is an array of
+   * {@link org.apache.calcite.avatica.util.ByteString} values;
+   * corresponds to {@link java.sql.Types#BINARY}
+   * and {@link java.sql.Types#VARBINARY}.
+   */
+  private static class BinaryAccessor extends AccessorImpl {
+    public BinaryAccessor(Getter getter) {
+      super(getter);
+    }
+
+    //FIXME: Protobuf gets byte[]
+    @Override public byte[] getBytes() {
+      Object obj = getObject();
+      try {
+        final ByteString o = (ByteString) obj;
+        return o == null ? null : o.getBytes();
+      } catch (Exception ex) {
+        return obj == null ? null : (byte[]) obj;
+      }
+    }
+
+    @Override public String getString() {
+      Object o = getObject();
+      if (null == o) {
+        return null;
+      }
+      if (o instanceof byte[]) {
+        return new String((byte[]) o, StandardCharsets.UTF_8);
+      } else if (o instanceof ByteString) {
+        return ((ByteString) o).toString();
+      }
+      throw new IllegalStateException("Unhandled value type: " + o.getClass());
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@link String},
+   * encoding {@link java.sql.Types#BINARY}
+   * and {@link java.sql.Types#VARBINARY} values in Base64 format.
+   */
+  private static class BinaryFromStringAccessor extends StringAccessor {
+    public BinaryFromStringAccessor(Getter getter) {
+      super(getter);
+    }
+
+    @Override public Object getObject() {
+      return super.getObject();
+    }
+
+    @Override public byte[] getBytes() {
+      // JSON sends this as a base64-enc string, protobuf can do binary.
+      Object obj = getObject();
+
+      if (obj instanceof byte[]) {
+        // If we already have bytes, just send them back.
+        return (byte[]) obj;
+      }
+
+      return getBase64Decoded();
+    }
+
+    private byte[] getBase64Decoded() {
+      final String string = super.getString();
+      if (null == string) {
+        return null;
+      }
+      // Need to base64 decode the string.
+      return ByteString.parseBase64(string);
+    }
+
+    @Override public String getString() {
+      final byte[] bytes = getBase64Decoded();
+      if (null == bytes) {
+        return null;
+      }
+      // Need to base64 decode the string.
+      return new String(bytes, StandardCharsets.UTF_8);
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a DATE,
+   * in its default representation {@code int};
+   * corresponds to {@link java.sql.Types#DATE}.
+   */
+  private static class DateFromNumberAccessor extends NumberAccessor {
+    private final Calendar localCalendar;
+
+    public DateFromNumberAccessor(Getter getter, Calendar localCalendar) {
+      super(getter, 0);
+      this.localCalendar = localCalendar;
+    }
+
+    @Override public Object getObject() {
+      return getDate(localCalendar);
+    }
+
+    @Override public Date getDate(Calendar calendar) {
+      final Number v = getNumber();
+      if (v == null) {
+        return null;
+      }
+      return longToDate(v.longValue() * DateTimeUtils.MILLIS_PER_DAY, 
calendar);
+    }
+
+    @Override public Timestamp getTimestamp(Calendar calendar) {
+      final Number v = getNumber();
+      if (v == null) {
+        return null;
+      }
+      return longToTimestamp(v.longValue() * DateTimeUtils.MILLIS_PER_DAY,
+          calendar);
+    }
+
+    @Override public String getString() {
+      final Number v = getNumber();
+      if (v == null) {
+        return null;
+      }
+      return dateAsString(v.intValue(), null);
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a Time,
+   * in its default representation {@code int};
+   * corresponds to {@link java.sql.Types#TIME}.
+   */
+  private static class TimeFromNumberAccessor extends NumberAccessor {
+    private final Calendar localCalendar;
+
+    public TimeFromNumberAccessor(Getter getter, Calendar localCalendar) {
+      super(getter, 0);
+      this.localCalendar = localCalendar;
+    }
+
+    @Override public Object getObject() {
+      return getTime(localCalendar);
+    }
+
+    @Override public Time getTime(Calendar calendar) {
+      final Number v = getNumber();
+      if (v == null) {
+        return null;
+      }
+      return intToTime(v.intValue(), calendar);
+    }
+
+    @Override public Timestamp getTimestamp(Calendar calendar) {
+      final Number v = getNumber();
+      if (v == null) {
+        return null;
+      }
+      return longToTimestamp(v.longValue(), calendar);
+    }
+
+    @Override public String getString() {
+      final Number v = getNumber();
+      if (v == null) {
+        return null;
+      }
+      return timeAsString(v.intValue(), null);
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a TIMESTAMP,
+   * in its default representation {@code long};
+   * corresponds to {@link java.sql.Types#TIMESTAMP}.
+   */
+  private static class TimestampFromNumberAccessor extends NumberAccessor {
+    private final Calendar localCalendar;
+
+    public TimestampFromNumberAccessor(Getter getter, Calendar localCalendar) {
+      super(getter, 0);
+      this.localCalendar = localCalendar;
+    }
+
+    @Override public Object getObject() {
+      return getTimestamp(localCalendar);
+    }
+
+    @Override public Timestamp getTimestamp(Calendar calendar) {
+      final Number v = getNumber();
+      if (v == null) {
+        return null;
+      }
+      return longToTimestamp(v.longValue(), calendar);
+    }
+
+    @Override public Date getDate(Calendar calendar) {
+      final Timestamp timestamp  = getTimestamp(calendar);
+      if (timestamp == null) {
+        return null;
+      }
+      return new Date(timestamp.getTime());
+    }
+
+    @Override public Time getTime(Calendar calendar) {
+      final Timestamp timestamp  = getTimestamp(calendar);
+      if (timestamp == null) {
+        return null;
+      }
+      return new Time(
+          DateTimeUtils.floorMod(timestamp.getTime(),
+              DateTimeUtils.MILLIS_PER_DAY));
+    }
+
+    @Override public String getString() {
+      final Number v = getNumber();
+      if (v == null) {
+        return null;
+      }
+      return timestampAsString(v.longValue(), null);
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a DATE,
+   * represented as a java.sql.Date;
+   * corresponds to {@link java.sql.Types#DATE}.
+   */
+  private static class DateAccessor extends ObjectAccessor {
+    public DateAccessor(Getter getter) {
+      super(getter);
+    }
+
+    @Override public Date getDate(Calendar calendar) {
+      java.sql.Date date = (Date) getObject();
+      if (date == null) {
+        return null;
+      }
+      if (calendar != null) {
+        long v = date.getTime();
+        v -= calendar.getTimeZone().getOffset(v);
+        date = new Date(v);
+      }
+      return date;
+    }
+
+    @Override public String getString() {
+      final int v = getInt();
+      if (v == 0 && wasNull()) {
+        return null;
+      }
+      return dateAsString(v, null);
+    }
+
+    @Override public long getLong() {
+      Date date = getDate(null);
+      return date == null
+          ? 0L
+          : (date.getTime() / DateTimeUtils.MILLIS_PER_DAY);
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a TIME,
+   * represented as a java.sql.Time;
+   * corresponds to {@link java.sql.Types#TIME}.
+   */
+  private static class TimeAccessor extends ObjectAccessor {
+    public TimeAccessor(Getter getter) {
+      super(getter);
+    }
+
+    @Override public Time getTime(Calendar calendar) {
+      Time date  = (Time) getObject();
+      if (date == null) {
+        return null;
+      }
+      if (calendar != null) {
+        long v = date.getTime();
+        v -= calendar.getTimeZone().getOffset(v);
+        date = new Time(v);
+      }
+      return date;
+    }
+
+    @Override public String getString() {
+      final int v = getInt();
+      if (v == 0 && wasNull()) {
+        return null;
+      }
+      return timeAsString(v, null);
+    }
+
+    @Override public long getLong() {
+      Time time = getTime(null);
+      return time == null ? 0L
+          : (time.getTime() % DateTimeUtils.MILLIS_PER_DAY);
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a TIMESTAMP,
+   * represented as a java.sql.Timestamp;
+   * corresponds to {@link java.sql.Types#TIMESTAMP}.
+   */
+  private static class TimestampAccessor extends ObjectAccessor {
+    public TimestampAccessor(Getter getter) {
+      super(getter);
+    }
+
+    @Override public Timestamp getTimestamp(Calendar calendar) {
+      Timestamp timestamp  = (Timestamp) getObject();
+      if (timestamp == null) {
+        return null;
+      }
+      if (calendar != null) {
+        long v = timestamp.getTime();
+        v -= calendar.getTimeZone().getOffset(v);
+        timestamp = new Timestamp(v);
+      }
+      return timestamp;
+    }
+
+    @Override public Date getDate(Calendar calendar) {
+      final Timestamp timestamp  = getTimestamp(calendar);
+      if (timestamp == null) {
+        return null;
+      }
+      return new Date(timestamp.getTime());
+    }
+
+    @Override public Time getTime(Calendar calendar) {
+      final Timestamp timestamp  = getTimestamp(calendar);
+      if (timestamp == null) {
+        return null;
+      }
+      return new Time(
+          DateTimeUtils.floorMod(timestamp.getTime(),
+              DateTimeUtils.MILLIS_PER_DAY));
+    }
+
+    @Override public String getString() {
+      final long v = getLong();
+      if (v == 0 && wasNull()) {
+        return null;
+      }
+      return timestampAsString(v, null);
+    }
+
+    @Override public long getLong() {
+      Timestamp timestamp = getTimestamp(null);
+      return timestamp == null ? 0 : timestamp.getTime();
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a TIMESTAMP,
+   * represented as a java.util.Date;
+   * corresponds to {@link java.sql.Types#TIMESTAMP}.
+   */
+  private static class TimestampFromUtilDateAccessor extends ObjectAccessor {
+    private final Calendar localCalendar;
+
+    public TimestampFromUtilDateAccessor(Getter getter,
+        Calendar localCalendar) {
+      super(getter);
+      this.localCalendar = localCalendar;
+    }
+
+    @Override public Timestamp getTimestamp(Calendar calendar) {
+      java.util.Date date  = (java.util.Date) getObject();
+      if (date == null) {
+        return null;
+      }
+      long v = date.getTime();
+      if (calendar != null) {
+        v -= calendar.getTimeZone().getOffset(v);
+      }
+      return new Timestamp(v);
+    }
+
+    @Override public Date getDate(Calendar calendar) {
+      final Timestamp timestamp  = getTimestamp(calendar);
+      if (timestamp == null) {
+        return null;
+      }
+      return new Date(timestamp.getTime());
+    }
+
+    @Override public Time getTime(Calendar calendar) {
+      final Timestamp timestamp  = getTimestamp(calendar);
+      if (timestamp == null) {
+        return null;
+      }
+      return new Time(
+          DateTimeUtils.floorMod(timestamp.getTime(),
+              DateTimeUtils.MILLIS_PER_DAY));
+    }
+
+    @Override public String getString() {
+      java.util.Date date  = (java.util.Date) getObject();
+      if (date == null) {
+        return null;
+      }
+      return timestampAsString(date.getTime(), null);
+    }
+
+    @Override public long getLong() {
+      Timestamp timestamp = getTimestamp(localCalendar);
+      return timestamp == null ? 0 : timestamp.getTime();
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@code int};
+   * corresponds to {@link java.sql.Types#OTHER}.
+   */
+  private static class IntervalYearMonthAccessor extends IntAccessor {
+    private final TimeUnitRange range;
+
+    public IntervalYearMonthAccessor(Getter getter, TimeUnitRange range) {
+      super(getter);
+      this.range = range;
+    }
+
+    @Override public String getString() {
+      final int v = getInt();
+      if (v == 0 && wasNull()) {
+        return null;
+      }
+      return DateTimeUtils.intervalYearMonthToString(v, range);
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a {@code long};
+   * corresponds to {@link java.sql.Types#OTHER}.
+   */
+  private static class IntervalDayTimeAccessor extends LongAccessor {
+    private final TimeUnitRange range;
+    private final int scale;
+
+    public IntervalDayTimeAccessor(Getter getter, TimeUnitRange range,
+        int scale) {
+      super(getter);
+      this.range = range;
+      this.scale = scale;
+    }
+
+    @Override public String getString() {
+      final long v = getLong();
+      if (v == 0 && wasNull()) {
+        return null;
+      }
+      return DateTimeUtils.intervalDayTimeToString(v, range, scale);
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is an ARRAY;
+   * corresponds to {@link java.sql.Types#ARRAY}.
+   */
+  static class ArrayAccessor extends AccessorImpl {
+    final ColumnMetaData.AvaticaType componentType;
+    final Accessor componentAccessor;
+    final SlotGetter componentSlotGetter;
+    final ArrayImpl.Factory factory;
+
+    public ArrayAccessor(Getter getter,
+        ColumnMetaData.AvaticaType componentType, Accessor componentAccessor,
+        SlotGetter componentSlotGetter, ArrayImpl.Factory factory) {
+      super(getter);
+      this.componentType = componentType;
+      this.componentAccessor = componentAccessor;
+      this.componentSlotGetter = componentSlotGetter;
+      this.factory = factory;
+    }
+
+    @Override public Object getObject() {
+      final Object object = super.getObject();
+      if (object == null || object instanceof List) {
+        return object;
+      }
+      // The object can be java array in case of user-provided class for row
+      // storage.
+      return AvaticaUtils.primitiveList(object);
+    }
+
+    @Override public Array getArray() {
+      final List list = (List) getObject();
+      if (list == null) {
+        return null;
+      }
+      return new ArrayImpl(list, this);
+    }
+
+    @Override public String getString() {
+      final Array array = getArray();
+      return array == null ? null : array.toString();
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is a STRUCT;
+   * corresponds to {@link java.sql.Types#STRUCT}.
+   */
+  private static class StructAccessor extends AccessorImpl {
+    private final List<Accessor> fieldAccessors;
+
+    public StructAccessor(Getter getter, List<Accessor> fieldAccessors) {
+      super(getter);
+      this.fieldAccessors = fieldAccessors;
+    }
+
+    @Override public Object getObject() {
+      return getStruct();
+    }
+
+    @Override public Struct getStruct() {
+      final Object o = super.getObject();
+      if (o == null) {
+        return null;
+      } else if (o instanceof List) {
+        return new StructImpl((List) o);
+      } else {
+        final List<Object> list = new ArrayList<>();
+        for (Accessor fieldAccessor : fieldAccessors) {
+          try {
+            list.add(fieldAccessor.getObject());
+          } catch (SQLException e) {
+            throw new RuntimeException(e);
+          }
+        }
+        return new StructImpl(list);
+      }
+    }
+  }
+
+  /**
+   * Accessor that assumes that the underlying value is an OBJECT;
+   * corresponds to {@link java.sql.Types#JAVA_OBJECT}.
+   */
+  private static class ObjectAccessor extends AccessorImpl {
+    public ObjectAccessor(Getter getter) {
+      super(getter);
+    }
+  }
+
+  /** Gets a value from a particular field of the current record of this
+   * cursor. */
+  protected interface Getter {
+    Object getObject();
+
+    boolean wasNull();
+  }
+
+  /** Abstract implementation of {@link Getter}. */
+  protected abstract class AbstractGetter implements Getter {
+    public boolean wasNull() {
+      return wasNull[0];
+    }
+  }
+
+  /** Implementation of {@link Getter} that returns the current contents of
+   * a mutable slot. */
+  public class SlotGetter implements Getter {
+    public Object slot;
+
+    public Object getObject() {
+      return slot;
+    }
+
+    public boolean wasNull() {
+      return slot == null;
+    }
+  }
+
+  /** Implementation of {@link Getter} that returns the value of a given field
+   * of the current contents of another getter. */
+  public class StructGetter implements Getter {
+    public final Getter getter;
+    private final ColumnMetaData columnMetaData;
+
+    public StructGetter(Getter getter, ColumnMetaData columnMetaData) {
+      this.getter = getter;
+      this.columnMetaData = columnMetaData;
+    }
+
+    public Object getObject() {
+      final Object o = getter.getObject();
+      if (o instanceof Object[]) {
+        Object[] objects = (Object[]) o;
+        return objects[columnMetaData.ordinal];
+      }
+      try {
+        final Field field = o.getClass().getField(columnMetaData.label);
+        return field.get(getter.getObject());
+      } catch (IllegalAccessException | NoSuchFieldException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    public boolean wasNull() {
+      return getObject() == null;
+    }
+  }
+}
+
+// End AbstractCursor.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java
----------------------------------------------------------------------
diff --git 
a/avatica/core/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java 
b/avatica/core/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java
new file mode 100644
index 0000000..b2d5ae9
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.avatica.util;
+
+import org.apache.calcite.avatica.AvaticaUtils;
+import org.apache.calcite.avatica.ColumnMetaData;
+
+import java.sql.Array;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/** Implementation of JDBC {@link Array}. */
+public class ArrayImpl implements Array {
+  private final List list;
+  private final AbstractCursor.ArrayAccessor accessor;
+
+  public ArrayImpl(List list, AbstractCursor.ArrayAccessor accessor) {
+    this.list = list;
+    this.accessor = accessor;
+  }
+
+  public String getBaseTypeName() throws SQLException {
+    return accessor.componentType.name;
+  }
+
+  public int getBaseType() throws SQLException {
+    return accessor.componentType.id;
+  }
+
+  public Object getArray() throws SQLException {
+    return getArray(list);
+  }
+
+  @Override public String toString() {
+    final Iterator iterator = list.iterator();
+    if (!iterator.hasNext()) {
+      return "[]";
+    }
+    final StringBuilder buf = new StringBuilder("[");
+    for (;;) {
+      accessor.componentSlotGetter.slot = iterator.next();
+      try {
+        append(buf, accessor.componentAccessor.getString());
+      } catch (SQLException e) {
+        throw new RuntimeException(e);
+      }
+      accessor.componentSlotGetter.slot = null;
+      if (!iterator.hasNext()) {
+        return buf.append("]").toString();
+      }
+      buf.append(", ");
+    }
+  }
+
+  private void append(StringBuilder buf, Object o) {
+    if (o == null) {
+      buf.append("null");
+    } else if (o.getClass().isArray()) {
+      append(buf, AvaticaUtils.primitiveList(o));
+    } else {
+      buf.append(o);
+    }
+  }
+
+  /**
+   * Converts a list into an array.
+   *
+   * <p>If the elements of the list are primitives, converts to an array of
+   * primitives (e.g. {@code boolean[]}.</p>
+   *
+   * @param list List of objects
+   *
+   * @return array
+   * @throws ClassCastException   if any element is not of the box type
+   * @throws NullPointerException if any element is null
+   */
+  @SuppressWarnings("unchecked")
+  protected Object getArray(List list) throws SQLException {
+    int i = 0;
+    switch (accessor.componentType.rep) {
+    case PRIMITIVE_DOUBLE:
+      final double[] doubles = new double[list.size()];
+      for (double v : (List<Double>) list) {
+        doubles[i++] = v;
+      }
+      return doubles;
+    case PRIMITIVE_FLOAT:
+      final float[] floats = new float[list.size()];
+      for (float v : (List<Float>) list) {
+        floats[i++] = v;
+      }
+      return floats;
+    case PRIMITIVE_INT:
+      final int[] ints = new int[list.size()];
+      for (int v : (List<Integer>) list) {
+        ints[i++] = v;
+      }
+      return ints;
+    case PRIMITIVE_LONG:
+      final long[] longs = new long[list.size()];
+      for (long v : (List<Long>) list) {
+        longs[i++] = v;
+      }
+      return longs;
+    case PRIMITIVE_SHORT:
+      final short[] shorts = new short[list.size()];
+      for (short v : (List<Short>) list) {
+        shorts[i++] = v;
+      }
+      return shorts;
+    case PRIMITIVE_BOOLEAN:
+      final boolean[] booleans = new boolean[list.size()];
+      for (boolean v : (List<Boolean>) list) {
+        booleans[i++] = v;
+      }
+      return booleans;
+    case PRIMITIVE_BYTE:
+      final byte[] bytes = new byte[list.size()];
+      for (byte v : (List<Byte>) list) {
+        bytes[i++] = v;
+      }
+      return bytes;
+    case PRIMITIVE_CHAR:
+      final char[] chars = new char[list.size()];
+      for (char v : (List<Character>) list) {
+        chars[i++] = v;
+      }
+      return chars;
+    default:
+      // fall through
+    }
+    final Object[] objects = list.toArray();
+    switch (accessor.componentType.id) {
+    case Types.ARRAY:
+      final AbstractCursor.ArrayAccessor componentAccessor =
+          (AbstractCursor.ArrayAccessor) accessor.componentAccessor;
+      for (i = 0; i < objects.length; i++) {
+        objects[i] = new ArrayImpl((List) objects[i], componentAccessor);
+      }
+    }
+    return objects;
+  }
+
+  public Object getArray(Map<String, Class<?>> map) throws SQLException {
+    throw new UnsupportedOperationException(); // TODO
+  }
+
+  public Object getArray(long index, int count) throws SQLException {
+    return getArray(list.subList((int) index, count));
+  }
+
+  public Object getArray(long index, int count, Map<String, Class<?>> map)
+      throws SQLException {
+    throw new UnsupportedOperationException(); // TODO
+  }
+
+  public ResultSet getResultSet() throws SQLException {
+    return accessor.factory.create(accessor.componentType, list);
+  }
+
+  public ResultSet getResultSet(Map<String, Class<?>> map)
+      throws SQLException {
+    throw new UnsupportedOperationException(); // TODO
+  }
+
+  public ResultSet getResultSet(long index, int count) throws SQLException {
+    throw new UnsupportedOperationException(); // TODO
+  }
+
+  public ResultSet getResultSet(long index, int count,
+      Map<String, Class<?>> map) throws SQLException {
+    throw new UnsupportedOperationException(); // TODO
+  }
+
+  public void free() throws SQLException {
+    // nothing to do
+  }
+
+  /** Factory that can create a result set based on a list of values. */
+  public interface Factory {
+    ResultSet create(ColumnMetaData.AvaticaType elementType,
+        Iterable<Object> iterable);
+  }
+}
+
+// End ArrayImpl.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/ArrayIteratorCursor.java
----------------------------------------------------------------------
diff --git 
a/avatica/core/src/main/java/org/apache/calcite/avatica/util/ArrayIteratorCursor.java
 
b/avatica/core/src/main/java/org/apache/calcite/avatica/util/ArrayIteratorCursor.java
new file mode 100644
index 0000000..0477984
--- /dev/null
+++ 
b/avatica/core/src/main/java/org/apache/calcite/avatica/util/ArrayIteratorCursor.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.avatica.util;
+
+import java.util.Iterator;
+
+/**
+ * Implementation of {@link Cursor} on top of an
+ * {@link java.util.Iterator} that
+ * returns an array of {@link Object} for each row.
+ */
+public class ArrayIteratorCursor extends IteratorCursor<Object[]> {
+  /**
+   * Creates an ArrayEnumeratorCursor.
+   *
+   * @param iterator Iterator
+   */
+  public ArrayIteratorCursor(Iterator<Object[]> iterator) {
+    super(iterator);
+  }
+
+  protected Getter createGetter(int ordinal) {
+    return new ArrayGetter(ordinal);
+  }
+}
+
+// End ArrayIteratorCursor.java

Reply via email to