You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2015/03/23 18:26:18 UTC

[4/9] incubator-calcite git commit: [CALCITE-618] Add Avatica support for getTables (Julian Hyde and Nick Dimiduk)

[CALCITE-618] Add Avatica support for getTables (Julian Hyde and Nick Dimiduk)

Also add getSchemas.

Remove dependencies on Guava. (Avatica depends only on JDK and Jackson.)

Add JdbcMeta; implement remote execute query

Rename some fields of AvaticaType to prevent clash between JSON "type" and type field.

Add FetchRequest; if FetchRequest has parameters, executes first.

Document Avatica

Move class ConnectionSpec to top-level

Use scott-data-hsqldb rather than foodmart-data-hsqldb for testing Avatica; it is smaller and therefore loads faster

Work around JSON's mangling of small longs into ints


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

Branch: refs/heads/master
Commit: 4d2f647a6d397a4a7c99fcc2d3d6359baf937943
Parents: 2b07a9e
Author: Julian Hyde <jh...@apache.org>
Authored: Thu Mar 12 14:51:14 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Mar 23 01:44:36 2015 -0700

----------------------------------------------------------------------
 avatica/pom.xml                                 |  11 +
 .../calcite/avatica/AvaticaConnection.java      |  30 +-
 .../apache/calcite/avatica/AvaticaFactory.java  |  11 +-
 .../calcite/avatica/AvaticaJdbc41Factory.java   |   4 +-
 .../avatica/AvaticaPreparedStatement.java       |  12 +-
 .../calcite/avatica/AvaticaResultSet.java       |  12 +-
 .../avatica/AvaticaResultSetMetaData.java       |   4 +-
 .../calcite/avatica/AvaticaStatement.java       |  33 +-
 .../apache/calcite/avatica/ColumnMetaData.java  |  46 +-
 .../java/org/apache/calcite/avatica/Meta.java   |  90 +++-
 .../org/apache/calcite/avatica/MetaImpl.java    | 131 ++++--
 .../calcite/avatica/UnregisteredDriver.java     |   2 +-
 .../apache/calcite/avatica/jdbc/JdbcMeta.java   | 442 +++++++++++++++++++
 .../calcite/avatica/jdbc/JdbcResultSet.java     | 105 +++++
 .../calcite/avatica/jdbc/package-info.java      |  22 +
 .../calcite/avatica/remote/JsonService.java     |  23 +-
 .../calcite/avatica/remote/LocalService.java    |  78 +++-
 .../calcite/avatica/remote/MockJsonService.java |  44 +-
 .../calcite/avatica/remote/RemoteMeta.java      |  45 +-
 .../apache/calcite/avatica/remote/Service.java  |  75 +++-
 .../calcite/avatica/util/AbstractCursor.java    |  75 +++-
 .../apache/calcite/avatica/util/ArrayImpl.java  |   8 +-
 .../calcite/avatica/test/RemoteDriverTest.java  | 216 ++++++++-
 core/pom.xml                                    |   5 +
 .../calcite/jdbc/CalciteJdbc41Factory.java      |   4 +-
 .../apache/calcite/jdbc/CalciteMetaImpl.java    |  12 +-
 .../apache/calcite/jdbc/CalciteResultSet.java   |   7 +-
 .../materialize/MaterializationService.java     |   2 +-
 .../org/apache/calcite/test/CalciteAssert.java  |  47 +-
 .../org/apache/calcite/test/ConnectionSpec.java |  37 ++
 .../apache/calcite/test/JdbcAdapterTest.java    |   6 +-
 .../java/org/apache/calcite/test/JdbcTest.java  |  25 +-
 doc/avatica.md                                  |  81 ++++
 pom.xml                                         |  37 +-
 34 files changed, 1566 insertions(+), 216 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/pom.xml
----------------------------------------------------------------------
diff --git a/avatica/pom.xml b/avatica/pom.xml
index 58aec3c..3e6040c 100644
--- a/avatica/pom.xml
+++ b/avatica/pom.xml
@@ -45,6 +45,17 @@ limitations under the License.
       <artifactId>junit</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>net.hydromatic</groupId>
