http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaDatabaseMetaData.java
----------------------------------------------------------------------
diff --git 
a/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaDatabaseMetaData.java
 
b/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaDatabaseMetaData.java
new file mode 100644
index 0000000..b57f36c
--- /dev/null
+++ 
b/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaDatabaseMetaData.java
@@ -0,0 +1,1416 @@
+/*
+ * 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.AvaticaConnection.CallableWithoutException;
+import org.apache.calcite.avatica.remote.MetaDataOperation;
+import org.apache.calcite.avatica.util.Casing;
+import org.apache.calcite.avatica.util.Quoting;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.RowIdLifetime;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.calcite.avatica.InternalProperty.CASE_SENSITIVE;
+import static org.apache.calcite.avatica.InternalProperty.NULL_SORTING;
+import static org.apache.calcite.avatica.InternalProperty.NullSorting;
+import static org.apache.calcite.avatica.InternalProperty.QUOTED_CASING;
+import static org.apache.calcite.avatica.InternalProperty.QUOTING;
+import static org.apache.calcite.avatica.InternalProperty.UNQUOTED_CASING;
+
+/**
+ * Implementation of {@link java.sql.DatabaseMetaData}
+ * for the Avatica engine.
+ *
+ * <p>This class has sub-classes which implement JDBC 3.0 and JDBC 4.0 APIs;
+ * it is instantiated using {@link AvaticaFactory#newDatabaseMetaData}.</p>
+ */
+public class AvaticaDatabaseMetaData implements DatabaseMetaData {
+  private final AvaticaConnection connection;
+
+  protected  AvaticaDatabaseMetaData(AvaticaConnection connection) {
+    this.connection = connection;
+  }
+
+  // Helper methods
+
+  private NullSorting nullSorting() {
+    return NULL_SORTING.getEnum(getProperties(), NullSorting.class);
+  }
+
+  private Quoting quoting() {
+    return QUOTING.getEnum(getProperties(), Quoting.class);
+  }
+
+  private Casing unquotedCasing() {
+    return UNQUOTED_CASING.getEnum(getProperties(), Casing.class);
+  }
+
+  private Casing quotedCasing() {
+    return QUOTED_CASING.getEnum(getProperties(), Casing.class);
+  }
+
+  private boolean caseSensitive() {
+    return CASE_SENSITIVE.getBoolean(getProperties());
+  }
+
+  // JDBC methods
+
+  public boolean allProceduresAreCallable() throws SQLException {
+    return true;
+  }
+
+  public boolean allTablesAreSelectable() throws SQLException {
+    return true;
+  }
+
+  public String getURL() throws SQLException {
+    return connection.url;
+  }
+
+  public String getUserName() throws SQLException {
+    return connection.info.getProperty("user");
+  }
+
+  public boolean isReadOnly() throws SQLException {
+    return true;
+  }
+
+  public boolean nullsAreSortedHigh() throws SQLException {
+    return nullSorting() == NullSorting.HIGH;
+  }
+
+  public boolean nullsAreSortedLow() throws SQLException {
+    return nullSorting() == NullSorting.LOW;
+  }
+
+  public boolean nullsAreSortedAtStart() throws SQLException {
+    return nullSorting() == NullSorting.START;
+  }
+
+  public boolean nullsAreSortedAtEnd() throws SQLException {
+    return nullSorting() == NullSorting.END;
+  }
+
+  public String getDatabaseProductName() throws SQLException {
+    return connection.driver.version.productName;
+  }
+
+  public String getDatabaseProductVersion() throws SQLException {
+    return connection.driver.version.productVersion;
+  }
+
+  public String getDriverName() throws SQLException {
+    return connection.driver.version.name;
+  }
+
+  public String getDriverVersion() throws SQLException {
+    return connection.driver.version.versionString;
+  }
+
+  public int getDriverMajorVersion() {
+    return connection.driver.getMajorVersion();
+  }
+
+  public int getDriverMinorVersion() {
+    return connection.driver.getMinorVersion();
+  }
+
+  public boolean usesLocalFiles() throws SQLException {
+    return false;
+  }
+
+  public boolean usesLocalFilePerTable() throws SQLException {
+    return false;
+  }
+
+  public boolean storesMixedCaseIdentifiers() throws SQLException {
+    return !caseSensitive() && unquotedCasing() == Casing.UNCHANGED;
+  }
+
+  public boolean supportsMixedCaseIdentifiers() throws SQLException {
+    return caseSensitive() && unquotedCasing() == Casing.UNCHANGED;
+  }
+
+  public boolean storesUpperCaseIdentifiers() throws SQLException {
+    return unquotedCasing() == Casing.TO_UPPER;
+  }
+
+  public boolean storesLowerCaseIdentifiers() throws SQLException {
+    return unquotedCasing() == Casing.TO_LOWER;
+  }
+
+  public boolean storesMixedCaseQuotedIdentifiers() throws SQLException {
+    return !caseSensitive() && quotedCasing() == Casing.UNCHANGED;
+  }
+
+  public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException {
+    return caseSensitive() && quotedCasing() == Casing.UNCHANGED;
+  }
+
+  public boolean storesUpperCaseQuotedIdentifiers() throws SQLException {
+    return quotedCasing() == Casing.TO_UPPER;
+  }
+
+  public boolean storesLowerCaseQuotedIdentifiers() throws SQLException {
+    return quotedCasing() == Casing.TO_LOWER;
+  }
+
+  public String getIdentifierQuoteString() throws SQLException {
+    return quoting().string;
+  }
+
+  private Map<InternalProperty, Object> getProperties() {
+    return connection.properties;
+  }
+
+  public String getSQLKeywords() throws SQLException {
+    return connection.invokeWithRetries(
+        new CallableWithoutException<String>() {
+          public String call() {
+            return Meta.DatabaseProperty.GET_S_Q_L_KEYWORDS
+                .getProp(connection.meta, connection.handle, String.class);
+          }
+        });
+  }
+
+  public String getNumericFunctions() throws SQLException {
+    return connection.invokeWithRetries(
+        new CallableWithoutException<String>() {
+          public String call() {
+            return Meta.DatabaseProperty.GET_NUMERIC_FUNCTIONS
+                .getProp(connection.meta, connection.handle, String.class);
+          }
+        });
+  }
+
+  public String getStringFunctions() throws SQLException {
+    return connection.invokeWithRetries(
+        new CallableWithoutException<String>() {
+          public String call() {
+            return Meta.DatabaseProperty.GET_STRING_FUNCTIONS
+                .getProp(connection.meta, connection.handle, String.class);
+          }
+        });
+  }
+
+  public String getSystemFunctions() throws SQLException {
+    return connection.invokeWithRetries(
+        new CallableWithoutException<String>() {
+          public String call() {
+            return Meta.DatabaseProperty.GET_SYSTEM_FUNCTIONS
+                .getProp(connection.meta, connection.handle, String.class);
+          }
+        });
+  }
+
+  public String getTimeDateFunctions() throws SQLException {
+    return connection.invokeWithRetries(
+        new CallableWithoutException<String>() {
+          public String call() {
+            return Meta.DatabaseProperty.GET_TIME_DATE_FUNCTIONS
+                .getProp(connection.meta, connection.handle, String.class);
+          }
+        });
+  }
+
+  public String getSearchStringEscape() throws SQLException {
+    return "\\";
+  }
+
+  public String getExtraNameCharacters() throws SQLException {
+    return "";
+  }
+
+  public boolean supportsAlterTableWithAddColumn() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsAlterTableWithDropColumn() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsColumnAliasing() throws SQLException {
+    return true;
+  }
+
+  public boolean nullPlusNonNullIsNull() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsConvert() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsConvert(int fromType, int toType) throws SQLException 
{
+    return false; // TODO: more detail
+  }
+
+  public boolean supportsTableCorrelationNames() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsDifferentTableCorrelationNames() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsExpressionsInOrderBy() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsOrderByUnrelated() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsGroupBy() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsGroupByUnrelated() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsGroupByBeyondSelect() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsLikeEscapeClause() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsMultipleResultSets() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsMultipleTransactions() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsNonNullableColumns() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsMinimumSQLGrammar() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsCoreSQLGrammar() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsExtendedSQLGrammar() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsANSI92EntryLevelSQL() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsANSI92IntermediateSQL() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsANSI92FullSQL() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsIntegrityEnhancementFacility() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsOuterJoins() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsFullOuterJoins() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsLimitedOuterJoins() throws SQLException {
+    return true;
+  }
+
+  public String getSchemaTerm() throws SQLException {
+    return "schema";
+  }
+
+  public String getProcedureTerm() throws SQLException {
+    return "procedure";
+  }
+
+  public String getCatalogTerm() throws SQLException {
+    return "catalog";
+  }
+
+  public boolean isCatalogAtStart() throws SQLException {
+    return true;
+  }
+
+  public String getCatalogSeparator() throws SQLException {
+    return ".";
+  }
+
+  public boolean supportsSchemasInDataManipulation() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsSchemasInProcedureCalls() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsSchemasInTableDefinitions() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsSchemasInIndexDefinitions() throws SQLException {
+    return true; // except that we don't support index definitions
+  }
+
+  public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException {
+    return true; // except that we don't support privilege definitions
+  }
+
+  public boolean supportsCatalogsInDataManipulation() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsCatalogsInProcedureCalls() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsCatalogsInTableDefinitions() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsCatalogsInIndexDefinitions() throws SQLException {
+    return true; // except that we don't support index definitions
+  }
+
+  public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException {
+    return true; // except that we don't support privilege definitions
+  }
+
+  public boolean supportsPositionedDelete() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsPositionedUpdate() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsSelectForUpdate() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsStoredProcedures() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsSubqueriesInComparisons() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsSubqueriesInExists() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsSubqueriesInIns() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsSubqueriesInQuantifieds() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsCorrelatedSubqueries() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsUnion() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsUnionAll() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsOpenCursorsAcrossCommit() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsOpenCursorsAcrossRollback() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsOpenStatementsAcrossCommit() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsOpenStatementsAcrossRollback() throws SQLException {
+    return false;
+  }
+
+  public int getMaxBinaryLiteralLength() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxCharLiteralLength() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxColumnNameLength() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxColumnsInGroupBy() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxColumnsInIndex() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxColumnsInOrderBy() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxColumnsInSelect() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxColumnsInTable() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxConnections() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxCursorNameLength() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxIndexLength() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxSchemaNameLength() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxProcedureNameLength() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxCatalogNameLength() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxRowSize() throws SQLException {
+    return 0;
+  }
+
+  public boolean doesMaxRowSizeIncludeBlobs() throws SQLException {
+    return false;
+  }
+
+  public int getMaxStatementLength() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxStatements() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxTableNameLength() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxTablesInSelect() throws SQLException {
+    return 0;
+  }
+
+  public int getMaxUserNameLength() throws SQLException {
+    return 0;
+  }
+
+  public int getDefaultTransactionIsolation() throws SQLException {
+    return connection.invokeWithRetries(
+        new CallableWithoutException<Integer>() {
+          public Integer call() {
+            return Meta.DatabaseProperty.GET_DEFAULT_TRANSACTION_ISOLATION
+                .getProp(connection.meta, connection.handle, Integer.class);
+          }
+        });
+  }
+
+  public boolean supportsTransactions() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsTransactionIsolationLevel(int level)
+      throws SQLException {
+    return level == Connection.TRANSACTION_NONE;
+  }
+
+  public boolean supportsDataDefinitionAndDataManipulationTransactions()
+      throws SQLException {
+    return false;
+  }
+
+  public boolean supportsDataManipulationTransactionsOnly()
+      throws SQLException {
+    return true;
+  }
+
+  public boolean dataDefinitionCausesTransactionCommit() throws SQLException {
+    return true;
+  }
+
+  public boolean dataDefinitionIgnoredInTransactions() throws SQLException {
+    return false;
+  }
+
+  public ResultSet getProcedures(
+      final String catalog,
+      final String schemaPattern,
+      final String procedureNamePattern) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getProcedures(connection.handle, catalog, 
pat(schemaPattern),
+                        pat(procedureNamePattern)),
+                    new QueryState(MetaDataOperation.GET_PROCEDURES, catalog, 
schemaPattern,
+                        procedureNamePattern));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getProcedureColumns(
+      final String catalog,
+      final String schemaPattern,
+      final String procedureNamePattern,
+      final String columnNamePattern) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getProcedureColumns(connection.handle, 
catalog,
+                        pat(schemaPattern), pat(procedureNamePattern), 
pat(columnNamePattern)),
+                    new QueryState(MetaDataOperation.GET_PROCEDURE_COLUMNS, 
catalog, schemaPattern,
+                        procedureNamePattern, columnNamePattern));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getTables(
+      final String catalog,
+      final String schemaPattern,
+      final String tableNamePattern,
+      final String[] types) throws SQLException {
+    final List<String> typeList = types == null ? null : Arrays.asList(types);
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getTables(connection.handle, catalog, 
pat(schemaPattern),
+                        pat(tableNamePattern), typeList),
+                    new QueryState(MetaDataOperation.GET_TABLES, catalog, 
schemaPattern,
+                        tableNamePattern, types));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  private static Meta.Pat pat(String schemaPattern) {
+    return Meta.Pat.of(schemaPattern);
+  }
+
+  public ResultSet getSchemas(
+      final String catalog, final String schemaPattern) throws SQLException {
+    // TODO: add a 'catch ... throw new SQLException' logic to this and other
+    // getXxx methods. Right now any error will throw a RuntimeException
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getSchemas(connection.handle, catalog, 
pat(schemaPattern)),
+                    new QueryState(MetaDataOperation.GET_SCHEMAS_WITH_ARGS, 
catalog,
+                        schemaPattern));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getSchemas() throws SQLException {
+    return getSchemas(null, null);
+  }
+
+  public ResultSet getCatalogs() throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return 
connection.createResultSet(connection.meta.getCatalogs(connection.handle),
+                    new QueryState(MetaDataOperation.GET_CATALOGS));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getTableTypes() throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return 
connection.createResultSet(connection.meta.getTableTypes(connection.handle),
+                    new QueryState(MetaDataOperation.GET_TABLE_TYPES));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getColumns(
+      final String catalog,
+      final String schemaPattern,
+      final String tableNamePattern,
+      final String columnNamePattern) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getColumns(connection.handle, catalog, 
pat(schemaPattern),
+                        pat(tableNamePattern), pat(columnNamePattern)),
+                    new QueryState(MetaDataOperation.GET_COLUMNS, catalog, 
schemaPattern,
+                        tableNamePattern, columnNamePattern));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getColumnPrivileges(
+      final String catalog,
+      final String schema,
+      final String table,
+      final String columnNamePattern) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getColumnPrivileges(connection.handle, 
catalog, schema, table,
+                        pat(columnNamePattern)),
+                    new QueryState(MetaDataOperation.GET_COLUMN_PRIVILEGES, 
catalog, schema, table,
+                        columnNamePattern));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getTablePrivileges(
+      final String catalog,
+      final String schemaPattern,
+      final String tableNamePattern) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getTablePrivileges(connection.handle, 
catalog,
+                        pat(schemaPattern), pat(tableNamePattern)),
+                    new QueryState(MetaDataOperation.GET_TABLE_PRIVILEGES, 
catalog, schemaPattern,
+                        tableNamePattern));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getBestRowIdentifier(
+      final String catalog,
+      final String schema,
+      final String table,
+      final int scope,
+      final boolean nullable) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getBestRowIdentifier(connection.handle, 
catalog, schema, table,
+                        scope, nullable),
+                    new QueryState(MetaDataOperation.GET_BEST_ROW_IDENTIFIER, 
catalog, table, scope,
+                        nullable));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getVersionColumns(
+      final String catalog, final String schema, final String table) throws 
SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getVersionColumns(connection.handle, 
catalog, schema, table),
+                    new QueryState(MetaDataOperation.GET_VERSION_COLUMNS, 
catalog, schema, table));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getPrimaryKeys(
+      final String catalog, final String schema, final String table) throws 
SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getPrimaryKeys(connection.handle, catalog, 
schema, table),
+                    new QueryState(MetaDataOperation.GET_PRIMARY_KEYS, 
catalog, schema, table));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getImportedKeys(
+      final String catalog, final String schema, final String table) throws 
SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getImportedKeys(connection.handle, 
catalog, schema, table),
+                    new QueryState(MetaDataOperation.GET_IMPORTED_KEYS, 
catalog, schema, table));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getExportedKeys(
+      final String catalog, final String schema, final String table) throws 
SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getExportedKeys(connection.handle, 
catalog, schema, table),
+                    new QueryState(MetaDataOperation.GET_EXPORTED_KEYS, 
catalog, schema, table));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getCrossReference(
+      final String parentCatalog,
+      final String parentSchema,
+      final String parentTable,
+      final String foreignCatalog,
+      final String foreignSchema,
+      final String foreignTable) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getCrossReference(connection.handle, 
parentCatalog,
+                        parentSchema, parentTable, foreignCatalog, 
foreignSchema, foreignTable),
+                    new QueryState(MetaDataOperation.GET_CROSS_REFERENCE, 
parentCatalog,
+                        parentSchema, parentTable, foreignCatalog, 
foreignSchema, foreignTable));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getTypeInfo() throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return 
connection.createResultSet(connection.meta.getTypeInfo(connection.handle),
+                    new QueryState(MetaDataOperation.GET_TYPE_INFO));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getIndexInfo(
+      final String catalog,
+      final String schema,
+      final String table,
+      final boolean unique,
+      final boolean approximate) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getIndexInfo(connection.handle, catalog, 
schema, table, unique,
+                        approximate),
+                    new QueryState(MetaDataOperation.GET_INDEX_INFO, catalog, 
schema, table, unique,
+                        approximate));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public boolean supportsResultSetType(int type) throws SQLException {
+    return type == ResultSet.TYPE_FORWARD_ONLY;
+  }
+
+  public boolean supportsResultSetConcurrency(
+      int type, int concurrency) throws SQLException {
+    return type == ResultSet.TYPE_FORWARD_ONLY
+        && concurrency == ResultSet.CONCUR_READ_ONLY;
+  }
+
+  public boolean ownUpdatesAreVisible(int type) throws SQLException {
+    throw connection.helper.todo();
+  }
+
+  public boolean ownDeletesAreVisible(int type) throws SQLException {
+    throw connection.helper.todo();
+  }
+
+  public boolean ownInsertsAreVisible(int type) throws SQLException {
+    throw connection.helper.todo();
+  }
+
+  public boolean othersUpdatesAreVisible(int type) throws SQLException {
+    throw connection.helper.todo();
+  }
+
+  public boolean othersDeletesAreVisible(int type) throws SQLException {
+    throw connection.helper.todo();
+  }
+
+  public boolean othersInsertsAreVisible(int type) throws SQLException {
+    throw connection.helper.todo();
+  }
+
+  public boolean updatesAreDetected(int type) throws SQLException {
+    throw connection.helper.todo();
+  }
+
+  public boolean deletesAreDetected(int type) throws SQLException {
+    throw connection.helper.todo();
+  }
+
+  public boolean insertsAreDetected(int type) throws SQLException {
+    throw connection.helper.todo();
+  }
+
+  public boolean supportsBatchUpdates() throws SQLException {
+    return false;
+  }
+
+  public ResultSet getUDTs(
+      final String catalog,
+      final String schemaPattern,
+      final String typeNamePattern,
+      final int[] types) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getUDTs(connection.handle, catalog, 
pat(schemaPattern),
+                        pat(typeNamePattern), types),
+                    new QueryState(MetaDataOperation.GET_UDTS, catalog, 
schemaPattern,
+                        typeNamePattern, types));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public Connection getConnection() throws SQLException {
+    return connection;
+  }
+
+  public boolean supportsSavepoints() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsNamedParameters() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsMultipleOpenResults() throws SQLException {
+    return false;
+  }
+
+  public boolean supportsGetGeneratedKeys() throws SQLException {
+    return false;
+  }
+
+  public ResultSet getSuperTypes(
+      final String catalog,
+      final String schemaPattern,
+      final String typeNamePattern) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getSuperTypes(connection.handle, catalog, 
pat(schemaPattern),
+                        pat(typeNamePattern)),
+                    new QueryState(MetaDataOperation.GET_SUPER_TYPES, catalog, 
schemaPattern,
+                        typeNamePattern));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getSuperTables(
+      final String catalog,
+      final String schemaPattern,
+      final String tableNamePattern) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getSuperTables(connection.handle, catalog, 
pat(schemaPattern),
+                        pat(tableNamePattern)),
+                    new QueryState(MetaDataOperation.GET_SUPER_TABLES, 
catalog, schemaPattern,
+                        tableNamePattern));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getAttributes(
+      final String catalog,
+      final String schemaPattern,
+      final String typeNamePattern,
+      final String attributeNamePattern) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getAttributes(connection.handle, catalog, 
pat(schemaPattern),
+                        pat(typeNamePattern), pat(attributeNamePattern)),
+                    new QueryState(MetaDataOperation.GET_ATTRIBUTES, catalog, 
schemaPattern,
+                        typeNamePattern, attributeNamePattern));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public boolean supportsResultSetHoldability(int holdability)
+      throws SQLException {
+    throw connection.helper.todo();
+  }
+
+  public int getResultSetHoldability() {
+    return ResultSet.HOLD_CURSORS_OVER_COMMIT;
+  }
+
+  public int getDatabaseMajorVersion() throws SQLException {
+    return connection.driver.version.databaseMajorVersion;
+  }
+
+  public int getDatabaseMinorVersion() throws SQLException {
+    return connection.driver.version.databaseMinorVersion;
+  }
+
+  public int getJDBCMajorVersion() throws SQLException {
+    return connection.factory.getJdbcMajorVersion();
+  }
+
+  public int getJDBCMinorVersion() throws SQLException {
+    return connection.factory.getJdbcMinorVersion();
+  }
+
+  public int getSQLStateType() throws SQLException {
+    return sqlStateSQL;
+  }
+
+  public boolean locatorsUpdateCopy() throws SQLException {
+    return true;
+  }
+
+  public boolean supportsStatementPooling() throws SQLException {
+    return false;
+  }
+
+  public RowIdLifetime getRowIdLifetime() throws SQLException {
+    return RowIdLifetime.ROWID_UNSUPPORTED;
+  }
+
+  public boolean supportsStoredFunctionsUsingCallSyntax()
+      throws SQLException {
+    return true;
+  }
+
+  public boolean autoCommitFailureClosesAllResultSets() throws SQLException {
+    return false;
+  }
+
+  public ResultSet getClientInfoProperties() throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getClientInfoProperties(connection.handle),
+                    new 
QueryState(MetaDataOperation.GET_CLIENT_INFO_PROPERTIES));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getFunctions(
+      final String catalog,
+      final String schemaPattern,
+      final String functionNamePattern) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getFunctions(connection.handle, catalog, 
pat(schemaPattern),
+                        pat(functionNamePattern)),
+                    new QueryState(MetaDataOperation.GET_FUNCTIONS, catalog, 
schemaPattern,
+                        functionNamePattern));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getFunctionColumns(
+      final String catalog,
+      final String schemaPattern,
+      final String functionNamePattern,
+      final String columnNamePattern) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getFunctionColumns(connection.handle, 
catalog,
+                        pat(schemaPattern), pat(functionNamePattern), 
pat(columnNamePattern)),
+                    new QueryState(MetaDataOperation.GET_FUNCTION_COLUMNS, 
catalog,
+                        schemaPattern, functionNamePattern, 
columnNamePattern));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public ResultSet getPseudoColumns(
+      final String catalog,
+      final String schemaPattern,
+      final String tableNamePattern,
+      final String columnNamePattern) throws SQLException {
+    try {
+      return connection.invokeWithRetries(
+          new CallableWithoutException<ResultSet>() {
+            public ResultSet call() {
+              try {
+                return connection.createResultSet(
+                    connection.meta.getPseudoColumns(connection.handle, 
catalog, pat(schemaPattern),
+                        pat(tableNamePattern), pat(columnNamePattern)),
+                    new QueryState(MetaDataOperation.GET_PSEUDO_COLUMNS, 
catalog, schemaPattern,
+                        tableNamePattern, columnNamePattern));
+              } catch (SQLException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
+    } catch (RuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof SQLException) {
+        throw (SQLException) cause;
+      }
+      throw e;
+    }
+  }
+
+  public boolean generatedKeyAlwaysReturned() throws SQLException {
+    return false;
+  }
+
+  // implement Wrapper
+
+  public <T> T unwrap(Class<T> iface) throws SQLException {
+    if (iface.isInstance(this)) {
+      return iface.cast(this);
+    }
+    throw connection.helper.createException(
+        "does not implement '" + iface + "'");
+  }
+
+  public boolean isWrapperFor(Class<?> iface) throws SQLException {
+    return iface.isInstance(this);
+  }
+}
+
+// End AvaticaDatabaseMetaData.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaFactory.java
----------------------------------------------------------------------
diff --git 
a/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaFactory.java 
b/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaFactory.java
new file mode 100644
index 0000000..1a2a97f
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaFactory.java
@@ -0,0 +1,83 @@
+/*
+ * 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 java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.Properties;
+import java.util.TimeZone;
+
+/**
+ * Factory for JDBC objects.
+ *
+ * <p>There is an implementation for each supported JDBC version.</p>
+ */
+public interface AvaticaFactory {
+  int getJdbcMajorVersion();
+
+  int getJdbcMinorVersion();
+
+  AvaticaConnection newConnection(
+      UnregisteredDriver driver,
+      AvaticaFactory factory,
+      String url,
+      Properties info) throws SQLException;
+
+  AvaticaStatement newStatement(AvaticaConnection connection,
+      /*@Nullable*/ Meta.StatementHandle h, int resultSetType,
+      int resultSetConcurrency, int resultSetHoldability) throws SQLException;
+
+  AvaticaPreparedStatement newPreparedStatement(AvaticaConnection connection,
+      /*@Nullable*/ Meta.StatementHandle h, Meta.Signature signature,
+      int resultSetType, int resultSetConcurrency, int resultSetHoldability)
+      throws SQLException;
+
+  /**
+   * Creates a result set. You will then need to call
+   * {@link AvaticaResultSet#execute()} on it.
+   *
+   * @param statement Statement
+   * @param state The state used to create this result set
+   * @param signature Prepared statement
+   * @param timeZone Time zone
+   * @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, QueryState state,
+      Meta.Signature signature, TimeZone timeZone, Meta.Frame firstFrame)
+      throws SQLException;
+
+  /**
+   * Creates meta data for the database.
+   *
+   * @return Database meta data
+   */
+  AvaticaDatabaseMetaData newDatabaseMetaData(AvaticaConnection connection);
+
+  /**
+   * Creates meta data for a result set.
+   *
+   * @param statement Statement
+   * @param signature Prepared statement
+   * @return Result set meta data
+   */
+  ResultSetMetaData newResultSetMetaData(AvaticaStatement statement,
+      Meta.Signature signature) throws SQLException;
+}
+
+// End AvaticaFactory.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaJdbc41Factory.java
----------------------------------------------------------------------
diff --git 
a/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaJdbc41Factory.java
 
b/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaJdbc41Factory.java
new file mode 100644
index 0000000..f410dc8
--- /dev/null
+++ 
b/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaJdbc41Factory.java
@@ -0,0 +1,256 @@
+/*
+ * 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 java.io.InputStream;
+import java.io.Reader;
+import java.sql.NClob;
+import java.sql.ResultSetMetaData;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLXML;
+import java.util.Properties;
+import java.util.TimeZone;
+
+/**
+ * Implementation of {@link AvaticaFactory} for JDBC 4.1 (corresponds to JDK
+ * 1.7).
+ */
+@SuppressWarnings("UnusedDeclaration")
+class AvaticaJdbc41Factory implements AvaticaFactory {
+  private final int major;
+  private final int minor;
+
+  /** Creates a JDBC factory. */
+  public AvaticaJdbc41Factory() {
+    this(4, 1);
+  }
+
+  /** Creates a JDBC factory with given major/minor version number. */
+  protected AvaticaJdbc41Factory(int major, int minor) {
+    this.major = major;
+    this.minor = minor;
+  }
+
+  public int getJdbcMajorVersion() {
+    return major;
+  }
+
+  public int getJdbcMinorVersion() {
+    return minor;
+  }
+
+  public AvaticaConnection newConnection(
+      UnregisteredDriver driver,
+      AvaticaFactory factory,
+      String url,
+      Properties info) {
+    return new AvaticaJdbc41Connection(driver, factory, url, info);
+  }
+
+  public AvaticaDatabaseMetaData newDatabaseMetaData(
+      AvaticaConnection connection) {
+    return new AvaticaJdbc41DatabaseMetaData(connection);
+  }
+
+  public AvaticaStatement newStatement(AvaticaConnection connection,
+      Meta.StatementHandle h, int resultSetType, int resultSetConcurrency,
+      int resultSetHoldability) {
+    return new AvaticaJdbc41Statement(connection, h, resultSetType,
+        resultSetConcurrency, resultSetHoldability);
+  }
+
+  public AvaticaPreparedStatement newPreparedStatement(
+      AvaticaConnection connection, Meta.StatementHandle h,
+      Meta.Signature signature, int resultSetType, int resultSetConcurrency,
+      int resultSetHoldability)
+      throws SQLException {
+    return new AvaticaJdbc41PreparedStatement(connection, h, signature,
+        resultSetType, resultSetConcurrency, resultSetHoldability);
+  }
+
+  public AvaticaResultSet newResultSet(AvaticaStatement statement,
+      QueryState state, Meta.Signature signature, TimeZone timeZone, 
Meta.Frame firstFrame) {
+    final ResultSetMetaData metaData =
+        newResultSetMetaData(statement, signature);
+    return new AvaticaResultSet(statement, state, signature, metaData, 
timeZone,
+        firstFrame);
+  }
+
+  public AvaticaResultSetMetaData newResultSetMetaData(
+      AvaticaStatement statement, Meta.Signature signature) {
+    return new AvaticaResultSetMetaData(statement, null, signature);
+  }
+
+  /** Implementation of Connection for JDBC 4.1. */
+  private static class AvaticaJdbc41Connection extends AvaticaConnection {
+    AvaticaJdbc41Connection(UnregisteredDriver driver,
+        AvaticaFactory factory,
+        String url,
+        Properties info) {
+      super(driver, factory, url, info);
+    }
+  }
+
+  /** Implementation of Statement for JDBC 4.1. */
+  private static class AvaticaJdbc41Statement extends AvaticaStatement {
+    public AvaticaJdbc41Statement(AvaticaConnection connection,
+        Meta.StatementHandle h, int resultSetType, int resultSetConcurrency,
+        int resultSetHoldability) {
+      super(connection, h, resultSetType, resultSetConcurrency,
+          resultSetHoldability);
+    }
+  }
+
+  /** Implementation of PreparedStatement for JDBC 4.1. */
+  private static class AvaticaJdbc41PreparedStatement
+      extends AvaticaPreparedStatement {
+    AvaticaJdbc41PreparedStatement(AvaticaConnection connection,
+        Meta.StatementHandle h, Meta.Signature signature, int resultSetType,
+        int resultSetConcurrency, int resultSetHoldability)
+        throws SQLException {
+      super(connection, h, signature, resultSetType, resultSetConcurrency,
+          resultSetHoldability);
+    }
+
+    public void setRowId(
+        int parameterIndex,
+        RowId x) throws SQLException {
+      getSite(parameterIndex).setRowId(x);
+    }
+
+    public void setNString(
+        int parameterIndex, String value) throws SQLException {
+      getSite(parameterIndex).setNString(value);
+    }
+
+    public void setNCharacterStream(
+        int parameterIndex,
+        Reader value,
+        long length) throws SQLException {
+      getSite(parameterIndex)
+          .setNCharacterStream(value, length);
+    }
+
+    public void setNClob(
+        int parameterIndex,
+        NClob value) throws SQLException {
+      getSite(parameterIndex).setNClob(value);
+    }
+
+    public void setClob(
+        int parameterIndex,
+        Reader reader,
+        long length) throws SQLException {
+      getSite(parameterIndex)
+          .setClob(reader, length);
+    }
+
+    public void setBlob(
+        int parameterIndex,
+        InputStream inputStream,
+        long length) throws SQLException {
+      getSite(parameterIndex)
+          .setBlob(inputStream, length);
+    }
+
+    public void setNClob(
+        int parameterIndex,
+        Reader reader,
+        long length) throws SQLException {
+      getSite(parameterIndex)
+          .setNClob(reader, length);
+    }
+
+    public void setSQLXML(
+        int parameterIndex, SQLXML xmlObject) throws SQLException {
+      getSite(parameterIndex).setSQLXML(xmlObject);
+    }
+
+    public void setAsciiStream(
+        int parameterIndex,
+        InputStream x,
+        long length) throws SQLException {
+      getSite(parameterIndex)
+          .setAsciiStream(x, length);
+    }
+
+    public void setBinaryStream(
+        int parameterIndex,
+        InputStream x,
+        long length) throws SQLException {
+      getSite(parameterIndex)
+          .setBinaryStream(x, length);
+    }
+
+    public void setCharacterStream(
+        int parameterIndex,
+        Reader reader,
+        long length) throws SQLException {
+      getSite(parameterIndex)
+          .setCharacterStream(reader, length);
+    }
+
+    public void setAsciiStream(
+        int parameterIndex, InputStream x) throws SQLException {
+      getSite(parameterIndex).setAsciiStream(x);
+    }
+
+    public void setBinaryStream(
+        int parameterIndex, InputStream x) throws SQLException {
+      getSite(parameterIndex).setBinaryStream(x);
+    }
+
+    public void setCharacterStream(
+        int parameterIndex, Reader reader) throws SQLException {
+      getSite(parameterIndex)
+          .setCharacterStream(reader);
+    }
+
+    public void setNCharacterStream(
+        int parameterIndex, Reader value) throws SQLException {
+      getSite(parameterIndex)
+          .setNCharacterStream(value);
+    }
+
+    public void setClob(
+        int parameterIndex,
+        Reader reader) throws SQLException {
+      getSite(parameterIndex).setClob(reader);
+    }
+
+    public void setBlob(
+        int parameterIndex, InputStream inputStream) throws SQLException {
+      getSite(parameterIndex).setBlob(inputStream);
+    }
+
+    public void setNClob(
+        int parameterIndex, Reader reader) throws SQLException {
+      getSite(parameterIndex).setNClob(reader);
+    }
+  }
+
+  /** Implementation of DatabaseMetaData for JDBC 4.1. */
+  private static class AvaticaJdbc41DatabaseMetaData
+      extends AvaticaDatabaseMetaData {
+    AvaticaJdbc41DatabaseMetaData(AvaticaConnection connection) {
+      super(connection);
+    }
+  }
+}
+
+// End AvaticaJdbc41Factory.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaParameter.java
----------------------------------------------------------------------
diff --git 
a/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaParameter.java 
b/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaParameter.java
new file mode 100644
index 0000000..3c5a9ab
--- /dev/null
+++ 
b/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaParameter.java
@@ -0,0 +1,135 @@
+/*
+ * 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 com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Objects;
+
+/**
+ * Metadata for a parameter.
+ */
+public class AvaticaParameter {
+  public final boolean signed;
+  public final int precision;
+  public final int scale;
+  public final int parameterType;
+  public final String typeName;
+  public final String className;
+  public final String name;
+
+  @JsonCreator
+  public AvaticaParameter(
+      @JsonProperty("signed") boolean signed,
+      @JsonProperty("precision") int precision,
+      @JsonProperty("scale") int scale,
+      @JsonProperty("parameterType") int parameterType,
+      @JsonProperty("typeName") String typeName,
+      @JsonProperty("className") String className,
+      @JsonProperty("name") String name) {
+    this.signed = signed;
+    this.precision = precision;
+    this.scale = scale;
+    this.parameterType = parameterType;
+    this.typeName = typeName;
+    this.className = className;
+    this.name = name;
+  }
+
+  public Common.AvaticaParameter toProto() {
+    Common.AvaticaParameter.Builder builder = 
Common.AvaticaParameter.newBuilder();
+
+    builder.setSigned(signed);
+    builder.setPrecision(precision);
+    builder.setScale(scale);
+    builder.setParameterType(parameterType);
+    builder.setTypeName(typeName);
+    builder.setClassName(className);
+    builder.setName(name);
+
+    return builder.build();
+  }
+
+  public static AvaticaParameter fromProto(Common.AvaticaParameter proto) {
+    return new AvaticaParameter(proto.getSigned(), proto.getPrecision(),
+        proto.getScale(), proto.getParameterType(), proto.getTypeName(),
+        proto.getClassName(), proto.getName());
+  }
+
+  @Override public int hashCode() {
+    return Objects.hash(className, name, parameterType, precision, scale,
+        signed, typeName);
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) {
+      return true;
+    }
+    if (obj instanceof AvaticaParameter) {
+      AvaticaParameter other = (AvaticaParameter) obj;
+
+      if (null == className) {
+        if (null != other.className) {
+          return false;
+        }
+      } else if (!className.equals(other.className)) {
+        return false;
+      }
+
+      if (null == name) {
+        if (null != other.name) {
+          return false;
+        }
+      } else if (!name.equals(other.name)) {
+        return false;
+      }
+
+      if (parameterType != other.parameterType) {
+        return false;
+      }
+
+      if (precision != other.precision) {
+        return false;
+      }
+
+      if (scale != other.scale) {
+        return false;
+      }
+
+      if (signed != other.signed) {
+        return false;
+      }
+
+      if (null == typeName) {
+        if (null != other.typeName) {
+          return false;
+        }
+      } else if (!typeName.equals(other.typeName)) {
+        return false;
+      }
+
+      return true;
+    }
+
+    return false;
+  }
+}
+
+// End AvaticaParameter.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaPreparedStatement.java
----------------------------------------------------------------------
diff --git 
a/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaPreparedStatement.java
 
b/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaPreparedStatement.java
new file mode 100644
index 0000000..f3a950a
--- /dev/null
+++ 
b/avatica/core/src/main/java/org/apache/calcite/avatica/AvaticaPreparedStatement.java
@@ -0,0 +1,343 @@
+/*
+ * 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.Signature;
+import org.apache.calcite.avatica.remote.TypedValue;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.List;
+
+/**
+ * Implementation of {@link java.sql.PreparedStatement}
+ * for the Avatica engine.
+ *
+ * <p>This class has sub-classes which implement JDBC 3.0 and JDBC 4.0 APIs;
+ * it is instantiated using {@link AvaticaFactory#newPreparedStatement}.</p>
+ */
+public abstract class AvaticaPreparedStatement
+    extends AvaticaStatement
+    implements PreparedStatement, ParameterMetaData {
+  private final ResultSetMetaData resultSetMetaData;
+  private Calendar calendar;
+  protected final TypedValue[] slots;
+
+  /**
+   * Creates an AvaticaPreparedStatement.
+   *
+   * @param connection Connection
+   * @param h Statement handle
+   * @param signature Result of preparing statement
+   * @param resultSetType Result set type
+   * @param resultSetConcurrency Result set concurrency
+   * @param resultSetHoldability Result set holdability
+   * @throws SQLException If fails due to underlying implementation reasons.
+   */
+  protected AvaticaPreparedStatement(AvaticaConnection connection,
+      Meta.StatementHandle h,
+      Meta.Signature signature,
+      int resultSetType,
+      int resultSetConcurrency,
+      int resultSetHoldability) throws SQLException {
+    super(connection, h, resultSetType, resultSetConcurrency,
+        resultSetHoldability, signature);
+    this.slots = new TypedValue[signature.parameters.size()];
+    this.resultSetMetaData =
+        connection.factory.newResultSetMetaData(this, signature);
+  }
+
+  @Override protected List<TypedValue> getParameterValues() {
+    return Arrays.asList(slots);
+  }
+
+  /** Returns a calendar in the connection's time zone, creating one the first
+   * time this method is called.
+   *
+   * <p>Uses the calendar to offset date-time values when calling methods such
+   * as {@link #setDate(int, Date)}.
+   *
+   * <p>A note on thread-safety. This method does not strictly need to be
+   * {@code synchronized}, because JDBC does not promise thread safety if
+   * different threads are accessing the same statement, or even different
+   * objects within a particular connection.
+   *
+   * <p>The calendar returned is to be used only within this statement, and
+   * JDBC only allows access to a statement from within one thread, so
+   * therefore does not need to be synchronized when accessed.
+   */
+  protected synchronized Calendar getCalendar() {
+    if (calendar == null) {
+      calendar = Calendar.getInstance(connection.getTimeZone());
+    }
+    return calendar;
+  }
+
+  // implement PreparedStatement
+
+  public ResultSet executeQuery() throws SQLException {
+    this.updateCount = -1;
+    final Signature sig = getSignature();
+    return getConnection().executeQueryInternal(this, sig, null,
+        new QueryState(sig.sql), false);
+  }
+
+  public ParameterMetaData getParameterMetaData() throws SQLException {
+    return this;
+  }
+
+  public final int executeUpdate() throws SQLException {
+    return (int) executeLargeUpdate();
+  }
+
+  public long executeLargeUpdate() throws SQLException {
+    getConnection().executeQueryInternal(this, null, null,
+        new QueryState(getSignature().sql), true);
+    return updateCount;
+  }
+
+  public void setNull(int parameterIndex, int sqlType) throws SQLException {
+    getSite(parameterIndex).setNull(sqlType);
+  }
+
+  public void setBoolean(int parameterIndex, boolean x) throws SQLException {
+    getSite(parameterIndex).setBoolean(x);
+  }
+
+  public void setByte(int parameterIndex, byte x) throws SQLException {
+    getSite(parameterIndex).setByte(x);
+  }
+
+  public void setShort(int parameterIndex, short x) throws SQLException {
+    getSite(parameterIndex).setShort(x);
+  }
+
+  public void setInt(int parameterIndex, int x) throws SQLException {
+    getSite(parameterIndex).setInt(x);
+  }
+
+  public void setLong(int parameterIndex, long x) throws SQLException {
+    getSite(parameterIndex).setLong(x);
+  }
+
+  public void setFloat(int parameterIndex, float x) throws SQLException {
+    getSite(parameterIndex).setFloat(x);
+  }
+
+  public void setDouble(int parameterIndex, double x) throws SQLException {
+    getSite(parameterIndex).setDouble(x);
+  }
+
+  public void setBigDecimal(int parameterIndex, BigDecimal x)
+      throws SQLException {
+    getSite(parameterIndex).setBigDecimal(x);
+  }
+
+  public void setString(int parameterIndex, String x) throws SQLException {
+    getSite(parameterIndex).setString(x);
+  }
+
+  public void setBytes(int parameterIndex, byte[] x) throws SQLException {
+    getSite(parameterIndex).setBytes(x);
+  }
+
+  public void setAsciiStream(int parameterIndex, InputStream x, int length)
+      throws SQLException {
+    getSite(parameterIndex).setAsciiStream(x, length);
+  }
+
+  public void setUnicodeStream(int parameterIndex, InputStream x, int length)
+      throws SQLException {
+    getSite(parameterIndex).setUnicodeStream(x, length);
+  }
+
+  public void setBinaryStream(int parameterIndex, InputStream x, int length)
+      throws SQLException {
+    getSite(parameterIndex).setBinaryStream(x, length);
+  }
+
+  public void clearParameters() throws SQLException {
+    for (int i = 0; i < slots.length; i++) {
+      slots[i] = null;
+    }
+  }
+
+  public void setObject(int parameterIndex, Object x, int targetSqlType)
+      throws SQLException {
+    getSite(parameterIndex).setObject(x, targetSqlType);
+  }
+
+  public void setObject(int parameterIndex, Object x) throws SQLException {
+    getSite(parameterIndex).setObject(x);
+  }
+
+  public boolean execute() throws SQLException {
+    this.updateCount = -1;
+    // We don't know if this is actually an update or a query, so call it a 
query so we pass the
+    // Signature to the server.
+    getConnection().executeQueryInternal(this, getSignature(), null,
+        new QueryState(getSignature().sql), false);
+    // Result set is null for DML or DDL.
+    // Result set is closed if user cancelled the query.
+    return openResultSet != null && !openResultSet.isClosed();
+  }
+
+  public void addBatch() throws SQLException {
+    throw connection.helper.unsupported();
+  }
+
+  public void setCharacterStream(int parameterIndex, Reader reader, int length)
+      throws SQLException {
+    getSite(parameterIndex).setCharacterStream(reader, length);
+  }
+
+  public void setRef(int parameterIndex, Ref x) throws SQLException {
+    getSite(parameterIndex).setRef(x);
+  }
+
+  public void setBlob(int parameterIndex, Blob x) throws SQLException {
+    getSite(parameterIndex).setBlob(x);
+  }
+
+  public void setClob(int parameterIndex, Clob x) throws SQLException {
+    getSite(parameterIndex).setClob(x);
+  }
+
+  public void setArray(int parameterIndex, Array x) throws SQLException {
+    getSite(parameterIndex).setArray(x);
+  }
+
+  public ResultSetMetaData getMetaData() {
+    return resultSetMetaData;
+  }
+
+  public void setDate(int parameterIndex, Date x, Calendar calendar)
+      throws SQLException {
+    getSite(parameterIndex).setDate(x, calendar);
+  }
+
+  public void setDate(int parameterIndex, Date x) throws SQLException {
+    setDate(parameterIndex, x, getCalendar());
+  }
+
+  public void setTime(int parameterIndex, Time x, Calendar calendar)
+      throws SQLException {
+    getSite(parameterIndex).setTime(x, calendar);
+  }
+
+  public void setTime(int parameterIndex, Time x) throws SQLException {
+    setTime(parameterIndex, x, getCalendar());
+  }
+
+  public void setTimestamp(int parameterIndex, Timestamp x, Calendar calendar)
+      throws SQLException {
+    getSite(parameterIndex).setTimestamp(x, calendar);
+  }
+
+  public void setTimestamp(int parameterIndex, Timestamp x)
+      throws SQLException {
+    setTimestamp(parameterIndex, x, getCalendar());
+  }
+
+  public void setNull(int parameterIndex, int sqlType, String typeName)
+      throws SQLException {
+    getSite(parameterIndex).setNull(sqlType, typeName);
+  }
+
+  public void setURL(int parameterIndex, URL x) throws SQLException {
+    getSite(parameterIndex).setURL(x);
+  }
+
+  public void setObject(int parameterIndex, Object x, int targetSqlType,
+      int scaleOrLength) throws SQLException {
+    getSite(parameterIndex).setObject(x, targetSqlType, scaleOrLength);
+  }
+
+  // implement ParameterMetaData
+
+  protected AvaticaParameter getParameter(int param) throws SQLException {
+    try {
+      return getSignature().parameters.get(param - 1);
+    } catch (IndexOutOfBoundsException e) {
+      //noinspection ThrowableResultOfMethodCallIgnored
+      throw connection.helper.toSQLException(
+          connection.helper.createException(
+              "parameter ordinal " + param + " out of range"));
+    }
+  }
+
+  protected AvaticaSite getSite(int param) throws SQLException {
+    final AvaticaParameter parameter = getParameter(param);
+    return new AvaticaSite(parameter, getCalendar(), param - 1, slots);
+  }
+
+  public int getParameterCount() {
+    return getSignature().parameters.size();
+  }
+
+  public int isNullable(int param) throws SQLException {
+    return ParameterMetaData.parameterNullableUnknown;
+  }
+
+  public boolean isSigned(int index) throws SQLException {
+    return getParameter(index).signed;
+  }
+
+  public int getPrecision(int index) throws SQLException {
+    return getParameter(index).precision;
+  }
+
+  public int getScale(int index) throws SQLException {
+    return getParameter(index).scale;
+  }
+
+  public int getParameterType(int index) throws SQLException {
+    return getParameter(index).parameterType;
+  }
+
+  public String getParameterTypeName(int index) throws SQLException {
+    return getParameter(index).typeName;
+  }
+
+  public String getParameterClassName(int index) throws SQLException {
+    return getParameter(index).className;
+  }
+
+  public int getParameterMode(int param) throws SQLException {
+    //noinspection UnusedDeclaration
+    AvaticaParameter paramDef = getParameter(param); // forces param range 
check
+    return ParameterMetaData.parameterModeIn;
+  }
+}
+
+// End AvaticaPreparedStatement.java

Reply via email to