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/04/03 15:02:36 UTC
[03/10] incubator-calcite git commit: [CALCITE-652] Move server
pieces of avatica into avatica-server (Nick Dimiduk)
[CALCITE-652] Move server pieces of avatica into avatica-server (Nick Dimiduk)
Close apache/incubator-calcite#69
Project: http://git-wip-us.apache.org/repos/asf/incubator-calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-calcite/commit/fa9bdc4a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-calcite/tree/fa9bdc4a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-calcite/diff/fa9bdc4a
Branch: refs/heads/master
Commit: fa9bdc4ac8b7d0106ebf5fa875cd9edc00d47865
Parents: 17d7a8e
Author: Nick Dimiduk <nd...@gmail.com>
Authored: Tue Mar 31 16:04:43 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Apr 3 01:09:29 2015 -0700
----------------------------------------------------------------------
avatica-server/pom.xml | 47 +-
.../apache/calcite/avatica/jdbc/JdbcMeta.java | 765 +++++++++++++++++++
.../calcite/avatica/jdbc/JdbcResultSet.java | 106 +++
.../calcite/avatica/jdbc/package-info.java | 22 +
.../calcite/avatica/RemoteDriverTest.java | 431 +++++++++++
.../calcite/avatica/test/AvaticaSuite.java | 36 +
avatica/pom.xml | 35 +-
.../apache/calcite/avatica/jdbc/JdbcMeta.java | 765 -------------------
.../calcite/avatica/jdbc/JdbcResultSet.java | 106 ---
.../calcite/avatica/jdbc/package-info.java | 22 -
.../calcite/avatica/test/AvaticaSuite.java | 33 -
.../calcite/avatica/test/RemoteDriverTest.java | 435 -----------
pom.xml | 41 +-
13 files changed, 1461 insertions(+), 1383 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/fa9bdc4a/avatica-server/pom.xml
----------------------------------------------------------------------
diff --git a/avatica-server/pom.xml b/avatica-server/pom.xml
index 4b35978..2da32ec 100644
--- a/avatica-server/pom.xml
+++ b/avatica-server/pom.xml
@@ -40,11 +40,56 @@ limitations under the License.
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-avatica</artifactId>
</dependency>
-
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ </dependency>
+
+ <!-- test dependencies -->
+ <dependency>
+ <groupId>org.apache.calcite</groupId>
+ <artifactId>calcite-avatica</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <!-- Used in RemoteDriverTest but dependency not detected by maven-dependency-plugin:2.8:analyze -->
+ <groupId>net.hydromatic</groupId>
+ <artifactId>scott-data-hsqldb</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <!-- Used in RemoteDriverTest but dependency not detected by maven-dependency-plugin:2.8:analyze -->
+ <groupId>org.hsqldb</groupId>
+ <artifactId>hsqldb</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/fa9bdc4a/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java b/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
new file mode 100644
index 0000000..90e8c2a
--- /dev/null
+++ b/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
@@ -0,0 +1,765 @@
+/*
+ * 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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.RemovalListener;
+import com.google.common.cache.RemovalNotification;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.DriverManager;
+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;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/** Implementation of {@link Meta} upon an existing JDBC data source. */
+public class JdbcMeta implements Meta {
+
+ private static final Log LOG = LogFactory.getLog(JdbcMeta.class);
+
+ /**
+ * 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);
+ }
+
+ /** A "magic column" descriptor used to return statement execute update count via a
+ * {@link Meta.MetaResultSet}. */
+ private static final ColumnMetaData UPDATE_COL = new ColumnMetaData(1, false, false, false,
+ false, ResultSetMetaData.columnNoNulls, true, 10, "u", "u", null, 15, 15, "update_result",
+ "avatica_internal", ColumnMetaData.scalar(Types.INTEGER, "INTEGER",
+ ColumnMetaData.Rep.INTEGER), true, false, false, "java.lang.Integer");
+
+ private static final String CONN_CACHE_KEY_BASE = "avatica.connectioncache";
+
+ /** Configurable connection cache settings. */
+ public enum ConnectionCacheSettings {
+ /** JDBC connection property for setting connection cache concurrency level. */
+ CONCURRENCY_LEVEL(CONN_CACHE_KEY_BASE + ".concurrency", "10"),
+
+ /** JDBC connection property for setting connection cache initial capacity. */
+ INITIAL_CAPACITY(CONN_CACHE_KEY_BASE + ".initialcapacity", "100"),
+
+ /** JDBC connection property for setting connection cache maximum capacity. */
+ MAX_CAPACITY(CONN_CACHE_KEY_BASE + ".maxcapacity", "1000"),
+
+ /** JDBC connection property for setting connection cache expiration duration. */
+ EXPIRY_DURATION(CONN_CACHE_KEY_BASE + ".expirydiration", "10"),
+
+ /** JDBC connection property for setting connection cache expiration unit. */
+ EXPIRY_UNIT(CONN_CACHE_KEY_BASE + ".expiryunit", TimeUnit.MINUTES.name());
+
+ private final String key;
+ private final String defaultValue;
+
+ private ConnectionCacheSettings(String key, String defaultValue) {
+ this.key = key;
+ this.defaultValue = defaultValue;
+ }
+
+ /** The configuration key for specifying this setting. */
+ public String key() {
+ return key;
+ }
+
+ /** The default value for this setting. */
+ public String defaultValue() {
+ return defaultValue;
+ }
+ }
+
+ private static final String STMT_CACHE_KEY_BASE = "avatica.statementcache";
+
+ /** Configurable statement cache settings. */
+ public enum StatementCacheSettings {
+ /** JDBC connection property for setting connection cache concurrency level. */
+ CONCURRENCY_LEVEL(STMT_CACHE_KEY_BASE + ".concurrency", "100"),
+
+ /** JDBC connection property for setting connection cache initial capacity. */
+ INITIAL_CAPACITY(STMT_CACHE_KEY_BASE + ".initialcapacity", "1000"),
+
+ /** JDBC connection property for setting connection cache maximum capacity. */
+ MAX_CAPACITY(STMT_CACHE_KEY_BASE + ".maxcapacity", "10000"),
+
+ /** JDBC connection property for setting connection cache expiration duration.
+ *
+ * <p>Used in conjunction with {@link #EXPIRY_UNIT}.</p>
+ */
+ EXPIRY_DURATION(STMT_CACHE_KEY_BASE + ".expirydiration", "5"),
+
+ /** JDBC connection property for setting connection cache expiration unit.
+ *
+ * <p>Used in conjunction with {@link #EXPIRY_DURATION}.</p>
+ */
+ EXPIRY_UNIT(STMT_CACHE_KEY_BASE + ".expiryunit", TimeUnit.MINUTES.name());
+
+ private final String key;
+ private final String defaultValue;
+
+ private StatementCacheSettings(String key, String defaultValue) {
+ this.key = key;
+ this.defaultValue = defaultValue;
+ }
+
+ /** The configuration key for specifying this setting. */
+ public String key() {
+ return key;
+ }
+
+ /** The default value for this setting. */
+ public String defaultValue() {
+ return defaultValue;
+ }
+ }
+
+ private static final String DEFAULT_CONN_ID =
+ UUID.fromString("00000000-0000-0000-0000-000000000000").toString();
+
+ private final String url;
+ private final Properties info;
+ private final Connection connection; // TODO: remove default connection
+ private final Cache<String, Connection> connectionCache;
+ private final Cache<Integer, StatementInfo> statementCache;
+
+ /**
+ * 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.LIST /* LIST because JdbcResultSet#frame */);
+ }
+
+ protected static Signature signature(ResultSetMetaData metaData)
+ throws SQLException {
+ return signature(metaData, null, null);
+ }
+
+ /** Callback for {@link #connectionCache} member expiration. */
+ private class ConnectionExpiryHandler
+ implements RemovalListener<String, Connection> {
+
+ public void onRemoval(RemovalNotification<String, Connection> notification) {
+ String connectionId = notification.getKey();
+ Connection doomed = notification.getValue();
+ // is String.equals() more efficient?
+ if (notification.getValue() == connection) {
+ return;
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Expiring connection " + connectionId + " because "
+ + notification.getCause());
+ }
+ try {
+ if (doomed != null) {
+ doomed.close();
+ }
+ } catch (Throwable t) {
+ LOG.info("Exception thrown while expiring connection " + connectionId, t);
+ }
+ }
+ }
+
+ /** Callback for {@link #statementCache} member expiration. */
+ private class StatementExpiryHandler
+ implements RemovalListener<Integer, StatementInfo> {
+ public void onRemoval(RemovalNotification<Integer, StatementInfo> notification) {
+ Integer stmtId = notification.getKey();
+ StatementInfo doomed = notification.getValue();
+ if (doomed == null) {
+ // log/throw?
+ return;
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Expiring statement " + stmtId + " because "
+ + notification.getCause());
+ }
+ try {
+ if (doomed.resultSet != null) {
+ doomed.resultSet.close();
+ }
+ if (doomed.statement != null) {
+ doomed.statement.close();
+ }
+ } catch (Throwable t) {
+ LOG.info("Exception thrown while expiring statement " + stmtId);
+ }
+ }
+ }
+
+ /**
+ * @param url a database url of the form
+ * <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
+ */
+ public JdbcMeta(String url) throws SQLException {
+ this(url, new Properties());
+ }
+
+ /**
+ * @param url a database url of the form
+ * <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>
+ * @param user the database user on whose behalf the connection is being
+ * made
+ * @param password the user's password
+ */
+ public JdbcMeta(final String url, final String user, final String password)
+ throws SQLException {
+ this(url, new Properties() {
+ {
+ put("user", user);
+ put("password", password);
+ }
+ });
+ }
+
+ /**
+ * @param url a database url of the form
+ * <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
+ * @param info a list of arbitrary string tag/value pairs as
+ * connection arguments; normally at least a "user" and
+ * "password" property should be included
+ */
+ public JdbcMeta(String url, Properties info) throws SQLException {
+ this.url = url;
+ this.info = info;
+ this.connection = DriverManager.getConnection(url, info);
+
+ int concurrencyLevel = Integer.parseInt(
+ info.getProperty(ConnectionCacheSettings.CONCURRENCY_LEVEL.key(),
+ ConnectionCacheSettings.CONCURRENCY_LEVEL.defaultValue()));
+ int initialCapacity = Integer.parseInt(
+ info.getProperty(ConnectionCacheSettings.INITIAL_CAPACITY.key(),
+ ConnectionCacheSettings.INITIAL_CAPACITY.defaultValue()));
+ long maxCapacity = Long.parseLong(
+ info.getProperty(ConnectionCacheSettings.MAX_CAPACITY.key(),
+ ConnectionCacheSettings.MAX_CAPACITY.defaultValue()));
+ long connectionExpiryDuration = Long.parseLong(
+ info.getProperty(ConnectionCacheSettings.EXPIRY_DURATION.key(),
+ ConnectionCacheSettings.EXPIRY_DURATION.defaultValue()));
+ TimeUnit connectionExpiryUnit = TimeUnit.valueOf(
+ info.getProperty(ConnectionCacheSettings.EXPIRY_UNIT.key(),
+ ConnectionCacheSettings.EXPIRY_UNIT.defaultValue()));
+ this.connectionCache = CacheBuilder.newBuilder()
+ .concurrencyLevel(concurrencyLevel)
+ .initialCapacity(initialCapacity)
+ .maximumSize(maxCapacity)
+ .expireAfterAccess(connectionExpiryDuration, connectionExpiryUnit)
+ .removalListener(new ConnectionExpiryHandler())
+ .build();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("instantiated connection cache: " + connectionCache.stats());
+ }
+
+ concurrencyLevel = Integer.parseInt(
+ info.getProperty(StatementCacheSettings.CONCURRENCY_LEVEL.key(),
+ StatementCacheSettings.CONCURRENCY_LEVEL.defaultValue()));
+ initialCapacity = Integer.parseInt(
+ info.getProperty(StatementCacheSettings.INITIAL_CAPACITY.key(),
+ StatementCacheSettings.INITIAL_CAPACITY.defaultValue()));
+ maxCapacity = Long.parseLong(
+ info.getProperty(StatementCacheSettings.MAX_CAPACITY.key(),
+ StatementCacheSettings.MAX_CAPACITY.defaultValue()));
+ connectionExpiryDuration = Long.parseLong(
+ info.getProperty(StatementCacheSettings.EXPIRY_DURATION.key(),
+ StatementCacheSettings.EXPIRY_DURATION.defaultValue()));
+ connectionExpiryUnit = TimeUnit.valueOf(
+ info.getProperty(StatementCacheSettings.EXPIRY_UNIT.key(),
+ StatementCacheSettings.EXPIRY_UNIT.defaultValue()));
+ this.statementCache = CacheBuilder.newBuilder()
+ .concurrencyLevel(concurrencyLevel)
+ .initialCapacity(initialCapacity)
+ .maximumSize(maxCapacity)
+ .expireAfterAccess(connectionExpiryDuration, connectionExpiryUnit)
+ .removalListener(new StatementExpiryHandler())
+ .build();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("instantiated statement cache: " + statementCache.stats());
+ }
+ }
+
+ 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(DEFAULT_CONN_ID, -1,
+ 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(DEFAULT_CONN_ID, -1,
+ 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(DEFAULT_CONN_ID, -1,
+ connection.getMetaData().getSchemas(catalog, schemaPattern.s));
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public MetaResultSet getCatalogs() {
+ try {
+ return JdbcResultSet.create(DEFAULT_CONN_ID, -1,
+ connection.getMetaData().getCatalogs());
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public MetaResultSet getTableTypes() {
+ try {
+ return JdbcResultSet.create(DEFAULT_CONN_ID, -1,
+ connection.getMetaData().getTableTypes());
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public MetaResultSet getProcedures(String catalog, Pat schemaPattern,
+ Pat procedureNamePattern) {
+ try {
+ return JdbcResultSet.create(DEFAULT_CONN_ID, -1,
+ 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(DEFAULT_CONN_ID, -1,
+ 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(DEFAULT_CONN_ID, -1,
+ 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(DEFAULT_CONN_ID, -1,
+ 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(DEFAULT_CONN_ID, -1,
+ 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(DEFAULT_CONN_ID, -1,
+ 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(DEFAULT_CONN_ID, -1,
+ 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;
+ }
+
+ protected Connection getConnection(String id) throws SQLException {
+ Connection conn = connectionCache.getIfPresent(id);
+ if (conn == null) {
+ conn = DriverManager.getConnection(url, info);
+ connectionCache.put(id, conn);
+ }
+ return conn;
+ }
+
+ public StatementHandle createStatement(ConnectionHandle ch) {
+ try {
+ final Connection conn = getConnection(ch.id);
+ final Statement statement = conn.createStatement();
+ final int id = System.identityHashCode(statement);
+ statementCache.put(id, new StatementInfo(statement));
+ StatementHandle h = new StatementHandle(ch.id, id, null);
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("created statement " + h);
+ }
+ return h;
+ } catch (SQLException e) {
+ throw propagate(e);
+ }
+ }
+
+ @Override public void closeStatement(StatementHandle h) {
+ StatementInfo info = statementCache.getIfPresent(h.id);
+ if (info == null || info.statement == null) {
+ LOG.debug("client requested close unknown statement " + h);
+ return;
+ }
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("closing statement " + h);
+ }
+ try {
+ if (info.resultSet != null) {
+ info.resultSet.close();
+ }
+ info.statement.close();
+ } catch (SQLException e) {
+ throw propagate(e);
+ } finally {
+ statementCache.invalidate(h.id);
+ }
+ }
+
+ @Override public void closeConnection(ConnectionHandle ch) {
+ Connection conn = connectionCache.getIfPresent(ch.id);
+ if (conn == null) {
+ LOG.debug("client requested close unknown connection " + ch);
+ return;
+ }
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("closing connection " + ch);
+ }
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ throw propagate(e);
+ } finally {
+ connectionCache.invalidate(ch.id);
+ }
+ }
+
+ 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 StatementHandle prepare(ConnectionHandle ch, String sql,
+ int maxRowCount) {
+ try {
+ final Connection conn = getConnection(ch.id);
+ final PreparedStatement statement = conn.prepareStatement(sql);
+ final int id = System.identityHashCode(statement);
+ statementCache.put(id, new StatementInfo(statement));
+ StatementHandle h = new StatementHandle(ch.id, id,
+ signature(statement.getMetaData(), statement.getParameterMetaData(),
+ sql));
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("prepared statement " + h);
+ }
+ return h;
+ } catch (SQLException e) {
+ throw propagate(e);
+ }
+ }
+
+ public MetaResultSet prepareAndExecute(ConnectionHandle ch, String sql,
+ int maxRowCount, PrepareCallback callback) {
+ try {
+ final Connection connection = getConnection(ch.id);
+ final PreparedStatement statement = connection.prepareStatement(sql);
+ final int id = System.identityHashCode(statement);
+ final StatementInfo info = new StatementInfo(statement);
+ statementCache.put(id, info);
+ info.resultSet = statement.executeQuery();
+ MetaResultSet mrs = JdbcResultSet.create(ch.id, id, info.resultSet);
+ if (LOG.isTraceEnabled()) {
+ StatementHandle h = new StatementHandle(ch.id, id, null);
+ LOG.trace("prepAndExec statement " + h);
+ }
+ return mrs;
+ } catch (SQLException e) {
+ throw propagate(e);
+ }
+ }
+
+ public Frame fetch(StatementHandle h, List<Object> parameterValues,
+ int offset, int fetchMaxRowCount) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("fetching " + h + " offset:" + offset + " fetchMaxRowCount:" + fetchMaxRowCount);
+ }
+ try {
+ final StatementInfo statementInfo = Objects.requireNonNull(
+ statementCache.getIfPresent(h.id),
+ "Statement not found, potentially expired. " + h);
+ 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/fa9bdc4a/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcResultSet.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcResultSet.java b/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcResultSet.java
new file mode 100644
index 0000000..827f31d
--- /dev/null
+++ b/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcResultSet.java
@@ -0,0 +1,106 @@
+/*
+ * 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(String connectionId, int statementId,
+ boolean ownStatement, Meta.Signature signature, Meta.Frame firstFrame) {
+ super(connectionId, statementId, ownStatement, signature, firstFrame);
+ }
+
+ /** Creates a result set. */
+ public static JdbcResultSet create(String connectionId, int statementId,
+ ResultSet resultSet) {
+ try {
+ Meta.Signature sig = JdbcMeta.signature(resultSet.getMetaData());
+ final Meta.Frame firstFrame = frame(resultSet, 0, -1);
+ resultSet.close();
+ return new JdbcResultSet(connectionId, statementId, 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/fa9bdc4a/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/package-info.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/package-info.java b/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/package-info.java
new file mode 100644
index 0000000..8b8fb76
--- /dev/null
+++ b/avatica-server/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/fa9bdc4a/avatica-server/src/test/java/org/apache/calcite/avatica/RemoteDriverTest.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/test/java/org/apache/calcite/avatica/RemoteDriverTest.java b/avatica-server/src/test/java/org/apache/calcite/avatica/RemoteDriverTest.java
new file mode 100644
index 0000000..a2ab87a
--- /dev/null
+++ b/avatica-server/src/test/java/org/apache/calcite/avatica/RemoteDriverTest.java
@@ -0,0 +1,431 @@
+/*
+ * 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.jdbc.JdbcMeta;
+import org.apache.calcite.avatica.remote.LocalJsonService;
+import org.apache.calcite.avatica.remote.LocalService;
+import org.apache.calcite.avatica.remote.MockJsonService;
+import org.apache.calcite.avatica.remote.Service;
+
+import com.google.common.cache.Cache;
+
+import net.hydromatic.scott.data.hsqldb.ScottHsqldb;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.sql.Connection;
+import java.sql.DriverManager;
+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.util.Map;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Unit test for Avatica Remote JDBC driver.
+ */
+public class RemoteDriverTest {
+ public static final String MJS =
+ MockJsonService.Factory.class.getName();
+
+ public static final String LJS =
+ LocalJdbcServiceFactory.class.getName();
+
+ public static final String QRJS =
+ QuasiRemoteJdbcServiceFactory.class.getName();
+
+ private static final ConnectionSpec CONNECTION_SPEC = ConnectionSpec.HSQLDB;
+
+ private Connection mjs() throws SQLException {
+ return DriverManager.getConnection("jdbc:avatica:remote:factory=" + MJS);
+ }
+
+ private Connection ljs() throws SQLException {
+ return DriverManager.getConnection("jdbc:avatica:remote:factory=" + QRJS);
+ }
+
+ @Before
+ public void before() throws Exception {
+ QuasiRemoteJdbcServiceFactory.initService();
+ }
+
+ @Test public void testRegister() throws Exception {
+ final Connection connection =
+ DriverManager.getConnection("jdbc:avatica:remote:");
+ assertThat(connection.isClosed(), is(false));
+ connection.close();
+ assertThat(connection.isClosed(), is(true));
+ }
+
+ @Test public void testSchemas() throws Exception {
+ final Connection connection = mjs();
+ final ResultSet resultSet =
+ connection.getMetaData().getSchemas(null, null);
+ assertFalse(resultSet.next());
+ final ResultSetMetaData metaData = resultSet.getMetaData();
+ assertTrue(metaData.getColumnCount() >= 2);
+ assertEquals("TABLE_CATALOG", metaData.getColumnName(1));
+ assertEquals("TABLE_SCHEM", metaData.getColumnName(2));
+ resultSet.close();
+ connection.close();
+ }
+
+ @Test public void testTables() throws Exception {
+ final Connection connection = mjs();
+ final ResultSet resultSet =
+ connection.getMetaData().getTables(null, null, null, new String[0]);
+ assertFalse(resultSet.next());
+ final ResultSetMetaData metaData = resultSet.getMetaData();
+ assertTrue(metaData.getColumnCount() >= 3);
+ assertEquals("TABLE_CAT", metaData.getColumnName(1));
+ assertEquals("TABLE_SCHEM", metaData.getColumnName(2));
+ assertEquals("TABLE_NAME", metaData.getColumnName(3));
+ resultSet.close();
+ connection.close();
+ }
+
+ @Ignore
+ @Test public void testNoFactory() throws Exception {
+ final Connection connection =
+ DriverManager.getConnection("jdbc:avatica:remote:");
+ assertThat(connection.isClosed(), is(false));
+ final ResultSet resultSet = connection.getMetaData().getSchemas();
+ assertFalse(resultSet.next());
+ final ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(2, metaData.getColumnCount());
+ assertEquals("TABLE_SCHEM", metaData.getColumnName(1));
+ assertEquals("TABLE_CATALOG", metaData.getColumnName(2));
+ resultSet.close();
+ connection.close();
+ assertThat(connection.isClosed(), is(true));
+ }
+
+ @Ignore
+ @Test public void testCatalogsMock() throws Exception {
+ final Connection connection = mjs();
+ assertThat(connection.isClosed(), is(false));
+ final ResultSet resultSet = connection.getMetaData().getSchemas();
+ assertFalse(resultSet.next());
+ final ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(2, metaData.getColumnCount());
+ assertEquals("TABLE_SCHEM", metaData.getColumnName(1));
+ assertEquals("TABLE_CATALOG", metaData.getColumnName(2));
+ resultSet.close();
+ connection.close();
+ assertThat(connection.isClosed(), is(true));
+ }
+
+ @Test public void testStatementExecuteQueryLocal() throws Exception {
+ checkStatementExecuteQuery(ljs(), false);
+ }
+
+ @Ignore
+ @Test public void testStatementExecuteQueryMock() throws Exception {
+ checkStatementExecuteQuery(mjs(), false);
+ }
+
+ @Ignore
+ @Test public void testPrepareExecuteQueryLocal() throws Exception {
+ checkStatementExecuteQuery(ljs(), true);
+ }
+
+ @Ignore
+ @Test public void testPrepareExecuteQueryMock() throws Exception {
+ checkStatementExecuteQuery(mjs(), true);
+ }
+
+ private void checkStatementExecuteQuery(Connection connection,
+ boolean prepare) throws SQLException {
+ final String sql = "select * from (\n"
+ + " values (1, 'a'), (null, 'b'), (3, 'c')) as t (c1, c2)";
+ final Statement statement;
+ final ResultSet resultSet;
+ final ParameterMetaData parameterMetaData;
+ if (prepare) {
+ final PreparedStatement ps = connection.prepareStatement(sql);
+ statement = ps;
+ parameterMetaData = ps.getParameterMetaData();
+ resultSet = ps.executeQuery();
+ } else {
+ statement = connection.createStatement();
+ parameterMetaData = null;
+ resultSet = statement.executeQuery(sql);
+ }
+ if (parameterMetaData != null) {
+ assertThat(parameterMetaData.getParameterCount(), equalTo(2));
+ }
+ final ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(2, metaData.getColumnCount());
+ assertEquals("C1", metaData.getColumnName(1));
+ assertEquals("C2", metaData.getColumnName(2));
+ assertTrue(resultSet.next());
+ assertTrue(resultSet.next());
+ assertTrue(resultSet.next());
+ assertFalse(resultSet.next());
+ resultSet.close();
+ statement.close();
+ connection.close();
+ }
+
+ @Test public void testStatementLifecycle() throws Exception {
+ try (AvaticaConnection connection = (AvaticaConnection) ljs()) {
+ Map<Integer, AvaticaStatement> clientMap = connection.statementMap;
+ Cache<Integer, Object> serverMap =
+ QuasiRemoteJdbcServiceFactory.getRemoteStatementMap(connection);
+ assertEquals(0, clientMap.size());
+ assertEquals(0, serverMap.size());
+ Statement stmt = connection.createStatement();
+ assertEquals(1, clientMap.size());
+ assertEquals(1, serverMap.size());
+ stmt.close();
+ assertEquals(0, clientMap.size());
+ assertEquals(0, serverMap.size());
+ }
+ }
+
+ @Test public void testConnectionIsolation() throws Exception {
+ final String sql = "select * from (values (1, 'a'))";
+ Connection conn1 = ljs();
+ Connection conn2 = ljs();
+ Cache<String, Connection> connectionMap =
+ QuasiRemoteJdbcServiceFactory.getRemoteConnectionMap(
+ (AvaticaConnection) conn1);
+ assertEquals("connection cache should start empty",
+ 0, connectionMap.size());
+ PreparedStatement conn1stmt1 = conn1.prepareStatement(sql);
+ assertEquals(
+ "statement creation implicitly creates a connection server-side",
+ 1, connectionMap.size());
+ PreparedStatement conn2stmt1 = conn2.prepareStatement(sql);
+ assertEquals(
+ "statement creation implicitly creates a connection server-side",
+ 2, connectionMap.size());
+ AvaticaPreparedStatement s1 = (AvaticaPreparedStatement) conn1stmt1;
+ AvaticaPreparedStatement s2 = (AvaticaPreparedStatement) conn2stmt1;
+ assertFalse("connection id's should be unique",
+ s1.handle.connectionId.equalsIgnoreCase(s2.handle.connectionId));
+ conn2.close();
+ assertEquals("closing a connection closes the server-side connection",
+ 1, connectionMap.size());
+ conn1.close();
+ assertEquals("closing a connection closes the server-side connection",
+ 0, connectionMap.size());
+ }
+
+ private void checkStatementExecuteQuery(Connection connection)
+ throws SQLException {
+ final Statement statement = connection.createStatement();
+ final ResultSet resultSet =
+ statement.executeQuery("select * from (\n"
+ + " values (1, 'a'), (null, 'b'), (3, 'c')) as t (c1, c2)");
+ final ResultSetMetaData metaData = resultSet.getMetaData();
+ assertEquals(2, metaData.getColumnCount());
+ assertEquals("C1", metaData.getColumnName(1));
+ assertEquals("C2", metaData.getColumnName(2));
+ assertTrue(resultSet.next());
+ assertTrue(resultSet.next());
+ assertTrue(resultSet.next());
+ assertFalse(resultSet.next());
+ resultSet.close();
+ statement.close();
+ connection.close();
+ }
+
+ @Test public void testPrepareBindExecuteFetch() throws Exception {
+ checkPrepareBindExecuteFetch(ljs());
+ }
+
+ private void checkPrepareBindExecuteFetch(Connection connection)
+ throws SQLException {
+ final String sql = "select cast(? as integer) * 3 as c, 'x' as x\n"
+ + "from (values (1, 'a'))";
+ final PreparedStatement ps =
+ connection.prepareStatement(sql);
+ final ResultSetMetaData metaData = ps.getMetaData();
+ assertEquals(2, metaData.getColumnCount());
+ assertEquals("C", metaData.getColumnName(1));
+ assertEquals("X", metaData.getColumnName(2));
+ try {
+ final ResultSet resultSet = ps.executeQuery();
+ fail("expected error, got " + resultSet);
+ } catch (SQLException e) {
+ assertThat(e.getMessage(),
+ equalTo("exception while executing query: unbound parameter"));
+ }
+
+ final ParameterMetaData parameterMetaData = ps.getParameterMetaData();
+ assertThat(parameterMetaData.getParameterCount(), equalTo(1));
+
+ ps.setInt(1, 10);
+ final ResultSet resultSet = ps.executeQuery();
+ assertTrue(resultSet.next());
+ assertThat(resultSet.getInt(1), equalTo(30));
+ assertFalse(resultSet.next());
+ resultSet.close();
+
+ ps.setInt(1, 20);
+ final ResultSet resultSet2 = ps.executeQuery();
+ assertFalse(resultSet2.isClosed());
+ assertTrue(resultSet2.next());
+ assertThat(resultSet2.getInt(1), equalTo(60));
+ assertThat(resultSet2.wasNull(), is(false));
+ assertFalse(resultSet2.next());
+ resultSet2.close();
+
+ ps.setObject(1, null);
+ final ResultSet resultSet3 = ps.executeQuery();
+ assertTrue(resultSet3.next());
+ assertThat(resultSet3.getInt(1), equalTo(0));
+ assertThat(resultSet3.wasNull(), is(true));
+ assertFalse(resultSet3.next());
+ resultSet3.close();
+
+ ps.close();
+ connection.close();
+ }
+
+ /**
+ * Factory that creates a service based on a local JDBC connection.
+ */
+ public static class LocalJdbcServiceFactory implements Service.Factory {
+ @Override public Service create(AvaticaConnection connection) {
+ try {
+ return new LocalService(
+ new JdbcMeta(CONNECTION_SPEC.url, CONNECTION_SPEC.username,
+ CONNECTION_SPEC.password));
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**
+ * Factory that creates a service based on a local JDBC connection.
+ */
+ public static class QuasiRemoteJdbcServiceFactory implements Service.Factory {
+
+ /** a singleton instance that is recreated for each test */
+ private static Service service;
+
+ static void initService() {
+ try {
+ final JdbcMeta jdbcMeta = new JdbcMeta(CONNECTION_SPEC.url,
+ CONNECTION_SPEC.username, CONNECTION_SPEC.password);
+ final LocalService localService = new LocalService(jdbcMeta);
+ service = new LocalJsonService(localService);
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override public Service create(AvaticaConnection connection) {
+ assert service != null;
+ return service;
+ }
+
+ /**
+ * Reach into the guts of a quasi-remote connection and pull out the
+ * statement map from the other side.
+ * TODO: refactor tests to replace reflection with package-local access
+ */
+ static Cache<Integer, Object>
+ getRemoteStatementMap(AvaticaConnection connection) throws Exception {
+ Field metaF = AvaticaConnection.class.getDeclaredField("meta");
+ metaF.setAccessible(true);
+ Meta clientMeta = (Meta) metaF.get(connection);
+ Field remoteMetaServiceF = clientMeta.getClass().getDeclaredField("service");
+ remoteMetaServiceF.setAccessible(true);
+ LocalJsonService remoteMetaService = (LocalJsonService) remoteMetaServiceF.get(clientMeta);
+ Field remoteMetaServiceServiceF = remoteMetaService.getClass().getDeclaredField("service");
+ remoteMetaServiceServiceF.setAccessible(true);
+ LocalService remoteMetaServiceService =
+ (LocalService) remoteMetaServiceServiceF.get(remoteMetaService);
+ Field remoteMetaServiceServiceMetaF =
+ remoteMetaServiceService.getClass().getDeclaredField("meta");
+ remoteMetaServiceServiceMetaF.setAccessible(true);
+ JdbcMeta serverMeta = (JdbcMeta) remoteMetaServiceServiceMetaF.get(remoteMetaServiceService);
+ Field jdbcMetaStatementMapF = JdbcMeta.class.getDeclaredField("statementCache");
+ jdbcMetaStatementMapF.setAccessible(true);
+ //noinspection unchecked
+ return (Cache<Integer, Object>) jdbcMetaStatementMapF.get(serverMeta);
+ }
+
+ /**
+ * Reach into the guts of a quasi-remote connection and pull out the
+ * connection map from the other side.
+ * TODO: refactor tests to replace reflection with package-local access
+ */
+ static Cache<String, Connection>
+ getRemoteConnectionMap(AvaticaConnection connection) throws Exception {
+ Field metaF = AvaticaConnection.class.getDeclaredField("meta");
+ metaF.setAccessible(true);
+ Meta clientMeta = (Meta) metaF.get(connection);
+ Field remoteMetaServiceF = clientMeta.getClass().getDeclaredField("service");
+ remoteMetaServiceF.setAccessible(true);
+ LocalJsonService remoteMetaService = (LocalJsonService) remoteMetaServiceF.get(clientMeta);
+ Field remoteMetaServiceServiceF = remoteMetaService.getClass().getDeclaredField("service");
+ remoteMetaServiceServiceF.setAccessible(true);
+ LocalService remoteMetaServiceService =
+ (LocalService) remoteMetaServiceServiceF.get(remoteMetaService);
+ Field remoteMetaServiceServiceMetaF =
+ remoteMetaServiceService.getClass().getDeclaredField("meta");
+ remoteMetaServiceServiceMetaF.setAccessible(true);
+ JdbcMeta serverMeta = (JdbcMeta) remoteMetaServiceServiceMetaF.get(remoteMetaServiceService);
+ Field jdbcMetaConnectionCacheF = JdbcMeta.class.getDeclaredField("connectionCache");
+ jdbcMetaConnectionCacheF.setAccessible(true);
+ //noinspection unchecked
+ return (Cache<String, Connection>) jdbcMetaConnectionCacheF.get(serverMeta);
+ }
+ }
+
+ /** Information necessary to create a JDBC connection. Specify one to run
+ * tests against a different database. (hsqldb is the default.) */
+ public static class ConnectionSpec {
+ public final String url;
+ public final String username;
+ public final String password;
+ public final String driver;
+
+ public ConnectionSpec(String url, String username, String password,
+ String driver) {
+ this.url = url;
+ this.username = username;
+ this.password = password;
+ this.driver = driver;
+ }
+
+ public static final ConnectionSpec HSQLDB =
+ new ConnectionSpec(ScottHsqldb.URI, ScottHsqldb.USER,
+ ScottHsqldb.PASSWORD, "org.hsqldb.jdbcDriver");
+ }
+}
+
+// End RemoteDriverTest.java
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/fa9bdc4a/avatica-server/src/test/java/org/apache/calcite/avatica/test/AvaticaSuite.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/test/java/org/apache/calcite/avatica/test/AvaticaSuite.java b/avatica-server/src/test/java/org/apache/calcite/avatica/test/AvaticaSuite.java
new file mode 100644
index 0000000..5afba20
--- /dev/null
+++ b/avatica-server/src/test/java/org/apache/calcite/avatica/test/AvaticaSuite.java
@@ -0,0 +1,36 @@
+/*
+ * 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.test;
+
+import org.apache.calcite.avatica.RemoteDriverTest;
+
+import org.junit.runner.RunWith;
+
+import org.junit.runners.Suite;
+
+/**
+ * Avatica test suite.
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ ConnectStringParserTest.class,
+ RemoteDriverTest.class
+})
+public class AvaticaSuite {
+}
+
+// End AvaticaSuite.java
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/fa9bdc4a/avatica/pom.xml
----------------------------------------------------------------------
diff --git a/avatica/pom.xml b/avatica/pom.xml
index 0bacc4a..01cd4e9 100644
--- a/avatica/pom.xml
+++ b/avatica/pom.xml
@@ -38,32 +38,21 @@ limitations under the License.
or on libraries other than Jackson. -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
+ <artifactId>jackson-core</artifactId>
</dependency>
<dependency>
- <groupId>com.google.guava</groupId>
- <artifactId>guava</artifactId>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<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>
@@ -94,6 +83,20 @@ limitations under the License.
</execution>
</executions>
</plugin>
+
+ <!-- Produce a tests jar so that avatica-server/pom.xml can reference for suite.
+ TODO: remove after moving over to annotation-based TestSuite definitions. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
</project>
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/fa9bdc4a/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
deleted file mode 100644
index 90e8c2a..0000000
--- a/avatica/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
+++ /dev/null
@@ -1,765 +0,0 @@
-/*
- * 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 org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.RemovalListener;
-import com.google.common.cache.RemovalNotification;
-
-import java.lang.reflect.Array;
-import java.lang.reflect.Type;
-import java.math.BigDecimal;
-import java.sql.Connection;
-import java.sql.DriverManager;
-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;
-import java.util.Properties;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-
-/** Implementation of {@link Meta} upon an existing JDBC data source. */
-public class JdbcMeta implements Meta {
-
- private static final Log LOG = LogFactory.getLog(JdbcMeta.class);
-
- /**
- * 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);
- }
-
- /** A "magic column" descriptor used to return statement execute update count via a
- * {@link Meta.MetaResultSet}. */
- private static final ColumnMetaData UPDATE_COL = new ColumnMetaData(1, false, false, false,
- false, ResultSetMetaData.columnNoNulls, true, 10, "u", "u", null, 15, 15, "update_result",
- "avatica_internal", ColumnMetaData.scalar(Types.INTEGER, "INTEGER",
- ColumnMetaData.Rep.INTEGER), true, false, false, "java.lang.Integer");
-
- private static final String CONN_CACHE_KEY_BASE = "avatica.connectioncache";
-
- /** Configurable connection cache settings. */
- public enum ConnectionCacheSettings {
- /** JDBC connection property for setting connection cache concurrency level. */
- CONCURRENCY_LEVEL(CONN_CACHE_KEY_BASE + ".concurrency", "10"),
-
- /** JDBC connection property for setting connection cache initial capacity. */
- INITIAL_CAPACITY(CONN_CACHE_KEY_BASE + ".initialcapacity", "100"),
-
- /** JDBC connection property for setting connection cache maximum capacity. */
- MAX_CAPACITY(CONN_CACHE_KEY_BASE + ".maxcapacity", "1000"),
-
- /** JDBC connection property for setting connection cache expiration duration. */
- EXPIRY_DURATION(CONN_CACHE_KEY_BASE + ".expirydiration", "10"),
-
- /** JDBC connection property for setting connection cache expiration unit. */
- EXPIRY_UNIT(CONN_CACHE_KEY_BASE + ".expiryunit", TimeUnit.MINUTES.name());
-
- private final String key;
- private final String defaultValue;
-
- private ConnectionCacheSettings(String key, String defaultValue) {
- this.key = key;
- this.defaultValue = defaultValue;
- }
-
- /** The configuration key for specifying this setting. */
- public String key() {
- return key;
- }
-
- /** The default value for this setting. */
- public String defaultValue() {
- return defaultValue;
- }
- }
-
- private static final String STMT_CACHE_KEY_BASE = "avatica.statementcache";
-
- /** Configurable statement cache settings. */
- public enum StatementCacheSettings {
- /** JDBC connection property for setting connection cache concurrency level. */
- CONCURRENCY_LEVEL(STMT_CACHE_KEY_BASE + ".concurrency", "100"),
-
- /** JDBC connection property for setting connection cache initial capacity. */
- INITIAL_CAPACITY(STMT_CACHE_KEY_BASE + ".initialcapacity", "1000"),
-
- /** JDBC connection property for setting connection cache maximum capacity. */
- MAX_CAPACITY(STMT_CACHE_KEY_BASE + ".maxcapacity", "10000"),
-
- /** JDBC connection property for setting connection cache expiration duration.
- *
- * <p>Used in conjunction with {@link #EXPIRY_UNIT}.</p>
- */
- EXPIRY_DURATION(STMT_CACHE_KEY_BASE + ".expirydiration", "5"),
-
- /** JDBC connection property for setting connection cache expiration unit.
- *
- * <p>Used in conjunction with {@link #EXPIRY_DURATION}.</p>
- */
- EXPIRY_UNIT(STMT_CACHE_KEY_BASE + ".expiryunit", TimeUnit.MINUTES.name());
-
- private final String key;
- private final String defaultValue;
-
- private StatementCacheSettings(String key, String defaultValue) {
- this.key = key;
- this.defaultValue = defaultValue;
- }
-
- /** The configuration key for specifying this setting. */
- public String key() {
- return key;
- }
-
- /** The default value for this setting. */
- public String defaultValue() {
- return defaultValue;
- }
- }
-
- private static final String DEFAULT_CONN_ID =
- UUID.fromString("00000000-0000-0000-0000-000000000000").toString();
-
- private final String url;
- private final Properties info;
- private final Connection connection; // TODO: remove default connection
- private final Cache<String, Connection> connectionCache;
- private final Cache<Integer, StatementInfo> statementCache;
-
- /**
- * 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.LIST /* LIST because JdbcResultSet#frame */);
- }
-
- protected static Signature signature(ResultSetMetaData metaData)
- throws SQLException {
- return signature(metaData, null, null);
- }
-
- /** Callback for {@link #connectionCache} member expiration. */
- private class ConnectionExpiryHandler
- implements RemovalListener<String, Connection> {
-
- public void onRemoval(RemovalNotification<String, Connection> notification) {
- String connectionId = notification.getKey();
- Connection doomed = notification.getValue();
- // is String.equals() more efficient?
- if (notification.getValue() == connection) {
- return;
- }
- if (LOG.isDebugEnabled()) {
- LOG.debug("Expiring connection " + connectionId + " because "
- + notification.getCause());
- }
- try {
- if (doomed != null) {
- doomed.close();
- }
- } catch (Throwable t) {
- LOG.info("Exception thrown while expiring connection " + connectionId, t);
- }
- }
- }
-
- /** Callback for {@link #statementCache} member expiration. */
- private class StatementExpiryHandler
- implements RemovalListener<Integer, StatementInfo> {
- public void onRemoval(RemovalNotification<Integer, StatementInfo> notification) {
- Integer stmtId = notification.getKey();
- StatementInfo doomed = notification.getValue();
- if (doomed == null) {
- // log/throw?
- return;
- }
- if (LOG.isDebugEnabled()) {
- LOG.debug("Expiring statement " + stmtId + " because "
- + notification.getCause());
- }
- try {
- if (doomed.resultSet != null) {
- doomed.resultSet.close();
- }
- if (doomed.statement != null) {
- doomed.statement.close();
- }
- } catch (Throwable t) {
- LOG.info("Exception thrown while expiring statement " + stmtId);
- }
- }
- }
-
- /**
- * @param url a database url of the form
- * <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
- */
- public JdbcMeta(String url) throws SQLException {
- this(url, new Properties());
- }
-
- /**
- * @param url a database url of the form
- * <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>
- * @param user the database user on whose behalf the connection is being
- * made
- * @param password the user's password
- */
- public JdbcMeta(final String url, final String user, final String password)
- throws SQLException {
- this(url, new Properties() {
- {
- put("user", user);
- put("password", password);
- }
- });
- }
-
- /**
- * @param url a database url of the form
- * <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
- * @param info a list of arbitrary string tag/value pairs as
- * connection arguments; normally at least a "user" and
- * "password" property should be included
- */
- public JdbcMeta(String url, Properties info) throws SQLException {
- this.url = url;
- this.info = info;
- this.connection = DriverManager.getConnection(url, info);
-
- int concurrencyLevel = Integer.parseInt(
- info.getProperty(ConnectionCacheSettings.CONCURRENCY_LEVEL.key(),
- ConnectionCacheSettings.CONCURRENCY_LEVEL.defaultValue()));
- int initialCapacity = Integer.parseInt(
- info.getProperty(ConnectionCacheSettings.INITIAL_CAPACITY.key(),
- ConnectionCacheSettings.INITIAL_CAPACITY.defaultValue()));
- long maxCapacity = Long.parseLong(
- info.getProperty(ConnectionCacheSettings.MAX_CAPACITY.key(),
- ConnectionCacheSettings.MAX_CAPACITY.defaultValue()));
- long connectionExpiryDuration = Long.parseLong(
- info.getProperty(ConnectionCacheSettings.EXPIRY_DURATION.key(),
- ConnectionCacheSettings.EXPIRY_DURATION.defaultValue()));
- TimeUnit connectionExpiryUnit = TimeUnit.valueOf(
- info.getProperty(ConnectionCacheSettings.EXPIRY_UNIT.key(),
- ConnectionCacheSettings.EXPIRY_UNIT.defaultValue()));
- this.connectionCache = CacheBuilder.newBuilder()
- .concurrencyLevel(concurrencyLevel)
- .initialCapacity(initialCapacity)
- .maximumSize(maxCapacity)
- .expireAfterAccess(connectionExpiryDuration, connectionExpiryUnit)
- .removalListener(new ConnectionExpiryHandler())
- .build();
- if (LOG.isDebugEnabled()) {
- LOG.debug("instantiated connection cache: " + connectionCache.stats());
- }
-
- concurrencyLevel = Integer.parseInt(
- info.getProperty(StatementCacheSettings.CONCURRENCY_LEVEL.key(),
- StatementCacheSettings.CONCURRENCY_LEVEL.defaultValue()));
- initialCapacity = Integer.parseInt(
- info.getProperty(StatementCacheSettings.INITIAL_CAPACITY.key(),
- StatementCacheSettings.INITIAL_CAPACITY.defaultValue()));
- maxCapacity = Long.parseLong(
- info.getProperty(StatementCacheSettings.MAX_CAPACITY.key(),
- StatementCacheSettings.MAX_CAPACITY.defaultValue()));
- connectionExpiryDuration = Long.parseLong(
- info.getProperty(StatementCacheSettings.EXPIRY_DURATION.key(),
- StatementCacheSettings.EXPIRY_DURATION.defaultValue()));
- connectionExpiryUnit = TimeUnit.valueOf(
- info.getProperty(StatementCacheSettings.EXPIRY_UNIT.key(),
- StatementCacheSettings.EXPIRY_UNIT.defaultValue()));
- this.statementCache = CacheBuilder.newBuilder()
- .concurrencyLevel(concurrencyLevel)
- .initialCapacity(initialCapacity)
- .maximumSize(maxCapacity)
- .expireAfterAccess(connectionExpiryDuration, connectionExpiryUnit)
- .removalListener(new StatementExpiryHandler())
- .build();
- if (LOG.isDebugEnabled()) {
- LOG.debug("instantiated statement cache: " + statementCache.stats());
- }
- }
-
- 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(DEFAULT_CONN_ID, -1,
- 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(DEFAULT_CONN_ID, -1,
- 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(DEFAULT_CONN_ID, -1,
- connection.getMetaData().getSchemas(catalog, schemaPattern.s));
- } catch (SQLException e) {
- throw new RuntimeException(e);
- }
- }
-
- public MetaResultSet getCatalogs() {
- try {
- return JdbcResultSet.create(DEFAULT_CONN_ID, -1,
- connection.getMetaData().getCatalogs());
- } catch (SQLException e) {
- throw new RuntimeException(e);
- }
- }
-
- public MetaResultSet getTableTypes() {
- try {
- return JdbcResultSet.create(DEFAULT_CONN_ID, -1,
- connection.getMetaData().getTableTypes());
- } catch (SQLException e) {
- throw new RuntimeException(e);
- }
- }
-
- public MetaResultSet getProcedures(String catalog, Pat schemaPattern,
- Pat procedureNamePattern) {
- try {
- return JdbcResultSet.create(DEFAULT_CONN_ID, -1,
- 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(DEFAULT_CONN_ID, -1,
- 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(DEFAULT_CONN_ID, -1,
- 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(DEFAULT_CONN_ID, -1,
- 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(DEFAULT_CONN_ID, -1,
- 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(DEFAULT_CONN_ID, -1,
- 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(DEFAULT_CONN_ID, -1,
- 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;
- }
-
- protected Connection getConnection(String id) throws SQLException {
- Connection conn = connectionCache.getIfPresent(id);
- if (conn == null) {
- conn = DriverManager.getConnection(url, info);
- connectionCache.put(id, conn);
- }
- return conn;
- }
-
- public StatementHandle createStatement(ConnectionHandle ch) {
- try {
- final Connection conn = getConnection(ch.id);
- final Statement statement = conn.createStatement();
- final int id = System.identityHashCode(statement);
- statementCache.put(id, new StatementInfo(statement));
- StatementHandle h = new StatementHandle(ch.id, id, null);
- if (LOG.isTraceEnabled()) {
- LOG.trace("created statement " + h);
- }
- return h;
- } catch (SQLException e) {
- throw propagate(e);
- }
- }
-
- @Override public void closeStatement(StatementHandle h) {
- StatementInfo info = statementCache.getIfPresent(h.id);
- if (info == null || info.statement == null) {
- LOG.debug("client requested close unknown statement " + h);
- return;
- }
- if (LOG.isTraceEnabled()) {
- LOG.trace("closing statement " + h);
- }
- try {
- if (info.resultSet != null) {
- info.resultSet.close();
- }
- info.statement.close();
- } catch (SQLException e) {
- throw propagate(e);
- } finally {
- statementCache.invalidate(h.id);
- }
- }
-
- @Override public void closeConnection(ConnectionHandle ch) {
- Connection conn = connectionCache.getIfPresent(ch.id);
- if (conn == null) {
- LOG.debug("client requested close unknown connection " + ch);
- return;
- }
- if (LOG.isTraceEnabled()) {
- LOG.trace("closing connection " + ch);
- }
- try {
- conn.close();
- } catch (SQLException e) {
- throw propagate(e);
- } finally {
- connectionCache.invalidate(ch.id);
- }
- }
-
- 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 StatementHandle prepare(ConnectionHandle ch, String sql,
- int maxRowCount) {
- try {
- final Connection conn = getConnection(ch.id);
- final PreparedStatement statement = conn.prepareStatement(sql);
- final int id = System.identityHashCode(statement);
- statementCache.put(id, new StatementInfo(statement));
- StatementHandle h = new StatementHandle(ch.id, id,
- signature(statement.getMetaData(), statement.getParameterMetaData(),
- sql));
- if (LOG.isTraceEnabled()) {
- LOG.trace("prepared statement " + h);
- }
- return h;
- } catch (SQLException e) {
- throw propagate(e);
- }
- }
-
- public MetaResultSet prepareAndExecute(ConnectionHandle ch, String sql,
- int maxRowCount, PrepareCallback callback) {
- try {
- final Connection connection = getConnection(ch.id);
- final PreparedStatement statement = connection.prepareStatement(sql);
- final int id = System.identityHashCode(statement);
- final StatementInfo info = new StatementInfo(statement);
- statementCache.put(id, info);
- info.resultSet = statement.executeQuery();
- MetaResultSet mrs = JdbcResultSet.create(ch.id, id, info.resultSet);
- if (LOG.isTraceEnabled()) {
- StatementHandle h = new StatementHandle(ch.id, id, null);
- LOG.trace("prepAndExec statement " + h);
- }
- return mrs;
- } catch (SQLException e) {
- throw propagate(e);
- }
- }
-
- public Frame fetch(StatementHandle h, List<Object> parameterValues,
- int offset, int fetchMaxRowCount) {
- if (LOG.isTraceEnabled()) {
- LOG.trace("fetching " + h + " offset:" + offset + " fetchMaxRowCount:" + fetchMaxRowCount);
- }
- try {
- final StatementInfo statementInfo = Objects.requireNonNull(
- statementCache.getIfPresent(h.id),
- "Statement not found, potentially expired. " + h);
- 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