+      <artifactId>scott-data-hsqldb</artifactId>
+      <version>0.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.hsqldb</groupId>
+      <artifactId>hsqldb</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java
index 492ec5f..bc8c3eb 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java
@@ -269,7 +269,17 @@ public abstract class AvaticaConnection implements Connection {
       int resultSetType,
       int resultSetConcurrency,
       int resultSetHoldability) throws SQLException {
-    throw new UnsupportedOperationException(); // TODO:
+    try {
+      // TODO: cut out round-trip to create a statement handle
+      final Meta.ConnectionHandle ch = new Meta.ConnectionHandle(id);
+      final Meta.StatementHandle h = meta.createStatement(ch);
+
+      final Meta.Signature x = meta.prepare(h, sql, -1);
+      return factory.newPreparedStatement(this, h, x, resultSetType,
+          resultSetConcurrency, resultSetHoldability);
+    } catch (RuntimeException e) {
+      throw helper.createException("while preparing SQL: " + sql, e);
+    }
   }
 
   public CallableStatement prepareCall(
@@ -391,12 +401,12 @@ public abstract class AvaticaConnection implements Connection {
    *
    * @param statement     Statement
    * @param signature     Prepared query
-   * @param iterable      List of rows, or null if we need to execute
+   * @param firstFrame    First frame of rows, or null if we need to execute
    * @return Result set
    * @throws java.sql.SQLException if a database error occurs
    */
   protected ResultSet executeQueryInternal(AvaticaStatement statement,
-      Meta.Signature signature, Iterable<Object> iterable) throws SQLException {
+      Meta.Signature signature, Meta.Frame firstFrame) throws SQLException {
     // Close the previous open result set, if there is one.
     synchronized (statement) {
       if (statement.openResultSet != null) {
@@ -412,7 +422,7 @@ public abstract class AvaticaConnection implements Connection {
 
       final TimeZone timeZone = getTimeZone();
       statement.openResultSet =
-          factory.newResultSet(statement, signature, timeZone, iterable);
+          factory.newResultSet(statement, signature, timeZone, firstFrame);
     }
     // Release the monitor before executing, to give another thread the
     // opportunity to call cancel.
@@ -447,11 +457,12 @@ public abstract class AvaticaConnection implements Connection {
             }
           }
 
-          public void assign(Meta.Signature signature,
-              Iterable<Object> iterable) throws SQLException {
+          public void assign(Meta.Signature signature, Meta.Frame firstFrame)
+              throws SQLException {
             final TimeZone timeZone = getTimeZone();
             statement.openResultSet =
-                factory.newResultSet(statement, signature, timeZone, iterable);
+                factory.newResultSet(statement, signature, timeZone,
+                    firstFrame);
           }
 
           public void execute() throws SQLException {
@@ -467,9 +478,8 @@ public abstract class AvaticaConnection implements Connection {
     final Meta.StatementHandle h =
         new Meta.StatementHandle(metaResultSet.statementId);
     final AvaticaStatement statement = lookupStatement(h);
-    return executeQueryInternal(statement,
-        metaResultSet.signature,
-        metaResultSet.iterable);
+    return executeQueryInternal(statement, metaResultSet.signature.sanitize(),
+        metaResultSet.firstFrame);
   }
 
   /** Creates a statement wrapper around an existing handle. */

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaFactory.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaFactory.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaFactory.java
index 0139ef3..c6d2ede 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaFactory.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaFactory.java
@@ -50,20 +50,15 @@ public interface AvaticaFactory {
    * Creates a result set. You will then need to call
    * {@link AvaticaResultSet#execute()} on it.
    *
-   * <p>If {@code signature} implements
-   * {@link org.apache.calcite.avatica.MetaImpl.WithIterable} we do not need
-   * to execute; we can use pre-canned data. This mechanism is used for
-   * metadata requests such as {@code getTables}.
-   *
    * @param statement Statement
    * @param signature Prepared statement
    * @param timeZone Time zone
-   * @param iterable Iterable over rows in result, or null if an execute/fetch
-   *                 is required
+   * @param firstFrame Frame containing the first (or perhaps only) rows in the
+   *                   result, or null if an execute/fetch is required
    * @return Result set
    */
   AvaticaResultSet newResultSet(AvaticaStatement statement,
-      Meta.Signature signature, TimeZone timeZone, Iterable<Object> iterable)
+      Meta.Signature signature, TimeZone timeZone, Meta.Frame firstFrame)
       throws SQLException;
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaJdbc41Factory.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaJdbc41Factory.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaJdbc41Factory.java
index 4b2f721..6a70a26 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaJdbc41Factory.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaJdbc41Factory.java
@@ -84,11 +84,11 @@ class AvaticaJdbc41Factory implements AvaticaFactory {
   }
 
   public AvaticaResultSet newResultSet(AvaticaStatement statement,
-      Meta.Signature signature, TimeZone timeZone, Iterable<Object> iterable) {
+      Meta.Signature signature, TimeZone timeZone, Meta.Frame firstFrame) {
     final ResultSetMetaData metaData =
         newResultSetMetaData(statement, signature);
     return new AvaticaResultSet(statement, signature, metaData, timeZone,
-        iterable);
+        firstFrame);
   }
 
   public AvaticaResultSetMetaData newResultSetMetaData(

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaPreparedStatement.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaPreparedStatement.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaPreparedStatement.java
index a40e8d1..9324db7 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaPreparedStatement.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaPreparedStatement.java
@@ -32,7 +32,7 @@ import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
 import java.sql.Time;
 import java.sql.Timestamp;
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Calendar;
 import java.util.List;
 
@@ -76,11 +76,7 @@ public abstract class AvaticaPreparedStatement
   }
 
   @Override protected List<Object> getParameterValues() {
-    final List<Object> list = new ArrayList<Object>();
-    for (Object o : slots) {
-      list.add(o == AvaticaParameter.DUMMY_VALUE ? null : o);
-    }
-    return list;
+    return Arrays.asList(slots);
   }
 
   // implement PreparedStatement
@@ -174,7 +170,9 @@ public abstract class AvaticaPreparedStatement
   }
 
   public void clearParameters() throws SQLException {
-    throw new UnsupportedOperationException();
+    for (int i = 0; i < slots.length; i++) {
+      slots[i] = null;
+    }
   }
 
   public void setObject(int parameterIndex, Object x, int targetSqlType)

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaResultSet.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaResultSet.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaResultSet.java
index 8d2fbee..fca227f 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaResultSet.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaResultSet.java
@@ -49,7 +49,7 @@ import java.util.TimeZone;
 public class AvaticaResultSet implements ResultSet, ArrayImpl.Factory {
   protected final AvaticaStatement statement;
   protected final Meta.Signature signature;
-  protected final Iterable<Object> iterable;
+  protected final Meta.Frame firstFrame;
   protected final List<ColumnMetaData> columnMetaDataList;
   protected final ResultSetMetaData resultSetMetaData;
   protected final Calendar localCalendar;
@@ -71,10 +71,10 @@ public class AvaticaResultSet implements ResultSet, ArrayImpl.Factory {
       Meta.Signature signature,
       ResultSetMetaData resultSetMetaData,
       TimeZone timeZone,
-      Iterable<Object> iterable) {
+      Meta.Frame firstFrame) {
     this.statement = statement;
     this.signature = signature;
-    this.iterable = iterable;
+    this.firstFrame = firstFrame;
     this.columnMetaDataList = signature.columns;
     this.type = statement.resultSetType;
     this.concurrency = statement.resultSetConcurrency;
@@ -179,9 +179,11 @@ public class AvaticaResultSet implements ResultSet, ArrayImpl.Factory {
    * @throws SQLException if execute fails for some reason.
    */
   protected AvaticaResultSet execute() throws SQLException {
-    this.cursor = MetaImpl.createCursor(signature.cursorFactory,
+    final List<Object> parameterValues = statement.getBoundParameterValues();
+    final Iterable<Object> iterable1 =
         statement.connection.meta.createIterable(statement.handle, signature,
-            iterable));
+            parameterValues, firstFrame);
+    this.cursor = MetaImpl.createCursor(signature.cursorFactory, iterable1);
     this.accessorList =
         cursor.createAccessors(columnMetaDataList, localCalendar, this);
     this.row = -1;

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaResultSetMetaData.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaResultSetMetaData.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaResultSetMetaData.java
index cb67a89..b4e8892 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaResultSetMetaData.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaResultSetMetaData.java
@@ -104,11 +104,11 @@ public class AvaticaResultSetMetaData implements ResultSetMetaData {
   }
 
   public int getColumnType(int column) throws SQLException {
-    return getColumnMetaData(column).type.type;
+    return getColumnMetaData(column).type.id;
   }
 
   public String getColumnTypeName(int column) throws SQLException {
-    return getColumnMetaData(column).type.typeName;
+    return getColumnMetaData(column).type.name;
   }
 
   public boolean isReadOnly(int column) throws SQLException {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java
index fb0a6f5..6df3528 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java
@@ -20,6 +20,7 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.SQLWarning;
 import java.sql.Statement;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -53,7 +54,7 @@ public abstract class AvaticaStatement
   final int resultSetHoldability;
   private int fetchSize;
   private int fetchDirection;
-  protected int maxRowCount;
+  protected int maxRowCount = 0;
 
   /**
    * Creates an AvaticaStatement.
@@ -98,17 +99,6 @@ public abstract class AvaticaStatement
     }
   }
 
-  public ResultSet executeQueryOld(String sql) throws SQLException {
-    try {
-      final int maxRowCount1 = maxRowCount <= 0 ? -1 : maxRowCount;
-      Meta.Signature x = connection.meta.prepare(handle, sql, maxRowCount1);
-      return executeQueryInternal(x);
-    } catch (RuntimeException e) {
-      throw connection.helper.createException(
-        "error while executing SQL \"" + sql + "\": " + e.getMessage(), e);
-    }
-  }
-
   public ResultSet executeQuery(String sql) throws SQLException {
     try {
       // In JDBC, maxRowCount = 0 means no limit; in prepare it means LIMIT 0
@@ -417,6 +407,25 @@ public abstract class AvaticaStatement
   protected List<Object> getParameterValues() {
     return Collections.emptyList();
   }
+
+  /** Returns a list of bound parameter values.
+   *
+   * <p>If any of the parameters have not been bound, throws.
+   * If parameters have been bound to null, the value in the list is null.
+   */
+  protected List<Object> getBoundParameterValues() throws SQLException {
+    final List<Object> list = new ArrayList<>();
+    for (Object parameterValue : getParameterValues()) {
+      if (parameterValue == null) {
+        throw new SQLException("unbound parameter");
+      }
+      if (parameterValue == AvaticaParameter.DUMMY_VALUE) {
+        parameterValue = null;
+      }
+      list.add(parameterValue);
+    }
+    return list;
+  }
 }
 
 // End AvaticaStatement.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java b/avatica/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java
index 0b6856d..8e9930f 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/ColumnMetaData.java
@@ -160,6 +160,13 @@ public class ColumnMetaData {
         type.columnClassName());
   }
 
+  public ColumnMetaData setTypeId(int typeId) {
+    return new ColumnMetaData(ordinal, autoIncrement, caseSensitive, searchable,
+        currency, nullable, signed, displaySize, label, columnName, schemaName,
+        precision, scale, tableName, catalogName, type.setId(typeId), readOnly,
+        writable, definitelyWritable, columnClassName);
+  }
+
   /** Description of the type used to internally represent a value. For example,
    * a {@link java.sql.Date} might be represented as a {@link #PRIMITIVE_INT}
    * if not nullable, or a {@link #JAVA_SQL_DATE}. */
@@ -192,7 +199,7 @@ public class ColumnMetaData {
     public static final Map<Class, Rep> VALUE_MAP;
 
     static {
-      Map<Class, Rep> builder = new HashMap<Class, Rep>();
+      Map<Class, Rep> builder = new HashMap<>();
       for (Rep rep : values()) {
         builder.put(rep.clazz, rep);
       }
@@ -220,31 +227,39 @@ public class ColumnMetaData {
       @JsonSubTypes.Type(value = StructType.class, name = "struct"),
       @JsonSubTypes.Type(value = ArrayType.class, name = "array") })
   public static class AvaticaType {
-    public final int type;
-    public final String typeName;
+    public final int id;
+    public final String name;
 
     /** The type of the field that holds the value. Not a JDBC property. */
-    public final Rep representation;
+    public final Rep rep;
 
-    protected AvaticaType(int type, String typeName, Rep representation) {
-      this.type = type;
-      this.typeName = typeName;
-      this.representation = representation;
-      assert representation != null;
+    protected AvaticaType(int id, String name, Rep rep) {
+      this.id = id;
+      this.name = name;
+      this.rep = rep;
+      assert rep != null;
     }
 
     public String columnClassName() {
-      return SqlType.valueOf(type).clazz.getName();
+      return SqlType.valueOf(id).clazz.getName();
+    }
+
+    public AvaticaType setId(int rep) {
+      throw new UnsupportedOperationException();
     }
   }
 
   /** Scalar type. */
   public static class ScalarType extends AvaticaType {
     @JsonCreator
-    public ScalarType(@JsonProperty("type") int type,
-        @JsonProperty("typeName") String typeName,
-        @JsonProperty("representation") Rep representation) {
-      super(type, typeName, representation);
+    public ScalarType(@JsonProperty("id") int id,
+        @JsonProperty("name") String name,
+        @JsonProperty("rep") Rep rep) {
+      super(id, name, rep);
+    }
+
+    @Override public AvaticaType setId(int id) {
+      return new ScalarType(id, name, rep);
     }
   }
 
@@ -306,8 +321,7 @@ public class ColumnMetaData {
     private final int type;
     private final Class clazz;
 
-    private static final Map<Integer, SqlType> BY_ID =
-        new HashMap<Integer, SqlType>();
+    private static final Map<Integer, SqlType> BY_ID = new HashMap<>();
     static {
       for (SqlType sqlType : values()) {
         BY_ID.put(sqlType.type, sqlType);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/Meta.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/Meta.java b/avatica/src/main/java/org/apache/calcite/avatica/Meta.java
index 107c4c9..f7f3b80 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/Meta.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/Meta.java
@@ -26,6 +26,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Command handler for getting various metadata. Should be implemented by each
@@ -148,7 +149,7 @@ public interface Meta {
    * requires to be not null; derived classes may instead choose to execute the
    * relational expression in {@code signature}. */
   Iterable<Object> createIterable(StatementHandle handle, Signature signature,
-      Iterable<Object> iterable);
+      List<Object> parameterValues, Frame firstFrame);
 
   /** Prepares a statement.
    *
@@ -170,6 +171,25 @@ public interface Meta {
   MetaResultSet prepareAndExecute(StatementHandle h, String sql,
       int maxRowCount, PrepareCallback callback);
 
+  /** 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 parameterValues A list of parameter values, if statement is to be
+   *                        executed; otherwise null
+   * @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, List<Object> parameterValues, int offset,
+      int fetchMaxRowCount);
+
   /** Called during the creation of a statement to allocate a new handle.
    *
    * @param ch Connection handle
@@ -201,15 +221,15 @@ public interface Meta {
   class MetaResultSet {
     public final int statementId;
     public final boolean ownStatement;
-    public final Iterable<Object> iterable;
+    public final Frame firstFrame;
     public final Signature signature;
 
     public MetaResultSet(int statementId, boolean ownStatement,
-        Signature signature, Iterable<Object> iterable) {
-      this.signature = signature;
+        Signature signature, Frame firstFrame) {
+      this.signature = Objects.requireNonNull(signature);
       this.statementId = statementId;
       this.ownStatement = ownStatement;
-      this.iterable = iterable;
+      this.firstFrame = firstFrame; // may be null
     }
   }
 
@@ -227,7 +247,7 @@ public interface Meta {
       assert (fieldNames != null)
           == (style == Style.RECORD_PROJECTION || style == Style.MAP);
       assert (fields != null) == (style == Style.RECORD_PROJECTION);
-      this.style = style;
+      this.style = Objects.requireNonNull(style);
       this.clazz = clazz;
       this.fields = fields;
       this.fieldNames = fieldNames;
@@ -271,7 +291,7 @@ public interface Meta {
     public static CursorFactory record(Class resultClass, List<Field> fields,
         List<String> fieldNames) {
       if (fields == null) {
-        fields = new ArrayList<Field>();
+        fields = new ArrayList<>();
         for (String fieldName : fieldNames) {
           try {
             fields.add(resultClass.getField(fieldName));
@@ -348,6 +368,60 @@ public interface Meta {
       return new Signature(columns, sql, parameters, internalParameters,
           cursorFactory);
     }
+
+    /** Creates a copy of this Signature with null lists and maps converted to
+     * empty. */
+    public Signature sanitize() {
+      if (columns == null || parameters == null || internalParameters == null) {
+        return new Signature(sanitize(columns), sql, sanitize(parameters),
+            sanitize(internalParameters), cursorFactory);
+      }
+      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;
+    }
+  }
+
+  /** A collection of rows. */
+  class Frame {
+    /** 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 int 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(int 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);
+    }
   }
 
   /** Connection handle. */
@@ -383,7 +457,7 @@ public interface Meta {
   interface PrepareCallback {
     Object getMonitor();
     void clear() throws SQLException;
-    void assign(Signature signature, Iterable<Object> iterable)
+    void assign(Signature signature, Frame firstFrame)
         throws SQLException;
     void execute() throws SQLException;
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/MetaImpl.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/MetaImpl.java b/avatica/src/main/java/org/apache/calcite/avatica/MetaImpl.java
index 9b2be07..a6c5f3c 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/MetaImpl.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/MetaImpl.java
@@ -36,8 +36,10 @@ 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}.
@@ -171,7 +173,7 @@ public abstract class MetaImpl implements Meta {
     return createResultSet(Collections.<String, Object>emptyMap(),
         fieldMetaData(clazz).columns,
         CursorFactory.deduce(fieldMetaData(clazz).columns, null),
-        Collections.emptyList());
+        Frame.EMPTY);
   }
 
   protected static ColumnMetaData columnMetaData(String name, int index,
@@ -207,14 +209,13 @@ public abstract class MetaImpl implements Meta {
 
   protected MetaResultSet createResultSet(
       Map<String, Object> internalParameters, List<ColumnMetaData> columns,
-      CursorFactory cursorFactory, Iterable<Object> iterable) {
+      CursorFactory cursorFactory, Frame firstFrame) {
     try {
       final AvaticaStatement statement = connection.createStatement();
-      final SignatureWithIterable signature =
-          new SignatureWithIterable(internalParameters, columns, "",
-              Collections.<AvaticaParameter>emptyList(), cursorFactory,
-              iterable);
-      return new MetaResultSet(statement.getId(), true, signature, iterable);
+      final Signature signature =
+          new Signature(columns, "", Collections.<AvaticaParameter>emptyList(),
+              internalParameters, cursorFactory);
+      return new MetaResultSet(statement.getId(), true, signature, firstFrame);
     } catch (SQLException e) {
       throw new RuntimeException(e);
     }
@@ -646,8 +647,16 @@ public abstract class MetaImpl implements Meta {
   }
 
   public Iterable<Object> createIterable(StatementHandle handle,
-      Signature signature, Iterable<Object> iterable) {
-    return iterable;
+      Signature signature, List<Object> parameterValues, Frame firstFrame) {
+    if (firstFrame != null && firstFrame.done) {
+      return firstFrame.rows;
+    }
+    return new FetchIterable(handle, firstFrame, parameterValues);
+  }
+
+  public Frame fetch(StatementHandle h, List<Object> parameterValues,
+      int offset, int fetchMaxRowCount) {
+    return null;
   }
 
   /** Information about a type. */
@@ -691,27 +700,99 @@ public abstract class MetaImpl implements Meta {
     }
   }
 
-  /** Prepare result with a iterable. Use this for simple statements
-   * that have a canned response and don't need to be executed. */
-  protected interface WithIterable {
-    Iterable getIterable();
+  /** 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 Frame}s. */
+  private class FetchIterable implements Iterable<Object> {
+    private final StatementHandle handle;
+    private final Frame firstFrame;
+    private final List<Object> parameterValues;
+
+    public FetchIterable(StatementHandle handle, Frame firstFrame,
+        List<Object> parameterValues) {
+      this.handle = handle;
+      this.firstFrame = firstFrame;
+      this.parameterValues = parameterValues;
+    }
+
+    public Iterator<Object> iterator() {
+      return new FetchIterator(handle, firstFrame, parameterValues);
+    }
   }
 
-  /** Prepare result that contains an iterable. */
-  protected static class SignatureWithIterable extends Signature
-      implements WithIterable {
-    private final Iterable iterable;
+  /** Iterator over rows coming from a sequence of {@link Frame}s. */
+  private class FetchIterator implements Iterator<Object> {
+    private final StatementHandle handle;
+    private Frame frame;
+    private Iterator<Object> rows;
+    private List<Object> parameterValues;
+
+    public FetchIterator(StatementHandle handle, Frame firstFrame,
+        List<Object> parameterValues) {
+      this.handle = handle;
+      this.parameterValues = parameterValues;
+      if (firstFrame == null) {
+        frame = Frame.MORE;
+        rows = EmptyIterator.INSTANCE;
+      } else {
+        frame = firstFrame;
+        rows = firstFrame.rows.iterator();
+      }
+      moveNext();
+    }
 
-    public SignatureWithIterable(Map<String, Object> internalParameters,
-        List<ColumnMetaData> columns, String sql,
-        List<AvaticaParameter> parameters, CursorFactory cursorFactory,
-        Iterable<Object> iterable) {
-      super(columns, sql, parameters, internalParameters, cursorFactory);
-      this.iterable = iterable;
+    public void remove() {
+      throw new UnsupportedOperationException("remove");
     }
 
-    public Iterable getIterable() {
-      return iterable;
+    public boolean hasNext() {
+      return rows != null;
+    }
+
+    public Object next() {
+      if (rows == null) {
+        throw new NoSuchElementException();
+      }
+      final Object o = rows.next();
+      moveNext();
+      return o;
+    }
+
+    private void moveNext() {
+      for (;;) {
+        if (rows.hasNext()) {
+          break;
+        }
+        if (frame.done) {
+          rows = null;
+          break;
+        }
+        frame = fetch(handle, parameterValues, frame.offset, 100);
+        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();
+      }
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/UnregisteredDriver.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/UnregisteredDriver.java b/avatica/src/main/java/org/apache/calcite/avatica/UnregisteredDriver.java
index 07883f6..fefd5f4 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/UnregisteredDriver.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/UnregisteredDriver.java
@@ -43,7 +43,7 @@ import java.util.logging.Logger;
  * <p>The provider must implement:</p>
  * <ul>
  *   <li>{@link Meta#prepare(org.apache.calcite.avatica.Meta.StatementHandle, String, int)}
- *   <li>{@link Meta#createIterable(org.apache.calcite.avatica.Meta.StatementHandle, org.apache.calcite.avatica.Meta.Signature, Iterable)}
+ *   <li>{@link Meta#createIterable(org.apache.calcite.avatica.Meta.StatementHandle, org.apache.calcite.avatica.Meta.Signature, java.util.List, Meta.Frame)}
  * </ul>
  */
 public abstract class UnregisteredDriver implements java.sql.Driver {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java b/avatica/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
new file mode 100644
index 0000000..56c20c4
--- /dev/null
+++ b/avatica/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
@@ -0,0 +1,442 @@
+/*
+ * 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.jdbc;
+
+import org.apache.calcite.avatica.AvaticaParameter;
+import org.apache.calcite.avatica.ColumnMetaData;
+import org.apache.calcite.avatica.Meta;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/** Implementation of {@link Meta} upon an existing JDBC data source. */
+public class JdbcMeta implements Meta {
+  /**
+   * JDBC Types Mapped to Java Types
+   *
+   * @see <a href="https://docs.oracle.com/javase/1.5.0/docs/guide/jdbc/getstart/mapping.html#1051555">JDBC Types Mapped to Java Types</a>
+   */
+  protected static final Map<Integer, Type> SQL_TYPE_TO_JAVA_TYPE =
+      new HashMap<>();
+  static {
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.CHAR, String.class);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.VARCHAR, String.class);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.LONGNVARCHAR, String.class);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.NUMERIC, BigDecimal.class);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.DECIMAL, BigDecimal.class);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.BIT, Boolean.TYPE);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.TINYINT, Byte.TYPE);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.SMALLINT, Short.TYPE);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.INTEGER, Integer.TYPE);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.BIGINT, Long.TYPE);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.REAL, Float.TYPE);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.FLOAT, Double.TYPE);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.DOUBLE, Double.TYPE);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.BINARY, byte[].class);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.VARBINARY, byte[].class);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.LONGVARBINARY, byte[].class);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.DATE, java.sql.Date.class);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.TIME, java.sql.Time.class);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.TIMESTAMP, java.sql.Timestamp.class);
+    //put(Types.CLOB, Clob);
+    //put(Types.BLOB, Blob);
+    SQL_TYPE_TO_JAVA_TYPE.put(Types.ARRAY, Array.class);
+  }
+
+  private final Map<Integer, StatementInfo> statementMap = new HashMap<>();
+
+  /**
+   * Convert from JDBC metadata to Avatica columns.
+   */
+  protected static List<ColumnMetaData>
+  columns(ResultSetMetaData metaData) throws SQLException {
+    final List<ColumnMetaData> columns = new ArrayList<>();
+    for (int i = 1; i <= metaData.getColumnCount(); i++) {
+      final Type javaType =
+          SQL_TYPE_TO_JAVA_TYPE.get(metaData.getColumnType(i));
+      ColumnMetaData.AvaticaType t =
+          ColumnMetaData.scalar(metaData.getColumnType(i),
+              metaData.getColumnTypeName(i), ColumnMetaData.Rep.of(javaType));
+      ColumnMetaData md =
+          new ColumnMetaData(i - 1, metaData.isAutoIncrement(i),
+              metaData.isCaseSensitive(i), metaData.isSearchable(i),
+              metaData.isCurrency(i), metaData.isNullable(i),
+              metaData.isSigned(i), metaData.getColumnDisplaySize(i),
+              metaData.getColumnLabel(i), metaData.getColumnName(i),
+              metaData.getSchemaName(i), metaData.getPrecision(i),
+              metaData.getScale(i), metaData.getTableName(i),
+              metaData.getCatalogName(i), t, metaData.isReadOnly(i),
+              metaData.isWritable(i), metaData.isDefinitelyWritable(i),
+              metaData.getColumnClassName(i));
+      columns.add(md);
+    }
+    return columns;
+  }
+
+  /**
+   * Converts from JDBC metadata to AvaticaParameters
+   */
+  protected static List<AvaticaParameter> parameters(ParameterMetaData metaData)
+      throws SQLException {
+    if (metaData == null) {
+      return Collections.emptyList();
+    }
+    final List<AvaticaParameter> params = new ArrayList<>();
+    for (int i = 1; i <= metaData.getParameterCount(); i++) {
+      params.add(
+          new AvaticaParameter(metaData.isSigned(i), metaData.getPrecision(i),
+              metaData.getScale(i), metaData.getParameterType(i),
+              metaData.getParameterTypeName(i),
+              metaData.getParameterClassName(i), "?" + i));
+    }
+    return params;
+  }
+
+  protected static Signature signature(ResultSetMetaData metaData,
+      ParameterMetaData parameterMetaData, String sql) throws  SQLException {
+    return new Signature(columns(metaData), sql, parameters(parameterMetaData),
+        null, CursorFactory.ARRAY);
+  }
+
+  protected static Signature signature(ResultSetMetaData metaData)
+      throws SQLException {
+    return signature(metaData, null, null);
+  }
+
+  protected final Connection connection;
+
+  public JdbcMeta(Connection connection) {
+    this.connection = connection;
+  }
+
+  public String getSqlKeywords() {
+    try {
+      return connection.getMetaData().getSQLKeywords();
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public String getNumericFunctions() {
+    try {
+      return connection.getMetaData().getNumericFunctions();
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public String getStringFunctions() {
+    return null;
+  }
+
+  public String getSystemFunctions() {
+    return null;
+  }
+
+  public String getTimeDateFunctions() {
+    return null;
+  }
+
+  public MetaResultSet getTables(String catalog, Pat schemaPattern,
+      Pat tableNamePattern, List<String> typeList) {
+    try {
+      String[] types = new String[typeList == null ? 0 : typeList.size()];
+      return JdbcResultSet.create(
+          connection.getMetaData().getTables(catalog, schemaPattern.s,
+              tableNamePattern.s,
+              typeList == null ? types : typeList.toArray(types)));
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public MetaResultSet getColumns(String catalog, Pat schemaPattern,
+      Pat tableNamePattern, Pat columnNamePattern) {
+    try {
+      return JdbcResultSet.create(
+          connection.getMetaData().getColumns(catalog, schemaPattern.s,
+              tableNamePattern.s, columnNamePattern.s));
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public MetaResultSet getSchemas(String catalog, Pat schemaPattern) {
+    try {
+      return JdbcResultSet.create(
+          connection.getMetaData().getSchemas(catalog, schemaPattern.s));
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public MetaResultSet getCatalogs() {
+    try {
+      return JdbcResultSet.create(connection.getMetaData().getCatalogs());
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public MetaResultSet getTableTypes() {
+    try {
+      return JdbcResultSet.create(connection.getMetaData().getTableTypes());
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public MetaResultSet getProcedures(String catalog, Pat schemaPattern,
+      Pat procedureNamePattern) {
+    try {
+      return JdbcResultSet.create(
+          connection.getMetaData().getProcedures(catalog, schemaPattern.s,
+              procedureNamePattern.s));
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public MetaResultSet getProcedureColumns(String catalog, Pat schemaPattern,
+      Pat procedureNamePattern, Pat columnNamePattern) {
+    try {
+      return JdbcResultSet.create(
+          connection.getMetaData().getProcedureColumns(catalog,
+              schemaPattern.s, procedureNamePattern.s, columnNamePattern.s));
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public MetaResultSet getColumnPrivileges(String catalog, String schema,
+      String table, Pat columnNamePattern) {
+    try {
+      return JdbcResultSet.create(
+          connection.getMetaData().getColumnPrivileges(catalog, schema,
+              table, columnNamePattern.s));
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public MetaResultSet getTablePrivileges(String catalog, Pat schemaPattern,
+      Pat tableNamePattern) {
+    try {
+      return JdbcResultSet.create(
+          connection.getMetaData().getTablePrivileges(catalog,
+              schemaPattern.s, tableNamePattern.s));
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public MetaResultSet getBestRowIdentifier(String catalog, String schema,
+      String table, int scope, boolean nullable) {
+    try {
+      return JdbcResultSet.create(
+          connection.getMetaData().getBestRowIdentifier(catalog, schema,
+              table, scope, nullable));
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public MetaResultSet getVersionColumns(String catalog, String schema,
+      String table) {
+    try {
+      return JdbcResultSet.create(
+          connection.getMetaData().getVersionColumns(catalog, schema, table));
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public MetaResultSet getPrimaryKeys(String catalog, String schema,
+      String table) {
+    try {
+      return JdbcResultSet.create(
+          connection.getMetaData().getPrimaryKeys(catalog, schema, table));
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public MetaResultSet getImportedKeys(String catalog, String schema,
+      String table) {
+    return null;
+  }
+
+  public MetaResultSet getExportedKeys(String catalog, String schema,
+      String table) {
+    return null;
+  }
+
+  public MetaResultSet getCrossReference(String parentCatalog,
+      String parentSchema, String parentTable, String foreignCatalog,
+      String foreignSchema, String foreignTable) {
+    return null;
+  }
+
+  public MetaResultSet getTypeInfo() {
+    return null;
+  }
+
+  public MetaResultSet getIndexInfo(String catalog, String schema, String table,
+      boolean unique, boolean approximate) {
+    return null;
+  }
+
+  public MetaResultSet getUDTs(String catalog, Pat schemaPattern,
+      Pat typeNamePattern, int[] types) {
+    return null;
+  }
+
+  public MetaResultSet getSuperTypes(String catalog, Pat schemaPattern,
+      Pat typeNamePattern) {
+    return null;
+  }
+
+  public MetaResultSet getSuperTables(String catalog, Pat schemaPattern,
+      Pat tableNamePattern) {
+    return null;
+  }
+
+  public MetaResultSet getAttributes(String catalog, Pat schemaPattern,
+      Pat typeNamePattern, Pat attributeNamePattern) {
+    return null;
+  }
+
+  public MetaResultSet getClientInfoProperties() {
+    return null;
+  }
+
+  public MetaResultSet getFunctions(String catalog, Pat schemaPattern,
+      Pat functionNamePattern) {
+    return null;
+  }
+
+  public MetaResultSet getFunctionColumns(String catalog, Pat schemaPattern,
+      Pat functionNamePattern, Pat columnNamePattern) {
+    return null;
+  }
+
+  public MetaResultSet getPseudoColumns(String catalog, Pat schemaPattern,
+      Pat tableNamePattern, Pat columnNamePattern) {
+    return null;
+  }
+
+  public Iterable<Object> createIterable(StatementHandle handle,
+      Signature signature, List<Object> parameterValues, Frame firstFrame) {
+    return null;
+  }
+
+  public StatementHandle createStatement(ConnectionHandle ch) {
+    try {
+      final Statement statement = connection.createStatement();
+      final int id = statementMap.size();
+      statementMap.put(id, new StatementInfo(statement));
+      return new StatementHandle(id);
+    } catch (SQLException e) {
+      throw propagate(e);
+    }
+  }
+
+  private RuntimeException propagate(Throwable e) {
+    if (e instanceof RuntimeException) {
+      throw (RuntimeException) e;
+    } else if (e instanceof Error) {
+      throw (Error) e;
+    } else {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public Signature prepare(StatementHandle h, String sql, int maxRowCount) {
+    // TODO: can't actually prepare an existing statement...
+    try {
+      PreparedStatement statement = connection.prepareStatement(sql);
+      statementMap.put(h.id, new StatementInfo(statement));
+      return signature(statement.getMetaData(),
+          statement.getParameterMetaData(), sql);
+    } catch (SQLException e) {
+      throw propagate(e);
+    }
+  }
+
+  public MetaResultSet prepareAndExecute(StatementHandle h, String sql,
+      int maxRowCount, PrepareCallback callback) {
+    final StatementInfo statementInfo = statementMap.get(h.id);
+    try {
+      statementInfo.resultSet = statementInfo.statement.executeQuery(sql);
+      return JdbcResultSet.create(statementInfo.resultSet);
+    } catch (SQLException e) {
+      throw propagate(e);
+    }
+  }
+
+  public Frame fetch(StatementHandle h, List<Object> parameterValues,
+      int offset, int fetchMaxRowCount) {
+    final StatementInfo statementInfo = statementMap.get(h.id);
+    try {
+      if (statementInfo.resultSet == null || parameterValues != null) {
+        if (statementInfo.resultSet != null) {
+          statementInfo.resultSet.close();
+        }
+        final PreparedStatement preparedStatement =
+            (PreparedStatement) statementInfo.statement;
+        if (parameterValues != null) {
+          for (int i = 0; i < parameterValues.size(); i++) {
+            Object o = parameterValues.get(i);
+            preparedStatement.setObject(i + 1, o);
+          }
+        }
+        statementInfo.resultSet = preparedStatement.executeQuery();
+      }
+      return JdbcResultSet.frame(statementInfo.resultSet, offset,
+          fetchMaxRowCount);
+    } catch (SQLException e) {
+      throw propagate(e);
+    }
+  }
+
+  /** All we know about a statement. */
+  private static class StatementInfo {
+    final Statement statement; // sometimes a PreparedStatement
+    ResultSet resultSet;
+
+    private StatementInfo(Statement statement) {
+      this.statement = Objects.requireNonNull(statement);
+    }
+  }
+}
+
+// End JdbcMeta.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/jdbc/JdbcResultSet.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/jdbc/JdbcResultSet.java b/avatica/src/main/java/org/apache/calcite/avatica/jdbc/JdbcResultSet.java
new file mode 100644
index 0000000..ac4aeb5
--- /dev/null
+++ b/avatica/src/main/java/org/apache/calcite/avatica/jdbc/JdbcResultSet.java
@@ -0,0 +1,105 @@
+/*
+ * 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.jdbc;
+
+import org.apache.calcite.avatica.Meta;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Implementation of {@link org.apache.calcite.avatica.Meta.MetaResultSet}
+ *  upon a JDBC {@link java.sql.ResultSet}.
+ *
+ *  @see org.apache.calcite.avatica.jdbc.JdbcMeta */
+class JdbcResultSet extends Meta.MetaResultSet {
+  protected JdbcResultSet(int statementId, boolean ownStatement,
+      Meta.Signature signature, Meta.Frame firstFrame) {
+    super(statementId, ownStatement, signature, firstFrame);
+  }
+
+  /** Creates a result set. */
+  public static JdbcResultSet create(ResultSet resultSet) {
+    try {
+      int id = resultSet.getStatement().hashCode();
+      Meta.Signature sig = JdbcMeta.signature(resultSet.getMetaData());
+      final Meta.Frame firstFrame = frame(resultSet, 0, -1);
+      resultSet.close();
+      return new JdbcResultSet(id, true, sig, firstFrame);
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /** Creates a frame containing a given number or unlimited number of rows
+   * from a result set. */
+  static Meta.Frame frame(ResultSet resultSet, int offset,
+      int fetchMaxRowCount) throws SQLException {
+    final ResultSetMetaData metaData = resultSet.getMetaData();
+    final int columnCount = metaData.getColumnCount();
+    final int[] types = new int[columnCount];
+    for (int i = 0; i < types.length; i++) {
+      types[i] = metaData.getColumnType(i + 1);
+    }
+    final List<Object> rows = new ArrayList<>();
+    boolean done = false;
+    for (int i = 0; fetchMaxRowCount < 0 || i < fetchMaxRowCount; i++) {
+      if (!resultSet.next()) {
+        done = true;
+        break;
+      }
+      Object[] columns = new Object[columnCount];
+      for (int j = 0; j < columnCount; j++) {
+        columns[j] = getValue(resultSet, types[j], j);
+      }
+      rows.add(columns);
+    }
+    return new Meta.Frame(offset, done, rows);
+  }
+
+  private static Object getValue(ResultSet resultSet, int type, int j)
+      throws SQLException {
+    switch (type) {
+    case Types.BIGINT:
+      final long aLong = resultSet.getLong(j + 1);
+      return aLong == 0 && resultSet.wasNull() ? null : aLong;
+    case Types.INTEGER:
+      final int anInt = resultSet.getInt(j + 1);
+      return anInt == 0 && resultSet.wasNull() ? null : anInt;
+    case Types.SMALLINT:
+      final short aShort = resultSet.getShort(j + 1);
+      return aShort == 0 && resultSet.wasNull() ? null : aShort;
+    case Types.TINYINT:
+      final byte aByte = resultSet.getByte(j + 1);
+      return aByte == 0 && resultSet.wasNull() ? null : aByte;
+    case Types.DOUBLE:
+    case Types.FLOAT:
+      final double aDouble = resultSet.getDouble(j + 1);
+      return aDouble == 0D && resultSet.wasNull() ? null : aDouble;
+    case Types.REAL:
+      final float aFloat = resultSet.getFloat(j + 1);
+      return aFloat == 0D && resultSet.wasNull() ? null : aFloat;
+    default:
+      return resultSet.getObject(j + 1);
+    }
+  }
+}
+
+// End JdbcResultSet.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/jdbc/package-info.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/jdbc/package-info.java b/avatica/src/main/java/org/apache/calcite/avatica/jdbc/package-info.java
new file mode 100644
index 0000000..8b8fb76
--- /dev/null
+++ b/avatica/src/main/java/org/apache/calcite/avatica/jdbc/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/** Implements an Avatica provider on top of an existing JDBC data source. */
+package org.apache.calcite.avatica.jdbc;
+
+
+// End package-info.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonService.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonService.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonService.java
index 3de4ef0..7e110ff 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonService.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonService.java
@@ -41,11 +41,14 @@ public abstract class JsonService implements Service {
    * responses to and from the peer service. */
   public abstract String apply(String request);
 
-  private <T> T decode(String response, Class<T> valueType) throws IOException {
+  //@VisibleForTesting
+  protected static <T> T decode(String response, Class<T> valueType)
+      throws IOException {
     return MAPPER.readValue(response, valueType);
   }
 
-  private <T> String encode(T request) throws IOException {
+  //@VisibleForTesting
+  protected static <T> String encode(T request) throws IOException {
     final StringWriter w = new StringWriter();
     MAPPER.writeValue(w, request);
     return w.toString();
@@ -71,6 +74,14 @@ public abstract class JsonService implements Service {
     }
   }
 
+  public ResultSetResponse apply(TablesRequest request) {
+    try {
+      return decode(apply(encode(request)), ResultSetResponse.class);
+    } catch (IOException e) {
+      throw handle(e);
+    }
+  }
+
   public PrepareResponse apply(PrepareRequest request) {
     try {
       return decode(apply(encode(request)), PrepareResponse.class);
@@ -87,6 +98,14 @@ public abstract class JsonService implements Service {
     }
   }
 
+  public FetchResponse apply(FetchRequest request) {
+    try {
+      return decode(apply(encode(request)), FetchResponse.class);
+    } catch (IOException e) {
+      throw handle(e);
+    }
+  }
+
   public CreateStatementResponse apply(CreateStatementRequest request) {
     try {
       return decode(apply(encode(request)), CreateStatementResponse.class);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/remote/LocalService.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/LocalService.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/LocalService.java
index 5ee3d16..faf60b8 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/remote/LocalService.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/LocalService.java
@@ -16,10 +16,13 @@
  */
 package org.apache.calcite.avatica.remote;
 
+import org.apache.calcite.avatica.ColumnMetaData;
 import org.apache.calcite.avatica.Meta;
 import org.apache.calcite.avatica.MetaImpl;
 
+import java.sql.Types;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -27,6 +30,8 @@ import java.util.List;
  */
 public class LocalService implements Service {
   final Meta meta;
+  /** Whether output is going to JSON. */
+  private final boolean json = true;
 
   public LocalService(Meta meta) {
     this.meta = meta;
@@ -47,8 +52,8 @@ public class LocalService implements Service {
   public ResultSetResponse toResponse(Meta.MetaResultSet resultSet) {
     Meta.CursorFactory cursorFactory = resultSet.signature.cursorFactory;
     final List<Object> list;
-    if (resultSet.iterable != null) {
-      list = list(resultSet.iterable);
+    if (resultSet.firstFrame != null) {
+      list = list(resultSet.firstFrame.rows);
       switch (cursorFactory.style) {
       case ARRAY:
         cursorFactory = Meta.CursorFactory.LIST;
@@ -68,16 +73,16 @@ public class LocalService implements Service {
       signature = signature.setCursorFactory(cursorFactory);
     }
     return new ResultSetResponse(resultSet.statementId, resultSet.ownStatement,
-        signature, list);
+        signature, new Meta.Frame(0, true, list));
   }
 
   private List<List<Object>> list2(Meta.MetaResultSet resultSet) {
-    List<List<Object>> list = new ArrayList<List<Object>>();
-    return MetaImpl.collect(resultSet.signature.cursorFactory,
-        meta.createIterable(new Meta.StatementHandle(resultSet.statementId),
-            resultSet.signature,
-            resultSet.iterable),
-        list);
+    final Meta.StatementHandle h =
+        new Meta.StatementHandle(resultSet.statementId);
+    final Iterable<Object> iterable = meta.createIterable(h,
+        resultSet.signature, Collections.emptyList(), resultSet.firstFrame);
+    final List<List<Object>> list = new ArrayList<>();
+    return MetaImpl.collect(resultSet.signature.cursorFactory, iterable, list);
   }
 
   public ResultSetResponse apply(CatalogsRequest request) {
@@ -91,11 +96,43 @@ public class LocalService implements Service {
     return toResponse(resultSet);
   }
 
+  public ResultSetResponse apply(TablesRequest request) {
+    final Meta.MetaResultSet resultSet =
+        meta.getTables(request.catalog, Meta.Pat.of(request.schemaPattern),
+            Meta.Pat.of(request.tableNamePattern), request.typeList);
+    return toResponse(resultSet);
+  }
+
   public PrepareResponse apply(PrepareRequest request) {
     final Meta.StatementHandle h =
         new Meta.StatementHandle(request.statementId);
-    final Meta.Signature signature =
-        meta.prepare(h, request.sql, request.maxRowCount);
+    Meta.Signature signature =
+        meta.prepare(h, request.sql, request.maxRowCount)
+            .setCursorFactory(Meta.CursorFactory.LIST);
+    if (json) {
+      final List<ColumnMetaData> columns = new ArrayList<>();
+      for (ColumnMetaData column : signature.columns) {
+        switch (column.type.rep) {
+        case BYTE:
+        case PRIMITIVE_BYTE:
+        case DOUBLE:
+        case PRIMITIVE_DOUBLE:
+        case FLOAT:
+        case PRIMITIVE_FLOAT:
+        case INTEGER:
+        case PRIMITIVE_INT:
+        case SHORT:
+        case PRIMITIVE_SHORT:
+        case LONG:
+        case PRIMITIVE_LONG:
+          column = column.setTypeId(Types.NUMERIC);
+        }
+        columns.add(column);
+      }
+      signature = new Meta.Signature(columns, signature.sql,
+          signature.parameters, signature.internalParameters,
+          signature.cursorFactory);
+    }
     return new PrepareResponse(signature);
   }
 
@@ -105,20 +142,29 @@ public class LocalService implements Service {
     final Meta.MetaResultSet resultSet =
         meta.prepareAndExecute(h, request.sql, request.maxRowCount,
             new Meta.PrepareCallback() {
-              public Object getMonitor() {
+              @Override public Object getMonitor() {
                 return LocalService.class;
               }
 
-              public void clear() {}
+              @Override public void clear() {}
 
-              public void assign(Meta.Signature signature,
-                  Iterable<Object> iterable) {}
+              @Override public void assign(Meta.Signature signature,
+                  Meta.Frame firstFrame) {}
 
-              public void execute() {}
+              @Override public void execute() {}
             });
     return toResponse(resultSet);
   }
 
+  public FetchResponse apply(FetchRequest request) {
+    final Meta.StatementHandle h =
+        new Meta.StatementHandle(request.statementId);
+    final Meta.Frame frame =
+        meta.fetch(h, request.parameterValues, request.offset,
+            request.fetchMaxRowCount);
+    return new FetchResponse(frame);
+  }
+
   public CreateStatementResponse apply(CreateStatementRequest request) {
     final Meta.StatementHandle h =
         meta.createStatement(new Meta.ConnectionHandle(request.connectionId));

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/remote/MockJsonService.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/MockJsonService.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/MockJsonService.java
index 7fe7ea8..19e59e3 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/remote/MockJsonService.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/MockJsonService.java
@@ -18,6 +18,8 @@ package org.apache.calcite.avatica.remote;
 
 import org.apache.calcite.avatica.AvaticaConnection;
 
+import java.io.IOException;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -44,10 +46,44 @@ public class MockJsonService extends JsonService {
   /** Factory that creates a {@code MockJsonService}. */
   public static class Factory implements Service.Factory {
     public Service create(AvaticaConnection connection) {
-      final Map<String, String> map1 = new HashMap<String, String>();
-      map1.put(
-          "{\"request\":\"getSchemas\",\"catalog\":null,\"schemaPattern\":{\"s\":null}}",
-          "{\"response\":\"resultSet\", rows: []}");
+      final Map<String, String> map1 = new HashMap<>();
+      try {
+        map1.put(
+            "{\"request\":\"getSchemas\",\"catalog\":null,\"schemaPattern\":{\"s\":null}}",
+            "{\"response\":\"resultSet\", firstFrame: {offset: 0, done: true, rows: []}}");
+        map1.put(
+            JsonService.encode(new SchemasRequest(null, null)),
+            "{\"response\":\"resultSet\", firstFrame: {offset: 0, done: true, rows: []}}");
+        map1.put(
+            JsonService.encode(
+                new TablesRequest(null, null, null, Arrays.<String>asList())),
+            "{\"response\":\"resultSet\", firstFrame: {offset: 0, done: true, rows: []}}");
+        map1.put(
+            "{\"request\":\"createStatement\",\"connectionId\":0}",
+            "{\"response\":\"createStatement\",\"id\":0}");
+        map1.put(
+            "{\"request\":\"prepareAndExecute\",\"statementId\":0,"
+                + "\"sql\":\"select * from (\\n  values (1, 'a'), (null, 'b'), (3, 'c')) as t (c1, c2)\",\"maxRowCount\":-1}",
+            "{\"response\":\"resultSet\",\"signature\": {\n"
+                + " \"columns\": [\n"
+                + "   {\"columnName\": \"C1\", \"type\": {type: \"scalar\", id: 4, rep: \"INTEGER\"}},\n"
+                + "   {\"columnName\": \"C2\", \"type\": {type: \"scalar\", id: 12, rep: \"STRING\"}}\n"
+                + " ], \"cursorFactory\": {\"style\": \"ARRAY\"}\n"
+                + "}, \"rows\": [[1, \"a\"], [null, \"b\"], [3, \"c\"]]}");
+        map1.put(
+            "{\"request\":\"prepare\",\"statementId\":0,"
+                + "\"sql\":\"select * from (\\n  values (1, 'a'), (null, 'b'), (3, 'c')) as t (c1, c2)\",\"maxRowCount\":-1}",
+            "{\"response\":\"prepare\",\"signature\": {\n"
+                + " \"columns\": [\n"
+                + "   {\"columnName\": \"C1\", \"type\": {type: \"scalar\", id: 4, rep: \"INTEGER\"}},\n"
+                + "   {\"columnName\": \"C2\", \"type\": {type: \"scalar\", id: 12, rep: \"STRING\"}}\n"
+                + " ],\n"
+                + " \"parameters\": [],\n"
+                + " \"cursorFactory\": {\"style\": \"ARRAY\"}\n"
+                + "}}");
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
       return new MockJsonService(map1);
     }
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteMeta.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteMeta.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteMeta.java
index 89ef490..83dd28c 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteMeta.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteMeta.java
@@ -17,10 +17,14 @@
 package org.apache.calcite.avatica.remote;
 
 import org.apache.calcite.avatica.AvaticaConnection;
+import org.apache.calcite.avatica.AvaticaParameter;
+import org.apache.calcite.avatica.ColumnMetaData;
 import org.apache.calcite.avatica.Meta;
 import org.apache.calcite.avatica.MetaImpl;
 
 import java.sql.SQLException;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Implementation of {@link Meta} for the remote driver.
@@ -33,10 +37,19 @@ class RemoteMeta extends MetaImpl {
     this.service = service;
   }
 
-  private MetaResultSet toResultSet(Service.ResultSetResponse response) {
-    final Signature signature0 = response.signature;
+  private MetaResultSet toResultSet(Class clazz,
+      Service.ResultSetResponse response) {
+    Signature signature0 = response.signature;
+    if (signature0 == null) {
+      final List<ColumnMetaData> columns =
+          clazz == null
+              ? Collections.<ColumnMetaData>emptyList()
+              : fieldMetaData(clazz).columns;
+      signature0 = Signature.create(columns,
+          "?", Collections.<AvaticaParameter>emptyList(), CursorFactory.ARRAY);
+    }
     return new MetaResultSet(response.statementId, response.ownStatement,
-        signature0, response.rows);
+        signature0, response.firstFrame);
   }
 
   @Override public StatementHandle createStatement(ConnectionHandle ch) {
@@ -48,13 +61,22 @@ class RemoteMeta extends MetaImpl {
   @Override public MetaResultSet getCatalogs() {
     final Service.ResultSetResponse response =
         service.apply(new Service.CatalogsRequest());
-    return toResultSet(response);
+    return toResultSet(MetaCatalog.class, response);
   }
 
   @Override public MetaResultSet getSchemas(String catalog, Pat schemaPattern) {
     final Service.ResultSetResponse response =
         service.apply(new Service.SchemasRequest(catalog, schemaPattern.s));
-    return toResultSet(response);
+    return toResultSet(MetaSchema.class, response);
+  }
+
+  @Override public MetaResultSet getTables(String catalog, Pat schemaPattern,
+      Pat tableNamePattern, List<String> typeList) {
+    final Service.ResultSetResponse response =
+        service.apply(
+            new Service.TablesRequest(catalog, schemaPattern.s,
+                tableNamePattern.s, typeList));
+    return toResultSet(MetaTable.class, response);
   }
 
   @Override public Signature prepare(StatementHandle h, String sql,
@@ -72,14 +94,23 @@ class RemoteMeta extends MetaImpl {
         callback.clear();
         response = service.apply(
             new Service.PrepareAndExecuteRequest(h.id, sql, maxRowCount));
-        callback.assign(response.signature, response.rows);
+        callback.assign(response.signature, response.firstFrame);
       }
       callback.execute();
-      return toResultSet(response);
+      return toResultSet(null, response);
     } catch (SQLException e) {
       throw new RuntimeException(e);
     }
   }
+
+  @Override public Frame fetch(StatementHandle h, List<Object> parameterValues,
+      int offset, int fetchMaxRowCount) {
+    final Service.FetchResponse response =
+        service.apply(
+            new Service.FetchRequest(h.id, parameterValues, offset,
+                fetchMaxRowCount));
+    return response.frame;
+  }
 }
 
 // End RemoteMeta.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/remote/Service.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/Service.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/Service.java
index cad38dd..cf73b49 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/remote/Service.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/Service.java
@@ -32,8 +32,10 @@ import java.util.List;
 public interface Service {
   ResultSetResponse apply(CatalogsRequest request);
   ResultSetResponse apply(SchemasRequest request);
+  ResultSetResponse apply(TablesRequest request);
   PrepareResponse apply(PrepareRequest request);
   ResultSetResponse apply(PrepareAndExecuteRequest request);
+  FetchResponse apply(FetchRequest request);
   CreateStatementResponse apply(CreateStatementRequest request);
 
   /** Factory that creates a {@code Service}. */
@@ -49,9 +51,11 @@ public interface Service {
   @JsonSubTypes({
       @JsonSubTypes.Type(value = CatalogsRequest.class, name = "getCatalogs"),
       @JsonSubTypes.Type(value = SchemasRequest.class, name = "getSchemas"),
+      @JsonSubTypes.Type(value = TablesRequest.class, name = "getTables"),
       @JsonSubTypes.Type(value = PrepareRequest.class, name = "prepare"),
       @JsonSubTypes.Type(value = PrepareAndExecuteRequest.class,
           name = "prepareAndExecute"),
+      @JsonSubTypes.Type(value = FetchRequest.class, name = "fetch"),
       @JsonSubTypes.Type(value = CreateStatementRequest.class,
           name = "createStatement") })
   abstract class Request {
@@ -66,6 +70,7 @@ public interface Service {
   @JsonSubTypes({
       @JsonSubTypes.Type(value = ResultSetResponse.class, name = "resultSet"),
       @JsonSubTypes.Type(value = PrepareResponse.class, name = "prepare"),
+      @JsonSubTypes.Type(value = FetchResponse.class, name = "fetch"),
       @JsonSubTypes.Type(value = CreateStatementResponse.class,
           name = "createStatement") })
   abstract class Response {
@@ -97,6 +102,31 @@ public interface Service {
     }
   }
 
+  /** Request for
+   * {@link Meta#getTables(String, org.apache.calcite.avatica.Meta.Pat, org.apache.calcite.avatica.Meta.Pat, java.util.List)}
+   */
+  class TablesRequest extends Request {
+    public final String catalog;
+    public final String schemaPattern;
+    public final String tableNamePattern;
+    public final List<String> typeList;
+
+    @JsonCreator
+    public TablesRequest(@JsonProperty("catalog") String catalog,
+        @JsonProperty("schemaPattern") String schemaPattern,
+        @JsonProperty("tableNamePattern") String tableNamePattern,
+        @JsonProperty("typeList") List<String> typeList) {
+      this.catalog = catalog;
+      this.schemaPattern = schemaPattern;
+      this.tableNamePattern = tableNamePattern;
+      this.typeList = typeList;
+    }
+
+    @Override Response accept(Service service) {
+      return service.apply(this);
+    }
+  }
+
   /** Response that contains a result set.
    *
    * <p>Several types of request, including
@@ -107,17 +137,17 @@ public interface Service {
     public final int statementId;
     public final boolean ownStatement;
     public final Meta.Signature signature;
-    public final List<Object> rows;
+    public final Meta.Frame firstFrame;
 
     @JsonCreator
     public ResultSetResponse(@JsonProperty("statementId") int statementId,
         @JsonProperty("ownStatement") boolean ownStatement,
         @JsonProperty("signature") Meta.Signature signature,
-        @JsonProperty("rows") List<Object> rows) {
+        @JsonProperty("firstFrame") Meta.Frame firstFrame) {
       this.statementId = statementId;
       this.ownStatement = ownStatement;
       this.signature = signature;
-      this.rows = rows;
+      this.firstFrame = firstFrame;
     }
   }
 
@@ -177,6 +207,45 @@ public interface Service {
   }
 
   /** Request for
+   * {@link org.apache.calcite.avatica.Meta#fetch(Meta.StatementHandle, List, int, int)}. */
+  class FetchRequest extends Request {
+    public final int statementId;
+    public final int offset;
+    /** Maximum number of rows to be returned in the frame. Negative means no
+     * limit. */
+    public final int fetchMaxRowCount;
+    /** A list of parameter values, if statement is to be executed; otherwise
+     * null. */
+    public final List<Object> parameterValues;
+
+    @JsonCreator
+    public FetchRequest(@JsonProperty("statementId") int statementId,
+        @JsonProperty("parameterValues") List<Object> parameterValues,
+        @JsonProperty("offset") int offset,
+        @JsonProperty("fetchMaxRowCount") int fetchMaxRowCount) {
+      this.statementId = statementId;
+      this.parameterValues = parameterValues;
+      this.offset = offset;
+      this.fetchMaxRowCount = fetchMaxRowCount;
+    }
+
+    @Override FetchResponse accept(Service service) {
+      return service.apply(this);
+    }
+  }
+
+  /** Response from
+   * {@link org.apache.calcite.avatica.remote.Service.FetchRequest}. */
+  class FetchResponse extends Response {
+    public final Meta.Frame frame;
+
+    @JsonCreator
+    public FetchResponse(@JsonProperty("frame") Meta.Frame frame) {
+      this.frame = frame;
+    }
+  }
+
+  /** Request for
    * {@link org.apache.calcite.avatica.Meta#createStatement(org.apache.calcite.avatica.Meta.ConnectionHandle)}. */
   class CreateStatementRequest extends Request {
     public final int connectionId;

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java b/avatica/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
index 301352f..42731ba 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java
@@ -22,6 +22,7 @@ import org.apache.calcite.avatica.ColumnMetaData;
 import java.io.InputStream;
 import java.io.Reader;
 import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.math.RoundingMode;
 import java.net.URL;
 import java.sql.Array;
@@ -71,12 +72,12 @@ public abstract class AbstractCursor implements Cursor {
     return accessors;
   }
 
-  protected Accessor createAccessor(ColumnMetaData type, int ordinal,
+  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);
-    switch (type.type.type) {
+    switch (columnMetaData.type.id) {
     case Types.TINYINT:
       return new ByteAccessor(getter);
     case Types.SMALLINT:
@@ -92,15 +93,17 @@ public abstract class AbstractCursor implements Cursor {
       return new FloatAccessor(getter);
     case Types.DOUBLE:
       return new DoubleAccessor(getter);
+    case Types.NUMERIC:
+      return new NumberAccessor(getter);
     case Types.DECIMAL:
       return new BigDecimalAccessor(getter);
     case Types.CHAR:
-      switch (type.type.representation) {
+      switch (columnMetaData.type.rep) {
       case PRIMITIVE_CHAR:
       case CHARACTER:
-        return new StringFromCharAccessor(getter, type.displaySize);
+        return new StringFromCharAccessor(getter, columnMetaData.displaySize);
       default:
-        return new FixedStringAccessor(getter, type.displaySize);
+        return new FixedStringAccessor(getter, columnMetaData.displaySize);
       }
     case Types.VARCHAR:
       return new StringAccessor(getter);
@@ -108,27 +111,27 @@ public abstract class AbstractCursor implements Cursor {
     case Types.VARBINARY:
       return new BinaryAccessor(getter);
     case Types.DATE:
-      switch (type.type.representation) {
+      switch (columnMetaData.type.rep) {
       case PRIMITIVE_INT:
       case INTEGER:
         return new DateFromIntAccessor(getter, localCalendar);
       case JAVA_SQL_DATE:
         return new DateAccessor(getter, localCalendar);
       default:
-        throw new AssertionError("bad " + type.type.representation);
+        throw new AssertionError("bad " + columnMetaData.type.rep);
       }
     case Types.TIME:
-      switch (type.type.representation) {
+      switch (columnMetaData.type.rep) {
       case PRIMITIVE_INT:
       case INTEGER:
         return new TimeFromIntAccessor(getter, localCalendar);
       case JAVA_SQL_TIME:
         return new TimeAccessor(getter, localCalendar);
       default:
-        throw new AssertionError("bad " + type.type.representation);
+        throw new AssertionError("bad " + columnMetaData.type.rep);
       }
     case Types.TIMESTAMP:
-      switch (type.type.representation) {
+      switch (columnMetaData.type.rep) {
       case PRIMITIVE_LONG:
       case LONG:
         return new TimestampFromLongAccessor(getter, localCalendar);
@@ -137,31 +140,32 @@ public abstract class AbstractCursor implements Cursor {
       case JAVA_UTIL_DATE:
         return new TimestampFromUtilDateAccessor(getter, localCalendar);
       default:
-        throw new AssertionError("bad " + type.type.representation);
+        throw new AssertionError("bad " + columnMetaData.type.rep);
       }
     case Types.ARRAY:
       return new ArrayAccessor(getter,
-          ((ColumnMetaData.ArrayType) type.type).component, factory);
+          ((ColumnMetaData.ArrayType) columnMetaData.type).component, factory);
     case Types.JAVA_OBJECT:
     case Types.STRUCT:
     case Types.OTHER: // e.g. map
-      if (type.type.typeName.startsWith("INTERVAL_")) {
-        int end = type.type.typeName.indexOf("(");
+      if (columnMetaData.type.name.startsWith("INTERVAL_")) {
+        int end = columnMetaData.type.name.indexOf("(");
         if (end < 0) {
-          end = type.type.typeName.length();
+          end = columnMetaData.type.name.length();
         }
         TimeUnitRange range =
             TimeUnitRange.valueOf(
-                type.type.typeName.substring("INTERVAL_".length(), end));
+                columnMetaData.type.name.substring("INTERVAL_".length(), end));
         if (range.monthly()) {
           return new IntervalYearMonthAccessor(getter, range);
         } else {
-          return new IntervalDayTimeAccessor(getter, range, type.scale);
+          return new IntervalDayTimeAccessor(getter, range,
+              columnMetaData.scale);
         }
       }
       return new ObjectAccessor(getter);
     default:
-      throw new RuntimeException("unknown type " + type.type.type);
+      throw new RuntimeException("unknown type " + columnMetaData.type.id);
     }
   }
 
@@ -617,6 +621,41 @@ public abstract class AbstractCursor implements Cursor {
   }
 
   /**
+   * 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.
+   */
+  private static class NumberAccessor extends BigNumberAccessor {
+    public NumberAccessor(Getter getter) {
+      super(getter);
+    }
+
+    protected Number getNumber() {
+      return (Number) getObject();
+    }
+
+    public BigDecimal getBigDecimal(int scale) {
+      return getBigDecimal();
+    }
+
+    public BigDecimal getBigDecimal() {
+      Number n = getNumber();
+      return n == null ? null
+          : n instanceof BigDecimal ? (BigDecimal) n
+          : n instanceof BigInteger ? new BigDecimal((BigInteger) n)
+          : n instanceof Long ? new BigDecimal((long) n)
+          : n instanceof Integer ? new BigDecimal((int) n)
+          : n instanceof Short ? new BigDecimal((short) n)
+          : n instanceof Byte ? new BigDecimal((byte) n)
+          : n instanceof Double ? new BigDecimal((double) n)
+          : new BigDecimal((float) n);
+    }
+  }
+
+  /**
    * Accessor that assumes that the underlying value is a {@link String};
    * corresponds to {@link java.sql.Types#CHAR}
    * and {@link java.sql.Types#VARCHAR}.

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/4d2f647a/avatica/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java b/avatica/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java
index e953e6a..385f733 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java
@@ -39,11 +39,11 @@ public class ArrayImpl implements Array {
   }
 
   public String getBaseTypeName() throws SQLException {
-    return elementType.typeName;
+    return elementType.name;
   }
 
   public int getBaseType() throws SQLException {
-    return elementType.type;
+    return elementType.id;
   }
 
   public Object getArray() throws SQLException {
@@ -65,7 +65,7 @@ public class ArrayImpl implements Array {
   @SuppressWarnings("unchecked")
   protected Object getArray(List list) throws SQLException {
     int i = 0;
-    switch (elementType.representation) {
+    switch (elementType.rep) {
     case PRIMITIVE_DOUBLE:
       final double[] doubles = new double[list.size()];
       for (double v : (List<Double>) list) {
@@ -118,7 +118,7 @@ public class ArrayImpl implements Array {
       // fall through
     }
     final Object[] objects = list.toArray();
-    switch (elementType.type) {
+    switch (elementType.id) {
     case Types.ARRAY:
       final ColumnMetaData.ArrayType arrayType =
           (ColumnMetaData.ArrayType) elementType;