You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by el...@apache.org on 2016/03/07 19:28:20 UTC

[42/59] [partial] calcite git commit: [CALCITE-1078] Detach avatica from the core calcite Maven project

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/InternalProperty.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/InternalProperty.java b/avatica/core/src/main/java/org/apache/calcite/avatica/InternalProperty.java
new file mode 100644
index 0000000..a5a3852
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/InternalProperty.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import org.apache.calcite.avatica.ConnectionProperty.Type;
+import org.apache.calcite.avatica.util.Casing;
+import org.apache.calcite.avatica.util.Quoting;
+
+import java.util.Map;
+
+/**
+ * Definitions of properties that drive the behavior of
+ * {@link org.apache.calcite.avatica.AvaticaDatabaseMetaData}.
+ */
+public enum InternalProperty {
+  /** Whether identifiers are matched case-sensitively. */
+  CASE_SENSITIVE(Type.BOOLEAN, true),
+
+  /** Character that quotes identifiers. */
+  SQL_KEYWORDS(Type.STRING, null),
+
+  /** How identifiers are quoted. */
+  QUOTING(Quoting.class, Quoting.DOUBLE_QUOTE),
+
+  /** How identifiers are stored if they are quoted. */
+  QUOTED_CASING(Casing.class, Casing.UNCHANGED),
+
+  /** How identifiers are stored if they are not quoted. */
+  UNQUOTED_CASING(Casing.class, Casing.TO_UPPER),
+
+  /** How identifiers are stored if they are not quoted. */
+  NULL_SORTING(NullSorting.class, NullSorting.END);
+
+  private final Type type;
+  private final Class enumClass;
+  private final Object defaultValue;
+
+  /** Creates an InternalProperty based on an enum. */
+  <E extends Enum> InternalProperty(Class<E> enumClass, E defaultValue) {
+    this(Type.ENUM, enumClass, defaultValue);
+  }
+
+  /** Creates an InternalProperty based on a non-enum type. */
+  InternalProperty(Type type, Object defaultValue) {
+    this(type, null, defaultValue);
+  }
+
+  private InternalProperty(Type type, Class enumClass, Object defaultValue) {
+    this.type = type;
+    this.enumClass = enumClass;
+    this.defaultValue = defaultValue;
+  }
+
+  private <T> T get_(Map<InternalProperty, Object> map, T defaultValue) {
+    final Object s = map.get(this);
+    if (s != null) {
+      return (T) s;
+    }
+    if (defaultValue != null) {
+      return (T) defaultValue;
+    }
+    throw new RuntimeException("Required property '" + name()
+        + "' not specified");
+  }
+
+  /** Returns the string value of this property, or null if not specified and
+   * no default. */
+  public String getString(Map<InternalProperty, Object> map) {
+    assert type == Type.STRING;
+    return get_(map, (String) defaultValue);
+  }
+
+  /** Returns the boolean value of this property. Throws if not set and no
+   * default. */
+  public boolean getBoolean(Map<InternalProperty, Object> map) {
+    assert type == Type.BOOLEAN;
+    return get_(map, (Boolean) defaultValue);
+  }
+
+  /** Returns the enum value of this property. Throws if not set and no
+   * default. */
+  public <E extends Enum> E getEnum(Map<InternalProperty, Object> map,
+      Class<E> enumClass) {
+    assert type == Type.ENUM;
+    //noinspection unchecked
+    return get_(map, (E) defaultValue);
+  }
+
+  /** Where nulls appear in a sorted relation. */
+  enum NullSorting {
+    START, END, LOW, HIGH,
+  }
+}
+
+// End InternalProperty.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/Meta.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/Meta.java b/avatica/core/src/main/java/org/apache/calcite/avatica/Meta.java
new file mode 100644
index 0000000..4cc460c
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/Meta.java
@@ -0,0 +1,1272 @@
+/*
+ * 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;
+
+import org.apache.calcite.avatica.proto.Common;
+import org.apache.calcite.avatica.remote.TypedValue;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.Descriptors.FieldDescriptor;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+
+/**
+ * Command handler for getting various metadata. Should be implemented by each
+ * driver.
+ *
+ * <p>Also holds other abstract methods that are not related to metadata
+ * that each provider must implement. This is not ideal.</p>
+ */
+public interface Meta {
+
+  /**
+   * Returns a map of static database properties.
+   *
+   * <p>The provider can omit properties whose value is the same as the
+   * default.
+   */
+  Map<DatabaseProperty, Object> getDatabaseProperties(ConnectionHandle ch);
+
+  /** Per {@link DatabaseMetaData#getTables(String, String, String, String[])}. */
+  MetaResultSet getTables(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat tableNamePattern,
+      List<String> typeList);
+
+  /** Per {@link DatabaseMetaData#getColumns(String, String, String, String)}. */
+  MetaResultSet getColumns(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat tableNamePattern,
+      Pat columnNamePattern);
+
+  MetaResultSet getSchemas(ConnectionHandle ch, String catalog, Pat schemaPattern);
+
+  /** Per {@link DatabaseMetaData#getCatalogs()}. */
+  MetaResultSet getCatalogs(ConnectionHandle ch);
+
+  /** Per {@link DatabaseMetaData#getTableTypes()}. */
+  MetaResultSet getTableTypes(ConnectionHandle ch);
+
+  /** Per {@link DatabaseMetaData#getProcedures(String, String, String)}. */
+  MetaResultSet getProcedures(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat procedureNamePattern);
+
+  /** Per {@link DatabaseMetaData#getProcedureColumns(String, String, String, String)}. */
+  MetaResultSet getProcedureColumns(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat procedureNamePattern,
+      Pat columnNamePattern);
+
+  /** Per {@link DatabaseMetaData#getColumnPrivileges(String, String, String, String)}. */
+  MetaResultSet getColumnPrivileges(ConnectionHandle ch,
+      String catalog,
+      String schema,
+      String table,
+      Pat columnNamePattern);
+
+  /** Per {@link DatabaseMetaData#getTablePrivileges(String, String, String)}. */
+  MetaResultSet getTablePrivileges(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat tableNamePattern);
+
+  /** Per
+   * {@link DatabaseMetaData#getBestRowIdentifier(String, String, String, int, boolean)}. */
+  MetaResultSet getBestRowIdentifier(ConnectionHandle ch,
+      String catalog,
+      String schema,
+      String table,
+      int scope,
+      boolean nullable);
+
+  /** Per {@link DatabaseMetaData#getVersionColumns(String, String, String)}. */
+  MetaResultSet getVersionColumns(ConnectionHandle ch, String catalog, String schema, String table);
+
+  /** Per {@link DatabaseMetaData#getPrimaryKeys(String, String, String)}. */
+  MetaResultSet getPrimaryKeys(ConnectionHandle ch, String catalog, String schema, String table);
+
+  /** Per {@link DatabaseMetaData#getImportedKeys(String, String, String)}. */
+  MetaResultSet getImportedKeys(ConnectionHandle ch, String catalog, String schema, String table);
+
+  /** Per {@link DatabaseMetaData#getExportedKeys(String, String, String)}. */
+  MetaResultSet getExportedKeys(ConnectionHandle ch, String catalog, String schema, String table);
+
+  /** Per
+   * {@link DatabaseMetaData#getCrossReference(String, String, String, String, String, String)}. */
+  MetaResultSet getCrossReference(ConnectionHandle ch,
+      String parentCatalog,
+      String parentSchema,
+      String parentTable,
+      String foreignCatalog,
+      String foreignSchema,
+      String foreignTable);
+
+  /** Per {@link DatabaseMetaData#getTypeInfo()}. */
+  MetaResultSet getTypeInfo(ConnectionHandle ch);
+
+  /** Per {@link DatabaseMetaData#getIndexInfo(String, String, String, boolean, boolean)}. */
+  MetaResultSet getIndexInfo(ConnectionHandle ch, String catalog,
+      String schema,
+      String table,
+      boolean unique,
+      boolean approximate);
+
+  /** Per {@link DatabaseMetaData#getUDTs(String, String, String, int[])}. */
+  MetaResultSet getUDTs(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat typeNamePattern,
+      int[] types);
+
+  /** Per {@link DatabaseMetaData#getSuperTypes(String, String, String)}. */
+  MetaResultSet getSuperTypes(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat typeNamePattern);
+
+  /** Per {@link DatabaseMetaData#getSuperTables(String, String, String)}. */
+  MetaResultSet getSuperTables(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat tableNamePattern);
+
+  /** Per {@link DatabaseMetaData#getAttributes(String, String, String, String)}. */
+  MetaResultSet getAttributes(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat typeNamePattern,
+      Pat attributeNamePattern);
+
+  /** Per {@link DatabaseMetaData#getClientInfoProperties()}. */
+  MetaResultSet getClientInfoProperties(ConnectionHandle ch);
+
+  /** Per {@link DatabaseMetaData#getFunctions(String, String, String)}. */
+  MetaResultSet getFunctions(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat functionNamePattern);
+
+  /** Per {@link DatabaseMetaData#getFunctionColumns(String, String, String, String)}. */
+  MetaResultSet getFunctionColumns(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat functionNamePattern,
+      Pat columnNamePattern);
+
+  /** Per {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}. */
+  MetaResultSet getPseudoColumns(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat tableNamePattern,
+      Pat columnNamePattern);
+
+  /** Creates an iterable for a result set.
+   *
+   * <p>The default implementation just returns {@code iterable}, which it
+   * 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);
+
+  /** Prepares a statement.
+   *
+   * @param ch Connection handle
+   * @param sql SQL query
+   * @param maxRowCount Negative for no limit (different meaning than JDBC)
+   * @return Signature of prepared statement
+   */
+  StatementHandle prepare(ConnectionHandle ch, String sql, long maxRowCount);
+
+  /** Prepares and executes a statement.
+   *
+   * @param h Statement handle
+   * @param sql SQL query
+   * @param maxRowCount Negative for no limit (different meaning than JDBC)
+   * @param callback Callback to lock, clear and assign cursor
+   *
+   * @return Result containing statement ID, and if a query, a result set and
+   *     first frame of data
+   */
+  ExecuteResult prepareAndExecute(StatementHandle h, String sql,
+      long maxRowCount, PrepareCallback callback) throws NoSuchStatementException;
+
+  /** Returns a frame of rows.
+   *
+   * <p>The frame describes whether there may be another frame. If there is not
+   * another frame, the current iteration is done when we have finished the
+   * rows in the this frame.
+   *
+   * <p>The default implementation always returns null.
+   *
+   * @param h Statement handle
+   * @param offset Zero-based offset of first row in the requested frame
+   * @param fetchMaxRowCount Maximum number of rows to return; negative means
+   * no limit
+   * @return Frame, or null if there are no more
+   */
+  Frame fetch(StatementHandle h, long offset, int fetchMaxRowCount) throws
+      NoSuchStatementException, MissingResultsException;
+
+  /** Executes a prepared statement.
+   *
+   * @param h Statement handle
+   * @param parameterValues A list of parameter values; may be empty, not null
+   * @param maxRowCount Maximum number of rows to return; negative means
+   * no limit
+   * @return Execute result
+   */
+  ExecuteResult execute(StatementHandle h, List<TypedValue> parameterValues,
+      long maxRowCount) throws NoSuchStatementException;
+
+  /** Called during the creation of a statement to allocate a new handle.
+   *
+   * @param ch Connection handle
+   */
+  StatementHandle createStatement(ConnectionHandle ch);
+
+  /** Closes a statement. */
+  void closeStatement(StatementHandle h);
+
+  /**
+   * Opens (creates) a connection. The client allocates its own connection ID which the server is
+   * then made aware of through the {@link ConnectionHandle}. The Map {@code info} argument is
+   * analogous to the {@link Properties} typically passed to a "normal" JDBC Driver. Avatica
+   * specific properties should not be included -- only properties for the underlying driver.
+   *
+   * @param ch A ConnectionHandle encapsulates information about the connection to be opened
+   *    as provided by the client.
+   * @param info A Map corresponding to the Properties typically passed to a JDBC Driver.
+   */
+  void openConnection(ConnectionHandle ch, Map<String, String> info);
+
+  /** Closes a connection */
+  void closeConnection(ConnectionHandle ch);
+
+  /**
+   * Re-set the {@link ResultSet} on a Statement. Not a JDBC method.
+   * @return True if there are results to fetch after resetting to the given offset. False otherwise
+   */
+  boolean syncResults(StatementHandle sh, QueryState state, long offset)
+      throws NoSuchStatementException;
+
+  /**
+   * Makes all changes since the last commit/rollback permanent. Analogy to
+   * {@link Connection#commit()}.
+   *
+   * @param ch A reference to the real JDBC Connection.
+   */
+  void commit(ConnectionHandle ch);
+
+  /**
+   * Undoes all changes since the last commit/rollback. Analogy to
+   * {@link Connection#rollback()};
+   *
+   * @param ch A reference to the real JDBC Connection.
+   */
+  void rollback(ConnectionHandle ch);
+
+  /** Sync client and server view of connection properties.
+   *
+   * <p>Note: this interface is considered "experimental" and may undergo further changes as this
+   * functionality is extended to other aspects of state management for
+   * {@link java.sql.Connection}, {@link java.sql.Statement}, and {@link java.sql.ResultSet}.</p>
+   */
+  ConnectionProperties connectionSync(ConnectionHandle ch, ConnectionProperties connProps);
+
+  /** Factory to create instances of {@link Meta}. */
+  interface Factory {
+    Meta create(List<String> args);
+  }
+
+  /** Wrapper to remind API calls that a parameter is a pattern (allows '%' and
+   * '_' wildcards, per the JDBC spec) rather than a string to be matched
+   * exactly. */
+  class Pat {
+    public final String s;
+
+    private Pat(String s) {
+      this.s = s;
+    }
+
+    @Override public String toString() {
+      return "Pat[" + s + "]";
+    }
+
+    @JsonCreator
+    public static Pat of(@JsonProperty("s") String name) {
+      return new Pat(name);
+    }
+  }
+
+  /** Database property.
+   *
+   * <p>Values exist for methods, such as
+   * {@link DatabaseMetaData#getSQLKeywords()}, which always return the same
+   * value at all times and across connections.
+   *
+   * @see #getDatabaseProperties(Meta.ConnectionHandle)
+   */
+  enum DatabaseProperty {
+    /** Database property containing the value of
+     * {@link DatabaseMetaData#getNumericFunctions()}. */
+    GET_NUMERIC_FUNCTIONS(""),
+
+    /** Database property containing the value of
+     * {@link DatabaseMetaData#getStringFunctions()}. */
+    GET_STRING_FUNCTIONS(""),
+
+    /** Database property containing the value of
+     * {@link DatabaseMetaData#getSystemFunctions()}. */
+    GET_SYSTEM_FUNCTIONS(""),
+
+    /** Database property containing the value of
+     * {@link DatabaseMetaData#getTimeDateFunctions()}. */
+    GET_TIME_DATE_FUNCTIONS(""),
+
+    /** Database property containing the value of
+     * {@link DatabaseMetaData#getSQLKeywords()}. */
+    GET_S_Q_L_KEYWORDS(""),
+
+    /** Database property containing the value of
+     * {@link DatabaseMetaData#getDefaultTransactionIsolation()}. */
+    GET_DEFAULT_TRANSACTION_ISOLATION(Connection.TRANSACTION_NONE);
+
+    public final Class<?> type;
+    public final Object defaultValue;
+    public final Method method;
+
+    <T> DatabaseProperty(T defaultValue) {
+      this.defaultValue = defaultValue;
+      final String methodName = AvaticaUtils.toCamelCase(name());
+      try {
+        this.method = DatabaseMetaData.class.getMethod(methodName);
+      } catch (NoSuchMethodException e) {
+        throw new RuntimeException(e);
+      }
+      this.type = AvaticaUtils.box(method.getReturnType());
+      assert defaultValue == null || defaultValue.getClass() == type;
+    }
+
+    /** Returns a value of this property, using the default value if the map
+     * does not contain an explicit value. */
+    public <T> T getProp(Meta meta, ConnectionHandle ch, Class<T> aClass) {
+      return getProp(meta.getDatabaseProperties(ch), aClass);
+    }
+
+    /** Returns a value of this property, using the default value if the map
+     * does not contain an explicit value. */
+    public <T> T getProp(Map<DatabaseProperty, Object> map, Class<T> aClass) {
+      assert aClass == type;
+      Object v = map.get(this);
+      if (v == null) {
+        v = defaultValue;
+      }
+      return aClass.cast(v);
+    }
+
+    public static DatabaseProperty fromProto(Common.DatabaseProperty proto) {
+      return DatabaseProperty.valueOf(proto.getName());
+    }
+
+    public Common.DatabaseProperty toProto() {
+      return Common.DatabaseProperty.newBuilder().setName(name()).build();
+    }
+  }
+
+  /** Response from execute.
+   *
+   * <p>Typically a query will have a result set and rowCount = -1;
+   * a DML statement will have a rowCount and no result sets.
+   */
+  class ExecuteResult {
+    public final List<MetaResultSet> resultSets;
+
+    public ExecuteResult(List<MetaResultSet> resultSets) {
+      this.resultSets = resultSets;
+    }
+  }
+
+  /** Meta data from which a result set can be constructed.
+   *
+   * <p>If {@code updateCount} is not -1, the result is just a count. A result
+   * set cannot be constructed. */
+  class MetaResultSet {
+    public final String connectionId;
+    public final int statementId;
+    public final boolean ownStatement;
+    public final Frame firstFrame;
+    public final Signature signature;
+    public final long updateCount;
+
+    @Deprecated // to be removed before 2.0
+    protected MetaResultSet(String connectionId, int statementId,
+        boolean ownStatement, Signature signature, Frame firstFrame,
+        int updateCount) {
+      this(connectionId, statementId, ownStatement, signature, firstFrame,
+          (long) updateCount);
+    }
+
+    protected MetaResultSet(String connectionId, int statementId,
+        boolean ownStatement, Signature signature, Frame firstFrame,
+        long updateCount) {
+      this.signature = signature;
+      this.connectionId = connectionId;
+      this.statementId = statementId;
+      this.ownStatement = ownStatement;
+      this.firstFrame = firstFrame; // may be null even if signature is not null
+      this.updateCount = updateCount;
+    }
+
+    public static MetaResultSet create(String connectionId, int statementId,
+        boolean ownStatement, Signature signature, Frame firstFrame) {
+      return new MetaResultSet(connectionId, statementId, ownStatement,
+          Objects.requireNonNull(signature), firstFrame, -1L);
+    }
+
+    public static MetaResultSet count(String connectionId, int statementId,
+        long updateCount) {
+      assert updateCount >= 0
+          : "Meta.count(" + connectionId + ", " + statementId + ", "
+          + updateCount + ")";
+      return new MetaResultSet(connectionId, statementId, false, null, null,
+          updateCount);
+    }
+  }
+
+  /** Information necessary to convert an {@link Iterable} into a
+   * {@link org.apache.calcite.avatica.util.Cursor}. */
+  final class CursorFactory {
+    private static final FieldDescriptor CLASS_NAME_DESCRIPTOR = Common.CursorFactory.
+        getDescriptor().findFieldByNumber(Common.CursorFactory.CLASS_NAME_FIELD_NUMBER);
+
+    public final Style style;
+    public final Class clazz;
+    @JsonIgnore
+    public final List<Field> fields;
+    public final List<String> fieldNames;
+
+    private CursorFactory(Style style, Class clazz, List<Field> fields,
+        List<String> fieldNames) {
+      assert (fieldNames != null)
+          == (style == Style.RECORD_PROJECTION || style == Style.MAP);
+      assert (fields != null) == (style == Style.RECORD_PROJECTION);
+      this.style = Objects.requireNonNull(style);
+      this.clazz = clazz;
+      this.fields = fields;
+      this.fieldNames = fieldNames;
+    }
+
+    @JsonCreator
+    public static CursorFactory create(@JsonProperty("style") Style style,
+        @JsonProperty("clazz") Class clazz,
+        @JsonProperty("fieldNames") List<String> fieldNames) {
+      switch (style) {
+      case OBJECT:
+        return OBJECT;
+      case ARRAY:
+        return ARRAY;
+      case LIST:
+        return LIST;
+      case RECORD:
+        return record(clazz);
+      case RECORD_PROJECTION:
+        return record(clazz, null, fieldNames);
+      case MAP:
+        return map(fieldNames);
+      default:
+        throw new AssertionError("unknown style: " + style);
+      }
+    }
+
+    public static final CursorFactory OBJECT =
+        new CursorFactory(Style.OBJECT, null, null, null);
+
+    public static final CursorFactory ARRAY =
+        new CursorFactory(Style.ARRAY, null, null, null);
+
+    public static final CursorFactory LIST =
+        new CursorFactory(Style.LIST, null, null, null);
+
+    public static CursorFactory record(Class resultClazz) {
+      return new CursorFactory(Style.RECORD, resultClazz, null, null);
+    }
+
+    public static CursorFactory record(Class resultClass, List<Field> fields,
+        List<String> fieldNames) {
+      if (fields == null) {
+        fields = new ArrayList<>();
+        for (String fieldName : fieldNames) {
+          try {
+            fields.add(resultClass.getField(fieldName));
+          } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+          }
+        }
+      }
+      return new CursorFactory(Style.RECORD_PROJECTION, resultClass, fields,
+          fieldNames);
+    }
+
+    public static CursorFactory map(List<String> fieldNames) {
+      return new CursorFactory(Style.MAP, null, null, fieldNames);
+    }
+
+    public static CursorFactory deduce(List<ColumnMetaData> columns,
+        Class resultClazz) {
+      if (columns.size() == 1) {
+        return OBJECT;
+      }
+      if (resultClazz == null) {
+        return ARRAY;
+      }
+      if (resultClazz.isArray()) {
+        return ARRAY;
+      }
+      if (List.class.isAssignableFrom(resultClazz)) {
+        return LIST;
+      }
+      return record(resultClazz);
+    }
+
+    public Common.CursorFactory toProto() {
+      Common.CursorFactory.Builder builder = Common.CursorFactory.newBuilder();
+
+      if (null != clazz) {
+        builder.setClassName(clazz.getName());
+      }
+      builder.setStyle(style.toProto());
+      if (null != fieldNames) {
+        builder.addAllFieldNames(fieldNames);
+      }
+
+      return builder.build();
+    }
+
+    public static CursorFactory fromProto(Common.CursorFactory proto) {
+      // Reconstruct CursorFactory
+      Class<?> clz = null;
+
+      if (proto.hasField(CLASS_NAME_DESCRIPTOR)) {
+        try {
+          clz = Class.forName(proto.getClassName());
+        } catch (ClassNotFoundException e) {
+          throw new RuntimeException(e);
+        }
+      }
+
+      return CursorFactory.create(Style.fromProto(proto.getStyle()), clz,
+          proto.getFieldNamesList());
+    }
+
+    @Override public int hashCode() {
+      return Objects.hash(clazz, fieldNames, fields, style);
+    }
+
+    @Override public boolean equals(Object o) {
+      return o == this
+          || o instanceof CursorFactory
+          && Objects.equals(clazz, ((CursorFactory) o).clazz)
+          && Objects.equals(fieldNames, ((CursorFactory) o).fieldNames)
+          && Objects.equals(fields, ((CursorFactory) o).fields)
+          && style == ((CursorFactory) o).style;
+    }
+  }
+
+  /** How logical fields are represented in the objects returned by the
+   * iterator. */
+  enum Style {
+    OBJECT,
+    RECORD,
+    RECORD_PROJECTION,
+    ARRAY,
+    LIST,
+    MAP;
+
+    public Common.CursorFactory.Style toProto() {
+      return Common.CursorFactory.Style.valueOf(name());
+    }
+
+    public static Style fromProto(Common.CursorFactory.Style proto) {
+      return Style.valueOf(proto.name());
+    }
+  }
+
+  /** Result of preparing a statement. */
+  public class Signature {
+    private static final FieldDescriptor SQL_DESCRIPTOR = Common.Signature
+        .getDescriptor().findFieldByNumber(Common.Signature.SQL_FIELD_NUMBER);
+    private static final FieldDescriptor CURSOR_FACTORY_DESCRIPTOR = Common.Signature
+        .getDescriptor().findFieldByNumber(Common.Signature.CURSOR_FACTORY_FIELD_NUMBER);
+
+    public final List<ColumnMetaData> columns;
+    public final String sql;
+    public final List<AvaticaParameter> parameters;
+    public final transient Map<String, Object> internalParameters;
+    public final CursorFactory cursorFactory;
+
+    public final Meta.StatementType statementType;
+
+    /** Creates a Signature. */
+    public Signature(List<ColumnMetaData> columns,
+        String sql,
+        List<AvaticaParameter> parameters,
+        Map<String, Object> internalParameters,
+        CursorFactory cursorFactory,
+        Meta.StatementType statementType) {
+      this.columns = columns;
+      this.sql = sql;
+      this.parameters = parameters;
+      this.internalParameters = internalParameters;
+      this.cursorFactory = cursorFactory;
+      this.statementType = statementType;
+    }
+
+    /** Used by Jackson to create a Signature by de-serializing JSON. */
+    @JsonCreator
+    public static Signature create(
+        @JsonProperty("columns") List<ColumnMetaData> columns,
+        @JsonProperty("sql") String sql,
+        @JsonProperty("parameters") List<AvaticaParameter> parameters,
+        @JsonProperty("cursorFactory") CursorFactory cursorFactory,
+        @JsonProperty("statementType") Meta.StatementType statementType) {
+      return new Signature(columns, sql, parameters,
+          Collections.<String, Object>emptyMap(), cursorFactory, statementType);
+    }
+
+    /** Returns a copy of this Signature, substituting given CursorFactory. */
+    public Signature setCursorFactory(CursorFactory cursorFactory) {
+      return new Signature(columns, sql, parameters, internalParameters,
+          cursorFactory, statementType);
+    }
+
+    /** Creates a copy of this Signature with null lists and maps converted to
+     * empty. */
+    public Signature sanitize() {
+      if (columns == null || parameters == null || internalParameters == null
+          || statementType == null) {
+        return new Signature(sanitize(columns), sql, sanitize(parameters),
+            sanitize(internalParameters), cursorFactory,
+            Meta.StatementType.SELECT);
+      }
+      return this;
+    }
+
+    private <E> List<E> sanitize(List<E> list) {
+      return list == null ? Collections.<E>emptyList() : list;
+    }
+
+    private <K, V> Map<K, V> sanitize(Map<K, V> map) {
+      return map == null ? Collections.<K, V>emptyMap() : map;
+    }
+
+    public Common.Signature toProto() {
+      Common.Signature.Builder builder = Common.Signature.newBuilder();
+
+      if (null != sql) {
+        builder.setSql(sql);
+      }
+
+      if (null != cursorFactory) {
+        builder.setCursorFactory(cursorFactory.toProto());
+      }
+
+      if (null != columns) {
+        for (ColumnMetaData column : columns) {
+          builder.addColumns(column.toProto());
+        }
+      }
+
+      if (null != parameters) {
+        for (AvaticaParameter parameter : parameters) {
+          builder.addParameters(parameter.toProto());
+        }
+      }
+
+      return builder.build();
+    }
+
+    public static Signature fromProto(Common.Signature protoSignature) {
+      List<ColumnMetaData> metadata = new ArrayList<>(protoSignature.getColumnsCount());
+      for (Common.ColumnMetaData protoMetadata : protoSignature.getColumnsList()) {
+        metadata.add(ColumnMetaData.fromProto(protoMetadata));
+      }
+
+      List<AvaticaParameter> parameters = new ArrayList<>(protoSignature.getParametersCount());
+      for (Common.AvaticaParameter protoParam : protoSignature.getParametersList()) {
+        parameters.add(AvaticaParameter.fromProto(protoParam));
+      }
+
+      String sql = null;
+      if (protoSignature.hasField(SQL_DESCRIPTOR)) {
+        sql = protoSignature.getSql();
+      }
+
+      CursorFactory cursorFactory = null;
+      if (protoSignature.hasField(CURSOR_FACTORY_DESCRIPTOR)) {
+        cursorFactory = CursorFactory.fromProto(protoSignature.getCursorFactory());
+      }
+      final Meta.StatementType statementType =
+          Meta.StatementType.fromProto(protoSignature.getStatementType());
+
+      return Signature.create(metadata, sql, parameters, cursorFactory, statementType);
+    }
+
+    @Override public int hashCode() {
+      return Objects.hash(columns, cursorFactory, parameters, sql);
+    }
+
+    @Override public boolean equals(Object o) {
+      return o == this
+          || o instanceof Signature
+          && Objects.equals(columns, ((Signature) o).columns)
+          && Objects.equals(cursorFactory, ((Signature) o).cursorFactory)
+          && Objects.equals(parameters, ((Signature) o).parameters)
+          && Objects.equals(sql, ((Signature) o).sql);
+    }
+  }
+
+  /** A collection of rows. */
+  class Frame {
+    private static final FieldDescriptor HAS_ARRAY_VALUE_DESCRIPTOR = Common.ColumnValue
+        .getDescriptor().findFieldByNumber(Common.ColumnValue.HAS_ARRAY_VALUE_FIELD_NUMBER);
+    private static final FieldDescriptor SCALAR_VALUE_DESCRIPTOR = Common.ColumnValue
+        .getDescriptor().findFieldByNumber(Common.ColumnValue.SCALAR_VALUE_FIELD_NUMBER);
+    /** Frame that has zero rows and is the last frame. */
+    public static final Frame EMPTY =
+        new Frame(0, true, Collections.emptyList());
+
+    /** Frame that has zero rows but may have another frame. */
+    public static final Frame MORE =
+        new Frame(0, false, Collections.emptyList());
+
+    /** Zero-based offset of first row. */
+    public final long offset;
+    /** Whether this is definitely the last frame of rows.
+     * If true, there are no more rows.
+     * If false, there may or may not be more rows. */
+    public final boolean done;
+    /** The rows. */
+    public final Iterable<Object> rows;
+
+    public Frame(long offset, boolean done, Iterable<Object> rows) {
+      this.offset = offset;
+      this.done = done;
+      this.rows = rows;
+    }
+
+    @JsonCreator
+    public static Frame create(@JsonProperty("offset") int offset,
+        @JsonProperty("done") boolean done,
+        @JsonProperty("rows") List<Object> rows) {
+      if (offset == 0 && done && rows.isEmpty()) {
+        return EMPTY;
+      }
+      return new Frame(offset, done, rows);
+    }
+
+    public Common.Frame toProto() {
+      Common.Frame.Builder builder = Common.Frame.newBuilder();
+
+      builder.setDone(done).setOffset(offset);
+
+      for (Object row : this.rows) {
+        if (null == row) {
+          // Does this need to be persisted for some reason?
+          continue;
+        }
+
+        if (row instanceof Object[]) {
+          final Common.Row.Builder rowBuilder = Common.Row.newBuilder();
+
+          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) {
+                columnBuilder.addArrayValue(serializeScalar(listItem));
+              }
+            } else {
+              // The default value, but still explicit.
+              columnBuilder.setHasArrayValue(false);
+              // Only one value for this column, a scalar.
+              columnBuilder.setScalarValue(serializeScalar(element));
+            }
+
+            // Add value to row
+            rowBuilder.addValue(columnBuilder.build());
+          }
+
+          // 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");
+        }
+      }
+
+      return builder.build();
+    }
+
+    static Common.TypedValue serializeScalar(Object element) {
+      final Common.TypedValue.Builder valueBuilder = Common.TypedValue.newBuilder();
+
+      // Numbers
+      if (element instanceof Byte) {
+        valueBuilder.setType(Common.Rep.BYTE).setNumberValue(((Byte) element).longValue());
+      } else if (element instanceof Short) {
+        valueBuilder.setType(Common.Rep.SHORT).setNumberValue(((Short) element).longValue());
+      } else if (element instanceof Integer) {
+        valueBuilder.setType(Common.Rep.INTEGER)
+          .setNumberValue(((Integer) element).longValue());
+      } else if (element instanceof Long) {
+        valueBuilder.setType(Common.Rep.LONG).setNumberValue((Long) element);
+      } else if (element instanceof Double) {
+        valueBuilder.setType(Common.Rep.DOUBLE).setDoubleValue((Double) element);
+      } else if (element instanceof Float) {
+        valueBuilder.setType(Common.Rep.FLOAT).setNumberValue(((Float) element).longValue());
+      } else if (element instanceof BigDecimal) {
+        valueBuilder.setType(Common.Rep.NUMBER)
+          .setDoubleValue(((BigDecimal) element).doubleValue());
+      // Strings
+      } else if (element instanceof String) {
+        valueBuilder.setType(Common.Rep.STRING)
+          .setStringValue((String) element);
+      } else if (element instanceof Character) {
+        valueBuilder.setType(Common.Rep.CHARACTER)
+          .setStringValue(element.toString());
+      // Bytes
+      } else if (element instanceof byte[]) {
+        valueBuilder.setType(Common.Rep.BYTE_STRING)
+          .setBytesValues(ByteString.copyFrom((byte[]) element));
+      // Boolean
+      } else if (element instanceof Boolean) {
+        valueBuilder.setType(Common.Rep.BOOLEAN).setBoolValue((boolean) element);
+      } else if (null == element) {
+        valueBuilder.setType(Common.Rep.NULL);
+      // Unhandled
+      } else {
+        throw new RuntimeException("Unhandled type in Frame: " + element.getClass());
+      }
+
+      return valueBuilder.build();
+    }
+
+    public static Frame fromProto(Common.Frame proto) {
+      List<Object> parsedRows = new ArrayList<>(proto.getRowsCount());
+      for (Common.Row protoRow : proto.getRowsList()) {
+        ArrayList<Object> row = new ArrayList<>(protoRow.getValueCount());
+        for (Common.ColumnValue protoColumn : protoRow.getValueList()) {
+          final Object value;
+          if (!isNewStyleColumn(protoColumn)) {
+            // Backward compatibility
+            value = parseOldStyleColumn(protoColumn);
+          } else {
+            // Current style parsing (separate scalar and array values)
+            value = parseColumn(protoColumn);
+          }
+
+          row.add(value);
+        }
+
+        parsedRows.add(row);
+      }
+
+      return new Frame(proto.getOffset(), proto.getDone(), parsedRows);
+    }
+
+    /**
+     * Determines whether this message contains the new attributes in the
+     * message. We can't directly test for the negative because our
+     * {@code hasField} trick does not work on repeated fields.
+     *
+     * @param column The protobuf column object
+     * @return True if the message is the new style, false otherwise.
+     */
+    static boolean isNewStyleColumn(Common.ColumnValue column) {
+      return column.hasField(HAS_ARRAY_VALUE_DESCRIPTOR)
+          || column.hasField(SCALAR_VALUE_DESCRIPTOR);
+    }
+
+    /**
+     * For Calcite 1.5, we made the mistake of using array length to determine when the value for a
+     * column is a scalar or an array. This method performs the old parsing for backwards
+     * compatibility.
+     *
+     * @param column The protobuf ColumnValue object
+     * @return The parsed value for this column
+     */
+    static Object parseOldStyleColumn(Common.ColumnValue column) {
+      if (column.getValueCount() > 1) {
+        List<Object> array = new ArrayList<>(column.getValueCount());
+        for (Common.TypedValue columnValue : column.getValueList()) {
+          array.add(getScalarValue(columnValue));
+        }
+        return array;
+      } else {
+        return getScalarValue(column.getValue(0));
+      }
+    }
+
+    /**
+     * Parses the value for a ColumnValue using the separated array and scalar attributes.
+     *
+     * @param column The protobuf ColumnValue object
+     * @return The parse value for this column
+     */
+    static Object parseColumn(Common.ColumnValue column) {
+      // Verify that we have one or the other (scalar or array)
+      validateColumnValue(column);
+
+      if (!column.hasField(SCALAR_VALUE_DESCRIPTOR)) {
+        // Array
+        List<Object> array = new ArrayList<>(column.getArrayValueCount());
+        for (Common.TypedValue arrayValue : column.getArrayValueList()) {
+          array.add(getScalarValue(arrayValue));
+        }
+        return array;
+      } else {
+        // Scalar
+        return getScalarValue(column.getScalarValue());
+      }
+    }
+
+    /**
+     * Verifies that a ColumnValue has only a scalar or array value, not both and not neither.
+     *
+     * @param column The protobuf ColumnValue object
+     * @throws IllegalArgumentException When the above condition is not met
+     */
+    static void validateColumnValue(Common.ColumnValue column) {
+      final boolean hasScalar = column.hasField(SCALAR_VALUE_DESCRIPTOR);
+      final boolean hasArrayValue = column.getHasArrayValue();
+
+      // These should always be different
+      if (hasScalar == hasArrayValue) {
+        throw new IllegalArgumentException("A column must have a scalar or array value, not "
+            + (hasScalar ? "both" : "neither"));
+      }
+    }
+
+    static Object getScalarValue(Common.TypedValue protoElement) {
+      // TODO Should these be primitives or Objects?
+      switch (protoElement.getType()) {
+      case BYTE:
+        return Long.valueOf(protoElement.getNumberValue()).byteValue();
+      case SHORT:
+        return Long.valueOf(protoElement.getNumberValue()).shortValue();
+      case INTEGER:
+        return Long.valueOf(protoElement.getNumberValue()).intValue();
+      case LONG:
+        return protoElement.getNumberValue();
+      case FLOAT:
+        return Long.valueOf(protoElement.getNumberValue()).floatValue();
+      case DOUBLE:
+        return protoElement.getDoubleValue();
+      case NUMBER:
+        // TODO more cases here to expand on? BigInteger?
+        return BigDecimal.valueOf(protoElement.getDoubleValue());
+      case STRING:
+        return protoElement.getStringValue();
+      case CHARACTER:
+        // A single character in the string
+        return protoElement.getStringValue().charAt(0);
+      case BYTE_STRING:
+        return protoElement.getBytesValues().toByteArray();
+      case BOOLEAN:
+        return protoElement.getBoolValue();
+      case NULL:
+        return null;
+      default:
+        throw new RuntimeException("Unhandled type: " + protoElement.getType());
+      }
+    }
+
+    @Override public int hashCode() {
+      return Objects.hash(done, offset, rows);
+    }
+
+    @Override public boolean equals(Object o) {
+      return o == this
+          || o instanceof Frame
+          && equalRows(rows, ((Frame) o).rows)
+          && offset == ((Frame) o).offset
+          && done == ((Frame) o).done;
+    }
+
+    private static boolean equalRows(Iterable<Object> rows, Iterable<Object> otherRows) {
+      if (null == rows) {
+        if (null != otherRows) {
+          return false;
+        }
+      } else {
+        Iterator<Object> iter1 = rows.iterator();
+        Iterator<Object> iter2 = otherRows.iterator();
+        while (iter1.hasNext() && iter2.hasNext()) {
+          Object obj1 = iter1.next();
+          Object obj2 = iter2.next();
+
+          // Can't just call equals on an array
+          if (obj1 instanceof Object[]) {
+            if (obj2 instanceof Object[]) {
+              // Compare array and array
+              if (!Arrays.equals((Object[]) obj1, (Object[]) obj2)) {
+                return false;
+              }
+            } else if (obj2 instanceof List) {
+              // compare array and list
+              @SuppressWarnings("unchecked")
+              List<Object> obj2List = (List<Object>) obj2;
+              if (!Arrays.equals((Object[]) obj1, obj2List.toArray())) {
+                return false;
+              }
+            } else {
+              // compare array and something that isn't an array will always fail
+              return false;
+            }
+          } else if (obj1 instanceof List) {
+            if (obj2 instanceof Object[]) {
+              // Compare list and array
+              @SuppressWarnings("unchecked")
+              List<Object> obj1List = (List<Object>) obj1;
+              if (!Arrays.equals(obj1List.toArray(), (Object[]) obj2)) {
+                return false;
+              }
+            } else if (!obj1.equals(obj2)) {
+              // compare list and something else, let it fall to equals()
+              return false;
+            }
+          } else if (!obj1.equals(obj2)) {
+            // Not an array, leave it to equals()
+            return false;
+          }
+        }
+
+        // More elements in one of the iterables
+        if (iter1.hasNext() || iter2.hasNext()) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+  }
+
+  /** Connection handle. */
+  class ConnectionHandle {
+    public final String id;
+
+    @Override public String toString() {
+      return id;
+    }
+
+    @JsonCreator
+    public ConnectionHandle(@JsonProperty("id") String id) {
+      this.id = id;
+    }
+  }
+
+  /** Statement handle. */
+  class StatementHandle {
+    private static final FieldDescriptor SIGNATURE_DESCRIPTOR = Common.StatementHandle
+        .getDescriptor().findFieldByNumber(Common.StatementHandle.SIGNATURE_FIELD_NUMBER);
+    public final String connectionId;
+    public final int id;
+
+    // not final because LocalService#apply(PrepareRequest)
+    /** Only present for PreparedStatement handles, null otherwise. */
+    public Signature signature;
+
+    @Override public String toString() {
+      return connectionId + "::" + Integer.toString(id);
+    }
+
+    @JsonCreator
+    public StatementHandle(
+        @JsonProperty("connectionId") String connectionId,
+        @JsonProperty("id") int id,
+        @JsonProperty("signature") Signature signature) {
+      this.connectionId = connectionId;
+      this.id = id;
+      this.signature = signature;
+    }
+
+    public Common.StatementHandle toProto() {
+      Common.StatementHandle.Builder builder = Common.StatementHandle.newBuilder()
+          .setConnectionId(connectionId).setId(id);
+      if (null != signature) {
+        builder.setSignature(signature.toProto());
+      }
+      return builder.build();
+    }
+
+    public static StatementHandle fromProto(Common.StatementHandle protoHandle) {
+      // Signature is optional in the update path for executes.
+      Signature signature = null;
+      if (protoHandle.hasField(SIGNATURE_DESCRIPTOR)) {
+        signature = Signature.fromProto(protoHandle.getSignature());
+      }
+      return new StatementHandle(protoHandle.getConnectionId(), protoHandle.getId(), signature);
+    }
+
+    @Override public int hashCode() {
+      return Objects.hash(connectionId, id, signature);
+    }
+
+    @Override public boolean equals(Object o) {
+      return o == this
+          || o instanceof StatementHandle
+          && Objects.equals(connectionId, ((StatementHandle) o).connectionId)
+          && Objects.equals(signature, ((StatementHandle) o).signature)
+          && id == ((StatementHandle) o).id;
+    }
+  }
+
+  /** A pojo containing various client-settable {@link java.sql.Connection} properties.
+   *
+   * <p>{@code java.lang} types are used here so that {@code null} can be used to indicate
+   * a value has no been set.</p>
+   *
+   * <p>Note: this interface is considered "experimental" and may undergo further changes as this
+   * functionality is extended to other aspects of state management for
+   * {@link java.sql.Connection}, {@link java.sql.Statement}, and {@link java.sql.ResultSet}.</p>
+   */
+  @JsonTypeInfo(
+      use = JsonTypeInfo.Id.NAME,
+      property = "connProps",
+      defaultImpl = ConnectionPropertiesImpl.class)
+  @JsonSubTypes({
+      @JsonSubTypes.Type(value = ConnectionPropertiesImpl.class, name = "connPropsImpl")
+  })
+  interface ConnectionProperties {
+
+    /** Overwrite fields in {@code this} with any non-null fields in {@code that}
+     *
+     * @return {@code this}
+     */
+    ConnectionProperties merge(ConnectionProperties that);
+
+    /** @return {@code true} when no properies have been set, {@code false} otherwise. */
+    @JsonIgnore
+    boolean isEmpty();
+
+    /** Set {@code autoCommit} status.
+     *
+     * @return {@code this}
+     */
+    ConnectionProperties setAutoCommit(boolean val);
+
+    Boolean isAutoCommit();
+
+    /** Set {@code readOnly} status.
+     *
+     * @return {@code this}
+     */
+    ConnectionProperties setReadOnly(boolean val);
+
+    Boolean isReadOnly();
+
+    /** Set {@code transactionIsolation} status.
+     *
+     * @return {@code this}
+     */
+    ConnectionProperties setTransactionIsolation(int val);
+
+    Integer getTransactionIsolation();
+
+    /** Set {@code catalog}.
+     *
+     * @return {@code this}
+     */
+    ConnectionProperties setCatalog(String val);
+
+    String getCatalog();
+
+    /** Set {@code schema}.
+     *
+     * @return {@code this}
+     */
+    ConnectionProperties setSchema(String val);
+
+    String getSchema();
+
+    Common.ConnectionProperties toProto();
+  }
+
+  /** API to put a result set into a statement, being careful to enforce
+   * thread-safety and not to overwrite existing open result sets. */
+  interface PrepareCallback {
+    Object getMonitor();
+    void clear() throws SQLException;
+    void assign(Signature signature, Frame firstFrame, long updateCount)
+        throws SQLException;
+    void execute() throws SQLException;
+  }
+
+  /** Type of statement. */
+  enum StatementType {
+    SELECT, INSERT, UPDATE, DELETE, UPSERT, MERGE, OTHER_DML, IS_DML,
+    CREATE, DROP, ALTER, OTHER_DDL, CALL;
+
+    public boolean canUpdate() {
+      switch(this) {
+      case INSERT:
+        return true;
+      case IS_DML:
+        return true;
+      default:
+        return false;
+      }
+    }
+
+    public Common.StatementType toProto() {
+      return Common.StatementType.valueOf(name());
+    }
+
+    public static StatementType fromProto(Common.StatementType proto) {
+      return StatementType.valueOf(proto.name());
+    }
+  }
+}
+
+// End Meta.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/MetaImpl.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/MetaImpl.java b/avatica/core/src/main/java/org/apache/calcite/avatica/MetaImpl.java
new file mode 100644
index 0000000..7bb99fc
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/MetaImpl.java
@@ -0,0 +1,961 @@
+/*
+ * 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;
+
+import org.apache.calcite.avatica.remote.TypedValue;
+import org.apache.calcite.avatica.util.ArrayIteratorCursor;
+import org.apache.calcite.avatica.util.Cursor;
+import org.apache.calcite.avatica.util.IteratorCursor;
+import org.apache.calcite.avatica.util.ListIteratorCursor;
+import org.apache.calcite.avatica.util.MapIteratorCursor;
+import org.apache.calcite.avatica.util.RecordIteratorCursor;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+/**
+ * Basic implementation of {@link Meta}.
+ *
+ * <p>Each sub-class must implement the two remaining abstract methods,
+ * {@link #prepare} and
+ * {@link #prepareAndExecute}.
+ * It should also override metadata methods such as {@link #getCatalogs(Meta.ConnectionHandle)} and
+ * {@link #getTables} for the element types for which it has instances; the
+ * default metadata methods return empty collections.
+ */
+public abstract class MetaImpl implements Meta {
+  /** The {@link AvaticaConnection} backing {@code this}. */
+  protected final AvaticaConnection connection;
+  /** Represents the various states specific to {@link #connection}.
+   *
+   * <p>Note: this instance is used recursively with {@link #connection}'s getter and setter
+   * methods.</p>
+   */
+  protected final ConnectionPropertiesImpl connProps;
+
+  public MetaImpl(AvaticaConnection connection) {
+    this.connection = connection;
+    this.connProps = new ConnectionPropertiesImpl();
+  }
+
+  /** Uses a {@link org.apache.calcite.avatica.Meta.CursorFactory} to convert
+   * an {@link Iterable} into a
+   * {@link org.apache.calcite.avatica.util.Cursor}. */
+  public static Cursor createCursor(CursorFactory cursorFactory,
+      Iterable<Object> iterable) {
+    switch (cursorFactory.style) {
+    case OBJECT:
+      return new IteratorCursor<Object>(iterable.iterator()) {
+        protected Getter createGetter(int ordinal) {
+          return new ObjectGetter(ordinal);
+        }
+      };
+    case ARRAY:
+      @SuppressWarnings("unchecked") final Iterable<Object[]> iterable1 =
+          (Iterable<Object[]>) (Iterable) iterable;
+      return new ArrayIteratorCursor(iterable1.iterator());
+    case RECORD:
+      @SuppressWarnings("unchecked") final Class<Object> clazz =
+          cursorFactory.clazz;
+      return new RecordIteratorCursor<Object>(iterable.iterator(), clazz);
+    case RECORD_PROJECTION:
+      @SuppressWarnings("unchecked") final Class<Object> clazz2 =
+          cursorFactory.clazz;
+      return new RecordIteratorCursor<Object>(iterable.iterator(), clazz2,
+          cursorFactory.fields);
+    case LIST:
+      @SuppressWarnings("unchecked") final Iterable<List<Object>> iterable2 =
+          (Iterable<List<Object>>) (Iterable) iterable;
+      return new ListIteratorCursor(iterable2.iterator());
+    case MAP:
+      @SuppressWarnings("unchecked") final Iterable<Map<String, Object>>
+          iterable3 =
+          (Iterable<Map<String, Object>>) (Iterable) iterable;
+      return new MapIteratorCursor(iterable3.iterator(),
+          cursorFactory.fieldNames);
+    default:
+      throw new AssertionError("unknown style: " + cursorFactory.style);
+    }
+  }
+
+  public static List<List<Object>> collect(CursorFactory cursorFactory,
+      final Iterator<Object> iterator, List<List<Object>> list) {
+    final Iterable<Object> iterable = new Iterable<Object>() {
+      public Iterator<Object> iterator() {
+        return iterator;
+      }
+    };
+    return collect(cursorFactory, iterable, list);
+  }
+
+  public static List<List<Object>> collect(CursorFactory cursorFactory,
+      Iterable<Object> iterable, final List<List<Object>> list) {
+    switch (cursorFactory.style) {
+    case OBJECT:
+      for (Object o : iterable) {
+        list.add(Collections.singletonList(o));
+      }
+      return list;
+    case ARRAY:
+      @SuppressWarnings("unchecked") final Iterable<Object[]> iterable1 =
+          (Iterable<Object[]>) (Iterable) iterable;
+      for (Object[] objects : iterable1) {
+        list.add(Arrays.asList(objects));
+      }
+      return list;
+    case RECORD:
+    case RECORD_PROJECTION:
+      final Field[] fields;
+      switch (cursorFactory.style) {
+      case RECORD:
+        fields = cursorFactory.clazz.getFields();
+        break;
+      default:
+        fields = cursorFactory.fields.toArray(
+            new Field[cursorFactory.fields.size()]);
+      }
+      for (Object o : iterable) {
+        final Object[] objects = new Object[fields.length];
+        for (int i = 0; i < fields.length; i++) {
+          Field field = fields[i];
+          try {
+            objects[i] = field.get(o);
+          } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+          }
+        }
+        list.add(Arrays.asList(objects));
+      }
+      return list;
+    case LIST:
+      @SuppressWarnings("unchecked") final Iterable<List<Object>> iterable2 =
+          (Iterable<List<Object>>) (Iterable) iterable;
+      for (List<Object> objects : iterable2) {
+        list.add(objects);
+      }
+      return list;
+    case MAP:
+      @SuppressWarnings("unchecked") final Iterable<Map<String, Object>>
+          iterable3 =
+          (Iterable<Map<String, Object>>) (Iterable) iterable;
+      for (Map<String, Object> map : iterable3) {
+        final List<Object> objects = new ArrayList<Object>();
+        for (String fieldName : cursorFactory.fieldNames) {
+          objects.add(map.get(fieldName));
+        }
+        list.add(objects);
+      }
+      return list;
+    default:
+      throw new AssertionError("unknown style: " + cursorFactory.style);
+    }
+  }
+
+  @Override public void openConnection(ConnectionHandle ch, Map<String, String> info) {
+    // dummy implementation, connection is already created at this point
+  }
+
+  @Override public void closeConnection(ConnectionHandle ch) {
+    // TODO: implement
+    //
+    // lots of Calcite tests break with this simple implementation,
+    // requires investigation
+
+//    try {
+//      connection.close();
+//    } catch (SQLException e) {
+//      throw new RuntimeException(e);
+//    }
+  }
+
+  @Override public ConnectionProperties connectionSync(ConnectionHandle ch,
+      ConnectionProperties connProps) {
+    this.connProps.merge(connProps);
+    this.connProps.setDirty(false);
+    return this.connProps;
+  }
+
+  public StatementHandle createStatement(ConnectionHandle ch) {
+    return new StatementHandle(ch.id, connection.statementCount++, null);
+  }
+
+  /** Creates an empty result set. Useful for JDBC metadata methods that are
+   * not implemented or which query entities that are not supported (e.g.
+   * triggers in Lingual). */
+  protected <E> MetaResultSet createEmptyResultSet(final Class<E> clazz) {
+    return createResultSet(Collections.<String, Object>emptyMap(),
+        fieldMetaData(clazz).columns,
+        CursorFactory.deduce(fieldMetaData(clazz).columns, null),
+        Frame.EMPTY);
+  }
+
+  public static ColumnMetaData columnMetaData(String name, int index,
+      Class<?> type) {
+    TypeInfo pair = TypeInfo.m.get(type);
+    ColumnMetaData.Rep rep =
+        ColumnMetaData.Rep.VALUE_MAP.get(type);
+    ColumnMetaData.AvaticaType scalarType =
+        ColumnMetaData.scalar(pair.sqlType, pair.sqlTypeName, rep);
+    return new ColumnMetaData(
+        index, false, true, false, false,
+        pair.primitive
+            ? DatabaseMetaData.columnNullable
+            : DatabaseMetaData.columnNoNulls,
+        true, -1, name, name, null,
+        0, 0, null, null, scalarType, true, false, false,
+        scalarType.columnClassName());
+  }
+
+  protected static ColumnMetaData.StructType fieldMetaData(Class clazz) {
+    final List<ColumnMetaData> list = new ArrayList<ColumnMetaData>();
+    for (Field field : clazz.getFields()) {
+      if (Modifier.isPublic(field.getModifiers())
+          && !Modifier.isStatic(field.getModifiers())) {
+        list.add(
+            columnMetaData(
+                AvaticaUtils.camelToUpper(field.getName()),
+                list.size() + 1, field.getType()));
+      }
+    }
+    return ColumnMetaData.struct(list);
+  }
+
+  protected MetaResultSet createResultSet(
+      Map<String, Object> internalParameters, List<ColumnMetaData> columns,
+      CursorFactory cursorFactory, Frame firstFrame) {
+    try {
+      final AvaticaStatement statement = connection.createStatement();
+      final Signature signature =
+          new Signature(columns, "", Collections.<AvaticaParameter>emptyList(),
+              internalParameters, cursorFactory, Meta.StatementType.SELECT);
+      return MetaResultSet.create(connection.id, statement.getId(), true,
+          signature, firstFrame);
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /** An object that has a name. */
+  public interface Named {
+    @JsonIgnore String getName();
+  }
+
+  /** Metadata describing a column. */
+  public static class MetaColumn implements Named {
+    public final String tableCat;
+    public final String tableSchem;
+    public final String tableName;
+    public final String columnName;
+    public final int dataType;
+    public final String typeName;
+    public final int columnSize;
+    public final String bufferLength = null;
+    public final Integer decimalDigits;
+    public final int numPrecRadix;
+    public final int nullable;
+    public final String remarks = null;
+    public final String columnDef = null;
+    public final String sqlDataType = null;
+    public final String sqlDatetimeSub = null;
+    public final int charOctetLength;
+    public final int ordinalPosition;
+    public final String isNullable;
+    public final String scopeCatalog = null;
+    public final String scopeSchema = null;
+    public final String scopeTable = null;
+    public final String sourceDataType = null;
+    public final String isAutoincrement = null;
+    public final String isGeneratedcolumn = null;
+
+    public MetaColumn(
+        String tableCat,
+        String tableSchem,
+        String tableName,
+        String columnName,
+        int dataType,
+        String typeName,
+        int columnSize,
+        Integer decimalDigits,
+        int numPrecRadix,
+        int nullable,
+        int charOctetLength,
+        int ordinalPosition,
+        String isNullable) {
+      this.tableCat = tableCat;
+      this.tableSchem = tableSchem;
+      this.tableName = tableName;
+      this.columnName = columnName;
+      this.dataType = dataType;
+      this.typeName = typeName;
+      this.columnSize = columnSize;
+      this.decimalDigits = decimalDigits;
+      this.numPrecRadix = numPrecRadix;
+      this.nullable = nullable;
+      this.charOctetLength = charOctetLength;
+      this.ordinalPosition = ordinalPosition;
+      this.isNullable = isNullable;
+    }
+
+    public String getName() {
+      return columnName;
+    }
+  }
+
+  /** Metadata describing a TypeInfo. */
+
+  /** Metadata describing a table. */
+  public static class MetaTable implements Named {
+    public final String tableCat;
+    public final String tableSchem;
+    public final String tableName;
+    public final String tableType;
+    public final String remarks = null;
+    public final String typeCat = null;
+    public final String typeSchem = null;
+    public final String typeName = null;
+    public final String selfReferencingColName = null;
+    public final String refGeneration = null;
+
+    public MetaTable(String tableCat,
+        String tableSchem,
+        String tableName,
+        String tableType) {
+      this.tableCat = tableCat;
+      this.tableSchem = tableSchem;
+      this.tableName = tableName;
+      this.tableType = tableType;
+    }
+
+    public String getName() {
+      return tableName;
+    }
+  }
+
+  /** Metadata describing a schema. */
+  public static class MetaSchema implements Named {
+    public final String tableCatalog;
+    public final String tableSchem;
+
+    public MetaSchema(
+        String tableCatalog,
+        String tableSchem) {
+      this.tableCatalog = tableCatalog;
+      this.tableSchem = tableSchem;
+    }
+
+    public String getName() {
+      return tableSchem;
+    }
+  }
+
+  /** Metadata describing a catalog. */
+  public static class MetaCatalog implements Named {
+    public final String tableCat;
+
+    public MetaCatalog(
+        String tableCatalog) {
+      this.tableCat = tableCatalog;
+    }
+
+    public String getName() {
+      return tableCat;
+    }
+  }
+
+  /** Metadata describing a table type. */
+  public static class MetaTableType {
+    public final String tableType;
+
+    public MetaTableType(String tableType) {
+      this.tableType = tableType;
+    }
+  }
+
+  /** Metadata describing a procedure. */
+  public static class MetaProcedure {
+  }
+
+  /** Metadata describing a procedure column. */
+  public static class MetaProcedureColumn {
+  }
+
+  /** Metadata describing a column privilege. */
+  public static class MetaColumnPrivilege {
+  }
+
+  /** Metadata describing a table privilege. */
+  public static class MetaTablePrivilege {
+  }
+
+  /** Metadata describing the best identifier for a row. */
+  public static class MetaBestRowIdentifier {
+  }
+
+  /** Metadata describing a version column. */
+  public static class MetaVersionColumn {
+    public final short scope;
+    public final String columnName;
+    public final int dataType;
+    public final String typeName;
+    public final int columnSize;
+    public final int bufferLength;
+    public final short decimalDigits;
+    public final short pseudoColumn;
+
+    MetaVersionColumn(short scope, String columnName, int dataType,
+        String typeName, int columnSize, int bufferLength, short decimalDigits,
+        short pseudoColumn) {
+      this.scope = scope;
+      this.columnName = columnName;
+      this.dataType = dataType;
+      this.typeName = typeName;
+      this.columnSize = columnSize;
+      this.bufferLength = bufferLength;
+      this.decimalDigits = decimalDigits;
+      this.pseudoColumn = pseudoColumn;
+    }
+  }
+
+  /** Metadata describing a primary key. */
+  public static class MetaPrimaryKey {
+    public final String tableCat;
+    public final String tableSchem;
+    public final String tableName;
+    public final String columnName;
+    public final short keySeq;
+    public final String pkName;
+
+    MetaPrimaryKey(String tableCat, String tableSchem, String tableName,
+        String columnName, short keySeq, String pkName) {
+      this.tableCat = tableCat;
+      this.tableSchem = tableSchem;
+      this.tableName = tableName;
+      this.columnName = columnName;
+      this.keySeq = keySeq;
+      this.pkName = pkName;
+    }
+  }
+
+  /** Metadata describing an imported key. */
+  public static class MetaImportedKey {
+  }
+
+  /** Metadata describing an exported key. */
+  public static class MetaExportedKey {
+  }
+
+  /** Metadata describing a cross reference. */
+  public static class MetaCrossReference {
+  }
+
+  /** Metadata describing type info. */
+  public static class MetaTypeInfo implements Named {
+    public final String typeName;
+    public final int dataType;
+    public final int precision;
+    public final String literalPrefix;
+    public final String literalSuffix;
+    //TODO: Add create parameter for type on DDL
+    public final String createParams = null;
+    public final int nullable;
+    public final boolean caseSensitive;
+    public final int searchable;
+    public final boolean unsignedAttribute;
+    public final boolean fixedPrecScale;
+    public final boolean autoIncrement;
+    public final String localTypeName;
+    public final int minimumScale;
+    public final int maximumScale;
+    public final int sqlDataType = 0;
+    public final int sqlDatetimeSub = 0;
+    public final Integer numPrecRadix; //nullable int
+
+    public MetaTypeInfo(
+        String typeName,
+        int dataType,
+        int precision,
+        String literalPrefix,
+        String literalSuffix,
+        int nullable,
+        boolean caseSensitive,
+        int searchable,
+        boolean unsignedAttribute,
+        boolean fixedPrecScale,
+        boolean autoIncrement,
+        int minimumScale,
+        int maximumScale,
+        int numPrecRadix) {
+      this.typeName = typeName;
+      this.dataType = dataType;
+      this.precision = precision;
+      this.literalPrefix = literalPrefix;
+      this.literalSuffix = literalSuffix;
+      this.nullable = nullable;
+      this.caseSensitive = caseSensitive;
+      this.searchable = searchable;
+      this.unsignedAttribute = unsignedAttribute;
+      this.fixedPrecScale = fixedPrecScale;
+      this.autoIncrement = autoIncrement;
+      this.localTypeName = typeName;
+      // Make min to be 0 instead of -1
+      this.minimumScale = minimumScale == -1 ? 0 : minimumScale;
+      this.maximumScale = maximumScale == -1 ? 0 : maximumScale;
+      this.numPrecRadix = numPrecRadix == 0 ? null : numPrecRadix;
+    }
+
+    public String getName() {
+      return typeName;
+    }
+  }
+
+  /** Metadata describing index info. */
+  public static class MetaIndexInfo {
+  }
+
+  /** Metadata describing a user-defined type. */
+  public static class MetaUdt {
+  }
+
+  /** Metadata describing a super-type. */
+  public static class MetaSuperType {
+  }
+
+  /** Metadata describing an attribute. */
+  public static class MetaAttribute {
+  }
+
+  /** Metadata describing a client info property. */
+  public static class MetaClientInfoProperty {
+  }
+
+  /** Metadata describing a function. */
+  public static class MetaFunction {
+  }
+
+  /** Metadata describing a function column. */
+  public static class MetaFunctionColumn {
+  }
+
+  /** Metadata describing a pseudo column. */
+  public static class MetaPseudoColumn {
+  }
+
+  /** Metadata describing a super-table. */
+  public static class MetaSuperTable {
+  }
+
+  public Map<DatabaseProperty, Object> getDatabaseProperties(ConnectionHandle ch) {
+    return Collections.emptyMap();
+  }
+
+  public MetaResultSet getTables(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat tableNamePattern,
+      List<String> typeList) {
+    return createEmptyResultSet(MetaTable.class);
+  }
+
+  public MetaResultSet getColumns(ConnectionHandle ch, String catalog,
+      Pat schemaPattern,
+      Pat tableNamePattern,
+      Pat columnNamePattern) {
+    return createEmptyResultSet(MetaColumn.class);
+  }
+
+  public MetaResultSet getSchemas(ConnectionHandle ch, String catalog, Pat schemaPattern) {
+    return createEmptyResultSet(MetaSchema.class);
+  }
+
+  public MetaResultSet getCatalogs(ConnectionHandle ch) {
+    return createEmptyResultSet(MetaCatalog.class);
+  }
+
+  public MetaResultSet getTableTypes(ConnectionHandle ch) {
+    return createEmptyResultSet(MetaTableType.class);
+  }
+
+  public MetaResultSet getProcedures(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat procedureNamePattern) {
+    return createEmptyResultSet(MetaProcedure.class);
+  }
+
+  public MetaResultSet getProcedureColumns(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat procedureNamePattern,
+      Pat columnNamePattern) {
+    return createEmptyResultSet(MetaProcedureColumn.class);
+  }
+
+  public MetaResultSet getColumnPrivileges(ConnectionHandle ch,
+      String catalog,
+      String schema,
+      String table,
+      Pat columnNamePattern) {
+    return createEmptyResultSet(MetaColumnPrivilege.class);
+  }
+
+  public MetaResultSet getTablePrivileges(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat tableNamePattern) {
+    return createEmptyResultSet(MetaTablePrivilege.class);
+  }
+
+  public MetaResultSet getBestRowIdentifier(ConnectionHandle ch,
+      String catalog,
+      String schema,
+      String table,
+      int scope,
+      boolean nullable) {
+    return createEmptyResultSet(MetaBestRowIdentifier.class);
+  }
+
+  public MetaResultSet getVersionColumns(ConnectionHandle ch,
+      String catalog,
+      String schema,
+      String table) {
+    return createEmptyResultSet(MetaVersionColumn.class);
+  }
+
+  public MetaResultSet getPrimaryKeys(ConnectionHandle ch,
+      String catalog,
+      String schema,
+      String table) {
+    return createEmptyResultSet(MetaPrimaryKey.class);
+  }
+
+  public MetaResultSet getImportedKeys(ConnectionHandle ch,
+      String catalog,
+      String schema,
+      String table) {
+    return createEmptyResultSet(MetaImportedKey.class);
+  }
+
+  public MetaResultSet getExportedKeys(ConnectionHandle ch,
+      String catalog,
+      String schema,
+      String table) {
+    return createEmptyResultSet(MetaExportedKey.class);
+  }
+
+  public MetaResultSet getCrossReference(ConnectionHandle ch,
+      String parentCatalog,
+      String parentSchema,
+      String parentTable,
+      String foreignCatalog,
+      String foreignSchema,
+      String foreignTable) {
+    return createEmptyResultSet(MetaCrossReference.class);
+  }
+
+  public MetaResultSet getTypeInfo(ConnectionHandle ch) {
+    return createEmptyResultSet(MetaTypeInfo.class);
+  }
+
+  public MetaResultSet getIndexInfo(ConnectionHandle ch,
+      String catalog,
+      String schema,
+      String table,
+      boolean unique,
+      boolean approximate) {
+    return createEmptyResultSet(MetaIndexInfo.class);
+  }
+
+  public MetaResultSet getUDTs(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat typeNamePattern,
+      int[] types) {
+    return createEmptyResultSet(MetaUdt.class);
+  }
+
+  public MetaResultSet getSuperTypes(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat typeNamePattern) {
+    return createEmptyResultSet(MetaSuperType.class);
+  }
+
+  public MetaResultSet getSuperTables(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat tableNamePattern) {
+    return createEmptyResultSet(MetaSuperTable.class);
+  }
+
+  public MetaResultSet getAttributes(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat typeNamePattern,
+      Pat attributeNamePattern) {
+    return createEmptyResultSet(MetaAttribute.class);
+  }
+
+  public MetaResultSet getClientInfoProperties(ConnectionHandle ch) {
+    return createEmptyResultSet(MetaClientInfoProperty.class);
+  }
+
+  public MetaResultSet getFunctions(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat functionNamePattern) {
+    return createEmptyResultSet(MetaFunction.class);
+  }
+
+  public MetaResultSet getFunctionColumns(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat functionNamePattern,
+      Pat columnNamePattern) {
+    return createEmptyResultSet(MetaFunctionColumn.class);
+  }
+
+  public MetaResultSet getPseudoColumns(ConnectionHandle ch,
+      String catalog,
+      Pat schemaPattern,
+      Pat tableNamePattern,
+      Pat columnNamePattern) {
+    return createEmptyResultSet(MetaPseudoColumn.class);
+  }
+
+  @Override public Iterable<Object> createIterable(StatementHandle handle, QueryState state,
+      Signature signature, List<TypedValue> parameterValues, Frame firstFrame) {
+    if (firstFrame != null && firstFrame.done) {
+      return firstFrame.rows;
+    }
+    AvaticaStatement stmt;
+    try {
+      stmt = connection.lookupStatement(handle);
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+    return new FetchIterable(stmt, state,
+        firstFrame, parameterValues);
+  }
+
+  public Frame fetch(AvaticaStatement stmt, List<TypedValue> parameterValues,
+      long offset, int fetchMaxRowCount) throws NoSuchStatementException, MissingResultsException {
+    return null;
+  }
+
+  /** Information about a type. */
+  private static class TypeInfo {
+    private static Map<Class<?>, TypeInfo> m =
+        new HashMap<Class<?>, TypeInfo>();
+    static {
+      put(boolean.class, true, Types.BOOLEAN, "BOOLEAN");
+      put(Boolean.class, false, Types.BOOLEAN, "BOOLEAN");
+      put(byte.class, true, Types.TINYINT, "TINYINT");
+      put(Byte.class, false, Types.TINYINT, "TINYINT");
+      put(short.class, true, Types.SMALLINT, "SMALLINT");
+      put(Short.class, false, Types.SMALLINT, "SMALLINT");
+      put(int.class, true, Types.INTEGER, "INTEGER");
+      put(Integer.class, false, Types.INTEGER, "INTEGER");
+      put(long.class, true, Types.BIGINT, "BIGINT");
+      put(Long.class, false, Types.BIGINT, "BIGINT");
+      put(float.class, true, Types.FLOAT, "FLOAT");
+      put(Float.class, false, Types.FLOAT, "FLOAT");
+      put(double.class, true, Types.DOUBLE, "DOUBLE");
+      put(Double.class, false, Types.DOUBLE, "DOUBLE");
+      put(String.class, false, Types.VARCHAR, "VARCHAR");
+      put(java.sql.Date.class, false, Types.DATE, "DATE");
+      put(Time.class, false, Types.TIME, "TIME");
+      put(Timestamp.class, false, Types.TIMESTAMP, "TIMESTAMP");
+    }
+
+    private final boolean primitive;
+    private final int sqlType;
+    private final String sqlTypeName;
+
+    public TypeInfo(boolean primitive, int sqlType, String sqlTypeName) {
+      this.primitive = primitive;
+      this.sqlType = sqlType;
+      this.sqlTypeName = sqlTypeName;
+    }
+
+    static void put(Class clazz, boolean primitive, int sqlType,
+        String sqlTypeName) {
+      m.put(clazz, new TypeInfo(primitive, sqlType, sqlTypeName));
+    }
+  }
+
+  /** Iterator that never returns any elements. */
+  private static class EmptyIterator implements Iterator<Object> {
+    public static final Iterator<Object> INSTANCE = new EmptyIterator();
+
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+
+    public boolean hasNext() {
+      return false;
+    }
+
+    public Object next() {
+      throw new NoSuchElementException();
+    }
+  }
+
+  /** Iterable that yields an iterator over rows coming from a sequence of
+   * {@link Meta.Frame}s. */
+  private class FetchIterable implements Iterable<Object> {
+    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) {
+      this.stmt = stmt;
+      this.state = state;
+      this.firstFrame = firstFrame;
+      this.parameterValues = parameterValues;
+    }
+
+    public Iterator<Object> iterator() {
+      return new FetchIterator(stmt, state, firstFrame, parameterValues);
+    }
+  }
+
+  /** Iterator over rows coming from a sequence of {@link Meta.Frame}s. */
+  private class FetchIterator implements Iterator<Object> {
+    private final AvaticaStatement stmt;
+    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) {
+      this.stmt = stmt;
+      this.state = state;
+      this.parameterValues = parameterValues;
+      this.originalParameterValues = parameterValues;
+      if (firstFrame == null) {
+        frame = Frame.MORE;
+        rows = EmptyIterator.INSTANCE;
+      } else {
+        frame = firstFrame;
+        rows = firstFrame.rows.iterator();
+      }
+      moveNext();
+    }
+
+    public void remove() {
+      throw new UnsupportedOperationException("remove");
+    }
+
+    public boolean hasNext() {
+      return rows != null;
+    }
+
+    public Object next() {
+      if (rows == null) {
+        throw new NoSuchElementException();
+      }
+      final Object o = rows.next();
+      currentOffset++;
+      moveNext();
+      return o;
+    }
+
+    private void moveNext() {
+      for (;;) {
+        if (rows.hasNext()) {
+          break;
+        }
+        if (frame.done) {
+          rows = null;
+          break;
+        }
+        try {
+          // currentOffset updated after element is read from `rows` iterator
+          frame = fetch(stmt.handle, currentOffset, AvaticaStatement.DEFAULT_FETCH_SIZE);
+        } catch (NoSuchStatementException e) {
+          resetStatement();
+          // re-fetch the batch where we left off
+          continue;
+        } catch (MissingResultsException e) {
+          try {
+            // We saw the statement, but it didnt' have a resultset initialized. So, reset it.
+            if (!stmt.syncResults(state, currentOffset)) {
+              // This returned false, so there aren't actually any more results to iterate over
+              frame = null;
+              rows = null;
+              break;
+            }
+            // syncResults returning true means we need to fetch those results
+          } catch (NoSuchStatementException e1) {
+            // Tried to reset the result set, but lost the statement, save a loop before retrying.
+            resetStatement();
+            // Will just loop back around to a MissingResultsException, but w/e recursion
+          }
+          // 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;
+        }
+        // It is valid for rows to be empty, so we go around the loop again to
+        // check
+        rows = frame.rows.iterator();
+      }
+    }
+
+    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();
+    }
+  }
+
+  /** Returns whether a list of parameter values has any null elements. */
+  public static boolean checkParameterValueHasNull(List<TypedValue> parameterValues) {
+    for (TypedValue x : parameterValues) {
+      if (x == null) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
+// End MetaImpl.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/MissingResultsException.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/MissingResultsException.java b/avatica/core/src/main/java/org/apache/calcite/avatica/MissingResultsException.java
new file mode 100644
index 0000000..7746769
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/MissingResultsException.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;
+
+import org.apache.calcite.avatica.Meta.StatementHandle;
+
+import java.sql.ResultSet;
+
+/**
+ * An Exception which denotes that a cached Statement is present but has no {@link ResultSet}.
+ */
+public class MissingResultsException extends Exception {
+
+  private static final long serialVersionUID = 1L;
+
+  private final StatementHandle handle;
+
+  public MissingResultsException(StatementHandle handle) {
+    this.handle = handle;
+  }
+
+  public StatementHandle getHandle() {
+    return handle;
+  }
+}
+
+// End MissingResultsException.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchConnectionException.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchConnectionException.java b/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchConnectionException.java
new file mode 100644
index 0000000..b5a940d
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchConnectionException.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * An Exception that denotes that the given Connection is not cached.
+ */
+public class NoSuchConnectionException extends RuntimeException {
+
+  private static final long serialVersionUID = 1L;
+
+  private final String connectionId;
+
+  public NoSuchConnectionException(String connectionId) {
+    this.connectionId = connectionId;
+  }
+
+  public String getConnectionId() {
+    return connectionId;
+  }
+}
+
+// End NoSuchConnectionException.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchStatementException.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchStatementException.java b/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchStatementException.java
new file mode 100644
index 0000000..321011b
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/NoSuchStatementException.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+import org.apache.calcite.avatica.Meta.StatementHandle;
+
+/**
+ * An Exception that denotes that the given Statement is not cached.
+ */
+public class NoSuchStatementException extends Exception {
+
+  private static final long serialVersionUID = 1L;
+
+  private final StatementHandle stmtHandle;
+
+  public NoSuchStatementException(StatementHandle stmtHandle) {
+    this.stmtHandle = stmtHandle;
+  }
+
+  public StatementHandle getStatementHandle() {
+    return stmtHandle;
+  }
+}
+
+// End NoSuchStatementException.java