blob: ffcc64dca0df1185a52a92b2913df33725616c97 [file] [log] [blame]
/*
* 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.DatabaseProperty;
import org.apache.calcite.avatica.jdbc.JdbcMeta;
import org.apache.calcite.avatica.remote.JsonService;
import org.apache.calcite.avatica.remote.LocalJsonService;
import org.apache.calcite.avatica.remote.LocalProtobufService;
import org.apache.calcite.avatica.remote.LocalService;
import org.apache.calcite.avatica.remote.ProtobufTranslation;
import org.apache.calcite.avatica.remote.ProtobufTranslationImpl;
import org.apache.calcite.avatica.remote.Service;
import org.apache.calcite.avatica.remote.TypedValue;
import org.apache.calcite.avatica.util.DateTimeUtils;
import com.google.common.cache.Cache;
import net.jcip.annotations.NotThreadSafe;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Date;
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.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.Callable;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.core.StringContains.containsString;
import static org.hamcrest.core.StringStartsWith.startsWith;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
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.
*/
@RunWith(Parameterized.class)
@NotThreadSafe // for testConnectionIsolation
public class RemoteDriverTest {
private static final Logger LOG = LoggerFactory.getLogger(RemoteDriverTest.class);
public static final String LJS =
LocalJdbcServiceFactory.class.getName();
public static final String QRJS =
QuasiRemoteJdbcServiceFactory.class.getName();
public static final String QRPBS =
QuasiRemotePBJdbcServiceFactory.class.getName();
private static final ConnectionSpec CONNECTION_SPEC = ConnectionSpec.HSQLDB;
private static Connection ljs() throws SQLException {
return DriverManager.getConnection("jdbc:avatica:remote:factory=" + QRJS);
}
private static Connection lpbs() throws SQLException {
return DriverManager.getConnection("jdbc:avatica:remote:factory=" + QRPBS);
}
private Connection canon() throws SQLException {
return DriverManager.getConnection(CONNECTION_SPEC.url,
CONNECTION_SPEC.username, CONNECTION_SPEC.password);
}
/**
* Interface that allows for alternate ways to access internals to the Connection for testing
* purposes.
*/
interface ConnectionInternals {
/**
* Reaches into the guts of a quasi-remote connection and pull out the
* statement map from the other side.
*
* <p>TODO: refactor tests to replace reflection with package-local access
*/
Cache<Integer, Object> getRemoteStatementMap(AvaticaConnection connection) throws Exception;
/**
* Reaches into the guts of a quasi-remote connection and pull out the
* connection map from the other side.
*
* <p>TODO: refactor tests to replace reflection with package-local access
*/
Cache<String, Connection> getRemoteConnectionMap(AvaticaConnection connection) throws Exception;
}
// Run each test with the LocalJsonService and LocalProtobufService
@Parameters(name = "{0}")
public static List<Object[]> parameters() {
List<Object[]> connections = new ArrayList<>();
// Json and Protobuf operations should be equivalent -- tests against one work on the other
// Each test needs to get a fresh Connection and also access some internals on that Connection.
connections.add(
new Object[] {
"JSON",
new Callable<Connection>() {
public Connection call() {
try {
return ljs();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
},
new QuasiRemoteJdbcServiceInternals(),
new Callable<RequestInspection>() {
public RequestInspection call() throws Exception {
assert null != QuasiRemoteJdbcServiceFactory.requestInspection;
return QuasiRemoteJdbcServiceFactory.requestInspection;
}
} });
// TODO write the ConnectionInternals implementation
connections.add(
new Object[] {
"PROTOBUF",
new Callable<Connection>() {
public Connection call() {
try {
return lpbs();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
},
new QuasiRemoteProtobufJdbcServiceInternals(),
new Callable<RequestInspection>() {
public RequestInspection call() throws Exception {
assert null != QuasiRemotePBJdbcServiceFactory.requestInspection;
return QuasiRemotePBJdbcServiceFactory.requestInspection;
}
} });
return connections;
}
private final Callable<Connection> localConnectionCallable;
private final ConnectionInternals localConnectionInternals;
private final Callable<RequestInspection> requestInspectionCallable;
public RemoteDriverTest(String name, Callable<Connection> localConnectionCallable,
ConnectionInternals internals, Callable<RequestInspection> requestInspectionCallable) {
this.localConnectionCallable = localConnectionCallable;
this.localConnectionInternals = internals;
this.requestInspectionCallable = requestInspectionCallable;
}
private Connection getLocalConnection() {
try {
return localConnectionCallable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private ConnectionInternals getLocalConnectionInternals() {
return localConnectionInternals;
}
private RequestInspection getRequestInspection() {
try {
return requestInspectionCallable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/** Executes a lambda for the canonical connection and the local
* connection. */
public void eachConnection(ConnectionFunction f, Connection localConn) throws Exception {
for (int i = 0; i < 2; i++) {
try (Connection connection = i == 0 ? canon() : localConn) {
f.apply(connection);
}
}
}
@Before
public void before() throws Exception {
QuasiRemoteJdbcServiceFactory.initService();
QuasiRemotePBJdbcServiceFactory.initService();
}
@Test public void testRegister() throws Exception {
final Connection connection = getLocalConnection();
assertThat(connection.isClosed(), is(false));
connection.close();
assertThat(connection.isClosed(), is(true));
}
@Test public void testIsValid() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
final Connection connection = getLocalConnection();
try {
connection.isValid(-1);
fail("Connection isValid should throw SQLException on negative timeout");
} catch (SQLException expected) {
assertEquals("timeout is less than 0", expected.getMessage());
}
// Check that connection isValid before and during use
assertThat(connection.isValid(1), is(true));
Statement statement = connection.createStatement();
assertTrue(statement.execute("VALUES 1"));
assertNotNull(statement.getResultSet());
assertThat(connection.isValid(1), is(true));
statement.close();
assertThat(connection.isValid(1), is(true));
// Connection should be invalid after being closed
connection.close();
assertThat(connection.isValid(1), is(false));
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testDatabaseProperties() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
final Connection connection = getLocalConnection();
for (Meta.DatabaseProperty p : Meta.DatabaseProperty.values()) {
switch (p) {
case GET_NUMERIC_FUNCTIONS:
assertThat(connection.getMetaData().getNumericFunctions(),
equalTo("ABS,ACOS,ASIN,ATAN,ATAN2,BITAND,BITOR,BITXOR,"
+ "CEILING,COS,COT,DEGREES,EXP,FLOOR,LOG,LOG10,MOD,"
+ "PI,POWER,RADIANS,RAND,ROUND,ROUNDMAGIC,SIGN,SIN,"
+ "SQRT,TAN,TRUNCATE"));
break;
case GET_SYSTEM_FUNCTIONS:
assertThat(connection.getMetaData().getSystemFunctions(),
equalTo("DATABASE,IFNULL,USER"));
break;
case GET_TIME_DATE_FUNCTIONS:
assertThat(connection.getMetaData().getTimeDateFunctions(),
equalTo("CURDATE,CURTIME,DATEDIFF,DAYNAME,DAYOFMONTH,DAYOFWEEK,"
+ "DAYOFYEAR,HOUR,MINUTE,MONTH,MONTHNAME,NOW,QUARTER,SECOND,"
+ "SECONDS_SINCE_MIDNIGHT,TIMESTAMPADD,TIMESTAMPDIFF,"
+ "TO_CHAR,WEEK,YEAR"));
break;
case GET_S_Q_L_KEYWORDS:
assertThat(connection.getMetaData().getSQLKeywords(),
equalTo("")); // No SQL keywords return for HSQLDB
break;
case GET_STRING_FUNCTIONS:
assertThat(connection.getMetaData().getStringFunctions(),
equalTo("ASCII,CHAR,CONCAT,DIFFERENCE,HEXTORAW,INSERT,LCASE,"
+ "LEFT,LENGTH,LOCATE,LTRIM,RAWTOHEX,REPEAT,REPLACE,"
+ "RIGHT,RTRIM,SOUNDEX,SPACE,SUBSTR,UCASE"));
break;
default:
}
}
connection.close();
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testTypeInfo() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
final Connection connection = getLocalConnection();
final ResultSet resultSet =
connection.getMetaData().getTypeInfo();
assertTrue(resultSet.next());
final ResultSetMetaData metaData = resultSet.getMetaData();
assertTrue(metaData.getColumnCount() >= 18);
assertEquals("TYPE_NAME", metaData.getColumnName(1));
assertEquals("DATA_TYPE", metaData.getColumnName(2));
assertEquals("PRECISION", metaData.getColumnName(3));
assertEquals("SQL_DATA_TYPE", metaData.getColumnName(16));
assertEquals("SQL_DATETIME_SUB", metaData.getColumnName(17));
assertEquals("NUM_PREC_RADIX", metaData.getColumnName(18));
resultSet.close();
connection.close();
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testGetTables() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
final Connection connection = getLocalConnection();
final ResultSet resultSet =
connection.getMetaData().getTables(null, "SCOTT", null, null);
assertEquals(13, resultSet.getMetaData().getColumnCount());
assertTrue(resultSet.next());
assertEquals("DEPT", resultSet.getString(3));
assertTrue(resultSet.next());
assertEquals("EMP", resultSet.getString(3));
assertTrue(resultSet.next());
assertEquals("BONUS", resultSet.getString(3));
assertTrue(resultSet.next());
assertEquals("SALGRADE", resultSet.getString(3));
resultSet.close();
connection.close();
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@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));
}
@Test public void testStatementExecuteQueryLocal() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
checkStatementExecuteQuery(getLocalConnection(), false);
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testPrepareExecuteQueryLocal() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
checkStatementExecuteQuery(getLocalConnection(), true);
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testInsertDrop() throws Exception {
final String t = AvaticaUtils.unique("TEST_TABLE2");
final String create =
String.format(Locale.ROOT, "create table if not exists %s ("
+ "id int not null, "
+ "msg varchar(3) not null)", t);
final String insert = String.format(Locale.ROOT,
"insert into %s values(1, 'foo')", t);
Connection connection = ljs();
Statement statement = connection.createStatement();
statement.execute(create);
Statement stmt = connection.createStatement();
int count = stmt.executeUpdate(insert);
assertThat(count, is(1));
ResultSet resultSet = stmt.getResultSet();
assertThat(resultSet, nullValue());
PreparedStatement pstmt = connection.prepareStatement(insert);
boolean status = pstmt.execute();
assertThat(status, is(false));
int updateCount = pstmt.getUpdateCount();
assertThat(updateCount, is(1));
}
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(0));
}
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 testStatementExecuteLocal() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
checkStatementExecute(getLocalConnection(), false);
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testStatementExecuteFetch() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
// Creating a > 100 rows queries to enable fetch request
String sql = "select * from emp cross join emp";
checkExecuteFetch(getLocalConnection(), sql, false, 1);
// PreparedStatement needed an extra fetch, as the execute will
// trigger the 1st fetch. Where statement execute will execute direct
// with results back.
// 1 fetch, because execute did the first fetch
checkExecuteFetch(getLocalConnection(), sql, true, 1);
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
private void checkExecuteFetch(Connection conn, String sql, boolean isPrepare,
int fetchCountMatch) throws SQLException {
final Statement exeStatement;
final ResultSet results;
getRequestInspection().getRequestLogger().enableAndClear();
if (isPrepare) {
PreparedStatement statement = conn.prepareStatement(sql);
exeStatement = statement;
results = statement.executeQuery();
} else {
Statement statement = conn.createStatement();
exeStatement = statement;
results = statement.executeQuery(sql);
}
int count = 0;
int fetchCount = 0;
while (results.next()) {
count++;
}
results.close();
exeStatement.close();
List<String[]> x = getRequestInspection().getRequestLogger().getAndDisable();
for (String[] pair : x) {
if (pair[0].contains("\"request\":\"fetch")) {
fetchCount++;
}
}
assertEquals(count, 196);
assertEquals(fetchCountMatch, fetchCount);
}
@Test public void testStatementExecuteLocalMaxRow() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
checkStatementExecute(getLocalConnection(), false, 2);
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testFetchSize() throws Exception {
Connection connection = ljs();
Statement statement = connection.createStatement();
statement.setFetchSize(101);
assertEquals(statement.getFetchSize(), 101);
PreparedStatement preparedStatement =
connection.prepareStatement("select * from (values (1, 'a')) as tbl1 (c1, c2)");
preparedStatement.setFetchSize(1);
assertEquals(preparedStatement.getFetchSize(), 1);
}
@Ignore("CALCITE-719: Refactor PreparedStatement to support setMaxRows")
@Test public void testStatementPrepareExecuteLocalMaxRow() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
checkStatementExecute(getLocalConnection(), true, 2);
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testPrepareExecuteLocal() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
checkStatementExecute(getLocalConnection(), true);
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
private void checkStatementExecute(Connection connection,
boolean prepare) throws SQLException {
checkStatementExecute(connection, prepare, 0);
}
private void checkStatementExecute(Connection connection,
boolean prepare, int maxRowCount) 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;
ps.setMaxRows(maxRowCount);
parameterMetaData = ps.getParameterMetaData();
assertTrue(ps.execute());
resultSet = ps.getResultSet();
} else {
statement = connection.createStatement();
statement.setMaxRows(maxRowCount);
parameterMetaData = null;
assertTrue(statement.execute(sql));
resultSet = statement.getResultSet();
}
if (parameterMetaData != null) {
assertThat(parameterMetaData.getParameterCount(), equalTo(0));
}
final ResultSetMetaData metaData = resultSet.getMetaData();
assertEquals(2, metaData.getColumnCount());
assertEquals("C1", metaData.getColumnName(1));
assertEquals("C2", metaData.getColumnName(2));
for (int i = 0; i < maxRowCount || (maxRowCount == 0 && i < 3); i++) {
assertTrue(resultSet.next());
}
assertFalse(resultSet.next());
resultSet.close();
statement.close();
connection.close();
}
@Test public void testCreateInsertUpdateDrop() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
final String t = AvaticaUtils.unique("TEST_TABLE");
final String drop =
String.format(Locale.ROOT, "drop table %s if exists", t);
final String create =
String.format(Locale.ROOT, "create table %s("
+ "id int not null, "
+ "msg varchar(3) not null)",
t);
final String insert = String.format(Locale.ROOT,
"insert into %s values(1, 'foo')", t);
final String update =
String.format(Locale.ROOT, "update %s set msg='bar' where id=1", t);
try (Connection connection = getLocalConnection();
Statement statement = connection.createStatement();
PreparedStatement pstmt = connection.prepareStatement("values 1")) {
// drop
assertFalse(statement.execute(drop));
assertEquals(0, statement.getUpdateCount());
assertNull(statement.getResultSet());
try {
final ResultSet rs = statement.executeQuery(drop);
fail("expected error, got " + rs);
} catch (SQLException e) {
assertThat(e.getMessage(),
equalTo("Statement did not return a result set"));
}
assertEquals(0, statement.executeUpdate(drop));
assertEquals(0, statement.getUpdateCount());
assertNull(statement.getResultSet());
// create
assertFalse(statement.execute(create));
assertEquals(0, statement.getUpdateCount());
assertNull(statement.getResultSet());
assertFalse(statement.execute(drop)); // tidy up
try {
final ResultSet rs = statement.executeQuery(create);
fail("expected error, got " + rs);
} catch (SQLException e) {
assertThat(e.getMessage(),
equalTo("Statement did not return a result set"));
}
assertFalse(statement.execute(drop)); // tidy up
assertEquals(0, statement.executeUpdate(create));
assertEquals(0, statement.getUpdateCount());
assertNull(statement.getResultSet());
// insert
assertFalse(statement.execute(insert));
assertEquals(1, statement.getUpdateCount());
assertNull(statement.getResultSet());
try {
final ResultSet rs = statement.executeQuery(insert);
fail("expected error, got " + rs);
} catch (SQLException e) {
assertThat(e.getMessage(),
equalTo("Statement did not return a result set"));
}
assertEquals(1, statement.executeUpdate(insert));
assertEquals(1, statement.getUpdateCount());
assertNull(statement.getResultSet());
// update
assertFalse(statement.execute(update));
assertEquals(3, statement.getUpdateCount());
assertNull(statement.getResultSet());
try {
final ResultSet rs = statement.executeQuery(update);
fail("expected error, got " + rs);
} catch (SQLException e) {
assertThat(e.getMessage(),
equalTo("Statement did not return a result set"));
}
assertEquals(3, statement.executeUpdate(update));
assertEquals(3, statement.getUpdateCount());
assertNull(statement.getResultSet());
final String[] messages = {
"Cannot call executeQuery(String) on prepared or callable statement",
"Cannot call execute(String) on prepared or callable statement",
"Cannot call executeUpdate(String) on prepared or callable statement",
};
for (String sql : new String[]{drop, create, insert, update}) {
for (int i = 0; i <= 2; i++) {
try {
Object o;
switch (i) {
case 0:
o = pstmt.executeQuery(sql);
break;
case 1:
o = pstmt.execute(sql);
break;
default:
o = pstmt.executeUpdate(sql);
}
fail("expected error, got " + o);
} catch (SQLException e) {
assertThat(e.getMessage(), equalTo(messages[i]));
}
}
}
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testTypeHandling() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
final String query = "select * from EMP";
try (Connection cannon = canon();
Connection underTest = getLocalConnection();
Statement s1 = cannon.createStatement();
Statement s2 = underTest.createStatement()) {
assertTrue(s1.execute(query));
assertTrue(s2.execute(query));
assertResultSetsEqual(s1, s2);
}
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
private void assertResultSetsEqual(Statement s1, Statement s2)
throws SQLException {
final TimeZone moscowTz = TimeZone.getTimeZone("Europe/Moscow");
final Calendar moscowCalendar =
Calendar.getInstance(moscowTz, Locale.ROOT);
final TimeZone alaskaTz = TimeZone.getTimeZone("America/Anchorage");
final Calendar alaskaCalendar =
Calendar.getInstance(alaskaTz, Locale.ROOT);
try (ResultSet rs1 = s1.getResultSet();
ResultSet rs2 = s2.getResultSet()) {
assertEquals(rs1.getMetaData().getColumnCount(),
rs2.getMetaData().getColumnCount());
int colCount = rs1.getMetaData().getColumnCount();
while (rs1.next() && rs2.next()) {
for (int i = 0; i < colCount; i++) {
Object o1 = rs1.getObject(i + 1);
Object o2 = rs2.getObject(i + 1);
if (o1 instanceof Integer && o2 instanceof Short) {
// Hsqldb returns Integer for short columns; we prefer Short
o1 = ((Number) o1).shortValue();
}
if (o1 instanceof Integer && o2 instanceof Byte) {
// Hsqldb returns Integer for tinyint columns; we prefer Byte
o1 = ((Number) o1).byteValue();
}
if (o1 instanceof Date) {
Date d1 = rs1.getDate(i + 1, moscowCalendar);
Date d2 = rs2.getDate(i + 1, moscowCalendar);
assertEquals(d1, d2);
d1 = rs1.getDate(i + 1, alaskaCalendar);
d2 = rs2.getDate(i + 1, alaskaCalendar);
assertEquals(d1, d2);
d1 = rs1.getDate(i + 1, null);
d2 = rs2.getDate(i + 1, null);
assertEquals(d1, d2);
d1 = rs1.getDate(i + 1);
d2 = rs2.getDate(i + 1);
assertEquals(d1, d2);
}
if (o1 instanceof Timestamp) {
Timestamp d1 = rs1.getTimestamp(i + 1, moscowCalendar);
Timestamp d2 = rs2.getTimestamp(i + 1, moscowCalendar);
assertEquals(d1, d2);
d1 = rs1.getTimestamp(i + 1, alaskaCalendar);
d2 = rs2.getTimestamp(i + 1, alaskaCalendar);
assertEquals(d1, d2);
d1 = rs1.getTimestamp(i + 1, null);
d2 = rs2.getTimestamp(i + 1, null);
assertEquals(d1, d2);
d1 = rs1.getTimestamp(i + 1);
d2 = rs2.getTimestamp(i + 1);
assertEquals(d1, d2);
}
assertEquals(o1, o2);
}
}
assertEquals(rs1.next(), rs2.next());
}
}
/** Callback to set parameters on each prepared statement before
* each is executed and the result sets compared. */
interface PreparedStatementFunction {
void apply(PreparedStatement s1, PreparedStatement s2)
throws SQLException;
}
/** Callback to execute some code against a connection. */
interface ConnectionFunction {
void apply(Connection c1) throws Exception;
}
@Test public void testSetParameter() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
checkSetParameter("select ? from (values 1)",
new PreparedStatementFunction() {
public void apply(PreparedStatement s1, PreparedStatement s2)
throws SQLException {
final Date d = new Date(1234567890);
s1.setDate(1, d);
s2.setDate(1, d);
}
});
checkSetParameter("select ? from (values 1)",
new PreparedStatementFunction() {
public void apply(PreparedStatement s1, PreparedStatement s2)
throws SQLException {
final Timestamp ts = new Timestamp(123456789012L);
s1.setTimestamp(1, ts);
s2.setTimestamp(1, ts);
}
});
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
void checkSetParameter(String query, PreparedStatementFunction fn)
throws SQLException {
try (Connection cannon = canon();
Connection underTest = ljs();
PreparedStatement s1 = cannon.prepareStatement(query);
PreparedStatement s2 = underTest.prepareStatement(query)) {
fn.apply(s1, s2);
assertTrue(s1.execute());
assertTrue(s2.execute());
assertResultSetsEqual(s1, s2);
}
}
@Test public void testStatementLifecycle() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try (AvaticaConnection connection = (AvaticaConnection) getLocalConnection()) {
Map<Integer, AvaticaStatement> clientMap = connection.statementMap;
Cache<Integer, Object> serverMap = getLocalConnectionInternals()
.getRemoteStatementMap(connection);
// Other tests being run might leave statements in the cache.
// The lock guards against more statements being cached during the test.
serverMap.invalidateAll();
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());
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testConnectionIsolation() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
Cache<String, Connection> connectionMap = getLocalConnectionInternals()
.getRemoteConnectionMap((AvaticaConnection) getLocalConnection());
// Other tests being run might leave connections in the cache.
// The lock guards against more connections being cached during the test.
connectionMap.invalidateAll();
final String sql = "select * from (values (1, 'a'))";
assertEquals("connection cache should start empty",
0, connectionMap.size());
Connection conn1 = getLocalConnection();
Connection conn2 = getLocalConnection();
assertEquals("we now have two connections open",
2, connectionMap.size());
PreparedStatement conn1stmt1 = conn1.prepareStatement(sql);
assertEquals(
"creating a statement does not cause new connection",
2, connectionMap.size());
PreparedStatement conn2stmt1 = conn2.prepareStatement(sql);
assertEquals(
"creating a statement does not cause new connection",
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());
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testPrepareBindExecuteFetch() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
getRequestInspection().getRequestLogger().enableAndClear();
checkPrepareBindExecuteFetch(getLocalConnection());
List<String[]> x = getRequestInspection().getRequestLogger().getAndDisable();
for (String[] pair : x) {
System.out.println(pair[0] + "=" + pair[1]);
}
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
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) {
LOG.info("Caught expected error", e);
assertThat(e.getMessage(),
containsString("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();
}
@Test public void testPrepareBindExecuteFetchVarbinary() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
final Connection connection = getLocalConnection();
final String sql = "select x'de' || ? as c from (values (1, 'a'))";
final PreparedStatement ps =
connection.prepareStatement(sql);
final ParameterMetaData parameterMetaData = ps.getParameterMetaData();
assertThat(parameterMetaData.getParameterCount(), equalTo(1));
ps.setBytes(1, new byte[]{65, 0, 66});
final ResultSet resultSet = ps.executeQuery();
assertTrue(resultSet.next());
assertThat(resultSet.getBytes(1),
equalTo(new byte[]{(byte) 0xDE, 65, 0, 66}));
resultSet.close();
ps.close();
connection.close();
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testPrepareBindExecuteFetchDate() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
eachConnection(
new ConnectionFunction() {
public void apply(Connection c1) throws Exception {
checkPrepareBindExecuteFetchDate(c1);
}
}, getLocalConnection());
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
private void checkPrepareBindExecuteFetchDate(Connection connection) throws Exception {
final String sql0 =
"select cast(? as varchar(20)) as c\n"
+ "from (values (1, 'a'))";
final Date date = Date.valueOf("2015-04-08");
final long time = date.getTime();
PreparedStatement ps;
ParameterMetaData parameterMetaData;
ResultSet resultSet;
ps = connection.prepareStatement(sql0);
parameterMetaData = ps.getParameterMetaData();
assertThat(parameterMetaData.getParameterCount(), equalTo(1));
ps.setDate(1, date);
resultSet = ps.executeQuery();
assertThat(resultSet.next(), is(true));
assertThat(resultSet.getString(1), is("2015-04-08"));
ps.setTimestamp(1, new Timestamp(time));
resultSet = ps.executeQuery();
assertThat(resultSet.next(), is(true));
assertThat(resultSet.getString(1), is("2015-04-08 00:00:00.0"));
ps.setTime(1, new Time(time));
resultSet = ps.executeQuery();
assertThat(resultSet.next(), is(true));
assertThat(resultSet.getString(1), is("00:00:00"));
ps.close();
connection.close();
}
@Test public void testDatabaseProperty() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
eachConnection(
new ConnectionFunction() {
public void apply(Connection c1) throws Exception {
checkDatabaseProperty(c1);
}
}, getLocalConnection());
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
private void checkDatabaseProperty(Connection connection)
throws SQLException {
final DatabaseMetaData metaData = connection.getMetaData();
assertThat(metaData.getSQLKeywords(), equalTo(""));
assertThat(metaData.getStringFunctions(),
equalTo("ASCII,CHAR,CONCAT,DIFFERENCE,HEXTORAW,INSERT,LCASE,LEFT,"
+ "LENGTH,LOCATE,LTRIM,RAWTOHEX,REPEAT,REPLACE,RIGHT,RTRIM,SOUNDEX,"
+ "SPACE,SUBSTR,UCASE"));
assertThat(metaData.getDefaultTransactionIsolation(),
equalTo(Connection.TRANSACTION_READ_COMMITTED));
}
@Test public void testBatchExecute() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
eachConnection(
new ConnectionFunction() {
public void apply(Connection c1) throws Exception {
executeBatchUpdate(c1);
}
}, getLocalConnection());
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
private void executeBatchUpdate(Connection conn) throws Exception {
final int numRows = 10;
try (Statement stmt = conn.createStatement()) {
final String tableName = AvaticaUtils.unique("BATCH_EXECUTE");
LOG.info("Creating table {}", tableName);
final String createCommand = String.format(Locale.ROOT,
"create table if not exists %s ("
+ "id int not null, "
+ "msg varchar(10) not null)", tableName);
assertFalse("Failed to create table", stmt.execute(createCommand));
final String updatePrefix =
String.format(Locale.ROOT, "INSERT INTO %s values(", tableName);
for (int i = 0; i < numRows; i++) {
stmt.addBatch(updatePrefix + i + ", '" + Integer.toString(i) + "')");
}
int[] updateCounts = stmt.executeBatch();
assertEquals("Unexpected number of update counts returned", numRows, updateCounts.length);
for (int i = 0; i < updateCounts.length; i++) {
assertEquals("Unexpected update count at index " + i, 1, updateCounts[i]);
}
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY id asc");
assertNotNull("ResultSet was null", rs);
for (int i = 0; i < numRows; i++) {
assertTrue("ResultSet should have a result", rs.next());
assertEquals("Wrong integer value for row " + i, i, rs.getInt(1));
assertEquals("Wrong string value for row " + i, Integer.toString(i), rs.getString(2));
}
assertFalse("ResultSet should have no more records", rs.next());
}
}
@Test public void testPreparedBatches() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
eachConnection(
new ConnectionFunction() {
public void apply(Connection c1) throws Exception {
executePreparedBatchUpdate(c1);
}
}, getLocalConnection());
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
private void executePreparedBatchUpdate(Connection conn) throws Exception {
final int numRows = 10;
final String tableName = AvaticaUtils.unique("PREPARED_BATCH_EXECUTE");
LOG.info("Creating table {}", tableName);
try (Statement stmt = conn.createStatement()) {
final String createCommand =
String.format(Locale.ROOT, "create table if not exists %s ("
+ "id int not null, "
+ "msg varchar(10) not null)", tableName);
assertFalse("Failed to create table", stmt.execute(createCommand));
}
final String insertSql =
String.format(Locale.ROOT, "INSERT INTO %s values(?, ?)", tableName);
try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
// Add batches with the prepared statement
for (int i = 0; i < numRows; i++) {
pstmt.setInt(1, i);
pstmt.setString(2, Integer.toString(i));
pstmt.addBatch();
}
int[] updateCounts = pstmt.executeBatch();
assertEquals("Unexpected number of update counts returned", numRows, updateCounts.length);
for (int i = 0; i < updateCounts.length; i++) {
assertEquals("Unexpected update count at index " + i, 1, updateCounts[i]);
}
}
try (Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY id asc");
assertNotNull("ResultSet was null", rs);
for (int i = 0; i < numRows; i++) {
assertTrue("ResultSet should have a result", rs.next());
assertEquals("Wrong integer value for row " + i, i, rs.getInt(1));
assertEquals("Wrong string value for row " + i, Integer.toString(i), rs.getString(2));
}
assertFalse("ResultSet should have no more records", rs.next());
}
}
@Test public void testPreparedInsert() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
eachConnection(
new ConnectionFunction() {
public void apply(Connection c1) throws Exception {
executePreparedInsert(c1);
}
}, getLocalConnection());
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
private void executePreparedInsert(Connection conn) throws Exception {
final int numRows = 10;
final String tableName = AvaticaUtils.unique("PREPARED_INSERT_EXECUTE");
LOG.info("Creating table {}", tableName);
try (Statement stmt = conn.createStatement()) {
final String createCommand =
String.format(Locale.ROOT, "create table if not exists %s ("
+ "id int not null, "
+ "msg varchar(10) not null)", tableName);
assertFalse("Failed to create table", stmt.execute(createCommand));
}
final String insertSql =
String.format(Locale.ROOT, "INSERT INTO %s values(?, ?)", tableName);
try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
// Add batches with the prepared statement
for (int i = 0; i < numRows; i++) {
pstmt.setInt(1, i);
pstmt.setString(2, Integer.toString(i));
assertEquals(1, pstmt.executeUpdate());
}
}
try (Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY id asc");
assertNotNull("ResultSet was null", rs);
for (int i = 0; i < numRows; i++) {
assertTrue("ResultSet should have a result", rs.next());
assertEquals("Wrong integer value for row " + i, i, rs.getInt(1));
assertEquals("Wrong string value for row " + i, Integer.toString(i), rs.getString(2));
}
assertFalse("ResultSet should have no more records", rs.next());
}
}
@Test public void testPreparedInsertWithNulls() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
eachConnection(
new ConnectionFunction() {
public void apply(Connection c1) throws Exception {
executePreparedInsertWithNulls(c1);
}
}, getLocalConnection());
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
private void executePreparedInsertWithNulls(Connection conn) throws Exception {
final int numRows = 10;
final String tableName = AvaticaUtils.unique("PREPARED_INSERT_EXECUTE_NULLS");
LOG.info("Creating table {}", tableName);
try (Statement stmt = conn.createStatement()) {
final String createCommand =
String.format(Locale.ROOT, "create table if not exists %s ("
+ "id int not null, "
+ "msg varchar(10))", tableName);
assertFalse("Failed to create table", stmt.execute(createCommand));
}
final String insertSql =
String.format(Locale.ROOT, "INSERT INTO %s values(?, ?)", tableName);
try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
// Add batches with the prepared statement
for (int i = 0; i < numRows; i++) {
pstmt.setInt(1, i);
// Even inserts are non-null, odd are null
if (0 == i % 2) {
pstmt.setString(2, Integer.toString(i));
} else {
pstmt.setNull(2, Types.VARCHAR);
}
assertEquals(1, pstmt.executeUpdate());
}
}
try (Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY id asc");
assertNotNull("ResultSet was null", rs);
for (int i = 0; i < numRows; i++) {
assertTrue("ResultSet should have a result", rs.next());
assertEquals("Wrong integer value for row " + i, i, rs.getInt(1));
if (0 == i % 2) {
assertEquals("Wrong string value for row " + i, Integer.toString(i), rs.getString(2));
} else {
assertNull("Expected null value for row " + i, rs.getString(2));
}
}
assertFalse("ResultSet should have no more records", rs.next());
}
}
@Test public void testBatchInsertWithNulls() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
eachConnection(
new ConnectionFunction() {
public void apply(Connection c1) throws Exception {
executeBatchInsertWithNulls(c1);
}
}, getLocalConnection());
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
private void executeBatchInsertWithNulls(Connection conn) throws Exception {
final int numRows = 10;
final String tableName = AvaticaUtils.unique("BATCH_INSERT_EXECUTE_NULLS");
LOG.info("Creating table {}", tableName);
try (Statement stmt = conn.createStatement()) {
final String createCommand =
String.format(Locale.ROOT, "create table if not exists %s ("
+ "id int not null, "
+ "msg varchar(10))", tableName);
assertFalse("Failed to create table", stmt.execute(createCommand));
}
final String insertSql =
String.format(Locale.ROOT, "INSERT INTO %s values(?, ?)", tableName);
try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
// Add batches with the prepared statement
for (int i = 0; i < numRows; i++) {
pstmt.setInt(1, i);
// Even inserts are non-null, odd are null
if (0 == i % 2) {
pstmt.setString(2, Integer.toString(i));
} else {
pstmt.setNull(2, Types.VARCHAR);
}
pstmt.addBatch();
}
// Verify that all updates were successful
int[] updateCounts = pstmt.executeBatch();
assertEquals(numRows, updateCounts.length);
int[] expectedCounts = new int[numRows];
Arrays.fill(expectedCounts, 1);
assertArrayEquals(expectedCounts, updateCounts);
}
try (Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY id asc");
assertNotNull("ResultSet was null", rs);
for (int i = 0; i < numRows; i++) {
assertTrue("ResultSet should have a result", rs.next());
assertEquals("Wrong integer value for row " + i, i, rs.getInt(1));
if (0 == i % 2) {
assertEquals("Wrong string value for row " + i, Integer.toString(i), rs.getString(2));
} else {
assertNull("Expected null value for row " + i, rs.getString(2));
}
}
assertFalse("ResultSet should have no more records", rs.next());
}
}
@Test public void preparedStatementParameterCopies() throws Exception {
// When implementing the JDBC batch APIs, it's important that we are copying the
// TypedValues and caching them in the AvaticaPreparedStatement. Otherwise, when we submit
// the batch, the parameter values for the last update added will be reflected in all previous
// updates added to the batch.
ConnectionSpec.getDatabaseLock().lock();
try {
final String tableName = AvaticaUtils.unique("PREPAREDSTATEMENT_VALUES");
final Connection conn = getLocalConnection();
try (Statement stmt = conn.createStatement()) {
final String sql = "CREATE TABLE " + tableName
+ " (id varchar(1) not null, col1 varchar(1) not null)";
assertFalse(stmt.execute(sql));
}
try (final PreparedStatement pstmt =
conn.prepareStatement("INSERT INTO " + tableName + " values(?, ?)")) {
pstmt.setString(1, "a");
pstmt.setString(2, "b");
@SuppressWarnings("resource")
AvaticaPreparedStatement apstmt = (AvaticaPreparedStatement) pstmt;
TypedValue[] slots = apstmt.slots;
assertEquals("Unexpected number of values", 2, slots.length);
List<TypedValue> valuesReference = apstmt.getParameterValues();
assertEquals(2, valuesReference.size());
assertEquals(slots[0], valuesReference.get(0));
assertEquals(slots[1], valuesReference.get(1));
List<TypedValue> copiedValues = apstmt.copyParameterValues();
assertEquals(2, valuesReference.size());
assertEquals(slots[0], copiedValues.get(0));
assertEquals(slots[1], copiedValues.get(1));
slots[0] = null;
slots[1] = null;
// Modifications to the array are reflected in the List from getParameterValues()
assertNull(valuesReference.get(0));
assertNull(valuesReference.get(1));
// copyParameterValues() copied the underlying array, so updates to slots is not reflected
assertNotNull(copiedValues.get(0));
assertNotNull(copiedValues.get(1));
}
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testBatchInsertWithDates() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
eachConnection(
new ConnectionFunction() {
public void apply(Connection c1) throws Exception {
executeBatchInsertWithDates(c1);
}
}, getLocalConnection());
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
private void executeBatchInsertWithDates(Connection conn) throws Exception {
final Calendar calendar = DateTimeUtils.calendar();
long now = calendar.getTime().getTime();
final int numRows = 10;
final String tableName = AvaticaUtils.unique("BATCH_INSERT_EXECUTE_DATES");
LOG.info("Creating table {}", tableName);
try (Statement stmt = conn.createStatement()) {
final String dropCommand =
String.format(Locale.ROOT, "drop table if exists %s", tableName);
assertFalse("Failed to drop table", stmt.execute(dropCommand));
final String createCommand =
String.format(Locale.ROOT, "create table %s ("
+ "id char(15) not null, "
+ "created_date date not null, "
+ "val_string varchar)", tableName);
assertFalse("Failed to create table", stmt.execute(createCommand));
}
final String insertSql =
String.format(Locale.ROOT, "INSERT INTO %s values(?, ?, ?)", tableName);
try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
// Add batches with the prepared statement
for (int i = 0; i < numRows; i++) {
pstmt.setString(1, Integer.toString(i));
pstmt.setDate(2, new Date(now + i), calendar);
pstmt.setString(3, UUID.randomUUID().toString());
pstmt.addBatch();
}
// Verify that all updates were successful
int[] updateCounts = pstmt.executeBatch();
assertEquals(numRows, updateCounts.length);
int[] expectedCounts = new int[numRows];
Arrays.fill(expectedCounts, 1);
assertArrayEquals(expectedCounts, updateCounts);
}
try (Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY id asc");
assertNotNull("ResultSet was null", rs);
for (int i = 0; i < numRows; i++) {
assertTrue("ResultSet should have a result", rs.next());
assertEquals("Wrong value for row " + i, Integer.toString(i), rs.getString(1).trim());
Date actual = rs.getDate(2, calendar);
calendar.setTime(actual);
int actualDay = calendar.get(Calendar.DAY_OF_MONTH);
int actualMonth = calendar.get(Calendar.MONTH);
int actualYear = calendar.get(Calendar.YEAR);
Date expected = new Date(now + i);
calendar.setTime(expected);
int expectedDay = calendar.get(Calendar.DAY_OF_MONTH);
int expectedMonth = calendar.get(Calendar.MONTH);
int expectedYear = calendar.get(Calendar.YEAR);
assertEquals("Wrong day for row " + i, expectedDay, actualDay);
assertEquals("Wrong month for row " + i, expectedMonth, actualMonth);
assertEquals("Wrong year for row " + i, expectedYear, actualYear);
assertNotNull("Non-null string for row " + i, rs.getString(3));
}
assertFalse("ResultSet should have no more records", rs.next());
}
}
@Test public void testDatabaseMetaData() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try (Connection conn = getLocalConnection()) {
DatabaseMetaData metadata = conn.getMetaData();
assertTrue(metadata.isWrapperFor(AvaticaSpecificDatabaseMetaData.class));
assertTrue(metadata.isWrapperFor(Properties.class));
Properties props = metadata.unwrap(Properties.class);
assertNotNull(props);
final Object productName = props.get(DatabaseProperty.GET_DATABASE_PRODUCT_NAME.name());
assertThat(productName, instanceOf(String.class));
assertThat((String) productName, startsWith("HSQL"));
final Object driverName = props.get(DatabaseProperty.GET_DRIVER_NAME.name());
assertThat(driverName, instanceOf(String.class));
assertThat((String) driverName, startsWith("HSQL"));
final Object driverVersion = props.get(DatabaseProperty.GET_DRIVER_VERSION.name());
final Object driverMinVersion = props.get(DatabaseProperty.GET_DRIVER_MINOR_VERSION.name());
final Object driverMajVersion = props.get(DatabaseProperty.GET_DRIVER_MAJOR_VERSION.name());
assertThat(driverVersion, instanceOf(String.class));
assertThat(driverMinVersion, instanceOf(String.class));
assertThat(driverMajVersion, instanceOf(String.class));
assertThat((String) driverVersion, startsWith(driverMajVersion + "." + driverMinVersion));
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testMetaDataCanHaveEmptyStringArgument() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try (Connection conn = getLocalConnection();
Statement stmt = conn.createStatement()) {
DatabaseMetaData meta = conn.getMetaData();
ResultSet rs = meta.getSchemas("PUBLIC", null);
assertTrue(rs.next());
rs.close();
//Test for CALCITE-4138
rs = meta.getSchemas("PUBLIC", "");
assertFalse(rs.next());
rs.close();
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testUnicodeColumnNames() throws Exception {
final String tableName = "unicodeColumn";
final String columnName = "\u041d\u043e\u043c\u0435\u0440\u0422\u0435\u043b"
+ "\u0435\u0444\u043e\u043d\u0430"; // PhoneNumber in Russian
ConnectionSpec.getDatabaseLock().lock();
try (Connection conn = getLocalConnection();
Statement stmt = conn.createStatement()) {
assertFalse(stmt.execute("DROP TABLE IF EXISTS " + tableName));
final String sql = "CREATE TABLE " + tableName + "(" + columnName + " integer)";
assertFalse(stmt.execute(sql));
final ResultSet results = stmt.executeQuery("SELECT * FROM " + tableName);
assertNotNull(results);
ResultSetMetaData metadata = results.getMetaData();
assertNotNull(metadata);
String actualColumnName = metadata.getColumnName(1);
// HSQLDB is going to upper-case the column name
assertEquals(columnName.toUpperCase(Locale.ROOT), actualColumnName);
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
@Test public void testBigDecimalPrecision() throws Exception {
final String tableName = "decimalPrecision";
// DECIMAL(25,5), 20 before, 5 after
BigDecimal decimal = new BigDecimal("12345123451234512345.09876");
try (Connection conn = getLocalConnection();
Statement stmt = conn.createStatement()) {
assertFalse(stmt.execute("DROP TABLE IF EXISTS " + tableName));
assertFalse(stmt.execute("CREATE TABLE " + tableName + " (col1 DECIMAL(25,5))"));
// Insert a single decimal
try (PreparedStatement pstmt = conn.prepareStatement("INSERT INTO " + tableName
+ " values (?)")) {
pstmt.setBigDecimal(1, decimal);
assertEquals(1, pstmt.executeUpdate());
}
ResultSet results = stmt.executeQuery("SELECT * FROM " + tableName);
assertNotNull(results);
assertTrue(results.next());
BigDecimal actualDecimal = results.getBigDecimal(1);
assertEquals(decimal, actualDecimal);
}
}
@Test public void testPreparedClearBatches() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
eachConnection(
new ConnectionFunction() {
public void apply(Connection c1) throws Exception {
executePreparedBatchClears(c1);
}
}, getLocalConnection());
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
private void executePreparedBatchClears(Connection conn) throws Exception {
final int numRows = 10;
final String tableName = AvaticaUtils.unique("BATCH_CLEARS");
LOG.info("Creating table {}", tableName);
try (Statement stmt = conn.createStatement()) {
final String createCommand =
String.format(Locale.ROOT, "create table if not exists %s ("
+ "id int not null, "
+ "msg varchar(10) not null)", tableName);
assertFalse("Failed to create table", stmt.execute(createCommand));
}
final String insertSql =
String.format(Locale.ROOT, "INSERT INTO %s values(?, ?)", tableName);
try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
// Add batches with the prepared statement
for (int i = 0; i < numRows; i++) {
pstmt.setInt(1, i);
pstmt.setString(2, Integer.toString(i));
pstmt.addBatch();
if (numRows / 2 - 1 == i) {
// Clear the first 5 entries in the batch
pstmt.clearBatch();
}
}
int[] updateCounts = pstmt.executeBatch();
assertEquals("Unexpected number of update counts returned", numRows / 2, updateCounts.length);
for (int i = 0; i < updateCounts.length; i++) {
assertEquals("Unexpected update count at index " + i, 1, updateCounts[i]);
}
}
try (Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY id asc");
assertNotNull("ResultSet was null", rs);
for (int i = 0 + (numRows / 2); i < numRows; i++) {
assertTrue("ResultSet should have a result", rs.next());
assertEquals("Wrong integer value for row " + i, i, rs.getInt(1));
assertEquals("Wrong string value for row " + i, Integer.toString(i), rs.getString(2));
}
assertFalse("ResultSet should have no more records", rs.next());
}
}
@Test public void testBatchClear() throws Exception {
ConnectionSpec.getDatabaseLock().lock();
try {
eachConnection(
new ConnectionFunction() {
public void apply(Connection c1) throws Exception {
executeBatchClear(c1);
}
}, getLocalConnection());
} finally {
ConnectionSpec.getDatabaseLock().unlock();
}
}
private void executeBatchClear(Connection conn) throws Exception {
final int numRows = 10;
try (Statement stmt = conn.createStatement()) {
final String tableName = AvaticaUtils.unique("BATCH_EXECUTE");
LOG.info("Creating table {}", tableName);
final String createCommand =
String.format(Locale.ROOT, "create table if not exists %s ("
+ "id int not null, "
+ "msg varchar(10) not null)", tableName);
assertFalse("Failed to create table", stmt.execute(createCommand));
final String updatePrefix =
String.format(Locale.ROOT, "INSERT INTO %s values(", tableName);
for (int i = 0; i < numRows; i++) {
stmt.addBatch(updatePrefix + i + ", '" + Integer.toString(i) + "')");
if (numRows / 2 - 1 == i) {
stmt.clearBatch();
}
}
int[] updateCounts = stmt.executeBatch();
assertEquals("Unexpected number of update counts returned", numRows / 2, updateCounts.length);
for (int i = 0; i < updateCounts.length; i++) {
assertEquals("Unexpected update count at index " + i, 1, updateCounts[i]);
}
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY id asc");
assertNotNull("ResultSet was null", rs);
for (int i = 0 + (numRows / 2); i < numRows; i++) {
assertTrue("ResultSet should have a result", rs.next());
assertEquals("Wrong integer value for row " + i, i, rs.getInt(1));
assertEquals("Wrong string value for row " + i, Integer.toString(i), rs.getString(2));
}
assertFalse("ResultSet should have no more records", rs.next());
}
}
@Test public void testDecimalParameters() throws Exception {
final String tableName = "decimalParameters";
BigDecimal decimal = new BigDecimal("123451234512345");
try (Connection conn = getLocalConnection();
Statement stmt = conn.createStatement()) {
assertFalse(stmt.execute("DROP TABLE IF EXISTS " + tableName));
String sql = "CREATE TABLE " + tableName + " (keycolumn VARCHAR(5), column1 DECIMAL(15,0))";
assertFalse(stmt.execute(sql));
// Insert a single decimal
try (PreparedStatement pstmt = conn.prepareStatement("INSERT INTO " + tableName
+ " values (?, ?)")) {
ParameterMetaData metadata = pstmt.getParameterMetaData();
assertNotNull(metadata);
assertEquals(5, metadata.getPrecision(1));
assertEquals(0, metadata.getScale(1));
assertEquals(15, metadata.getPrecision(2));
assertEquals(0, metadata.getScale(2));
pstmt.setString(1, "asdfg");
pstmt.setBigDecimal(2, decimal);
assertEquals(1, pstmt.executeUpdate());
}
ResultSet results = stmt.executeQuery("SELECT * FROM " + tableName);
assertNotNull(results);
assertTrue(results.next());
BigDecimal actualDecimal = results.getBigDecimal(2);
assertEquals(decimal, actualDecimal);
ResultSetMetaData resultMetadata = results.getMetaData();
assertNotNull(resultMetadata);
assertEquals(15, resultMetadata.getPrecision(2));
assertEquals(0, resultMetadata.getScale(2));
}
}
@Test public void testSignedParameters() throws Exception {
final String tableName = "signedParameters";
try (Connection conn = getLocalConnection();
Statement stmt = conn.createStatement()) {
assertFalse(stmt.execute("DROP TABLE IF EXISTS " + tableName));
String sql = "CREATE TABLE " + tableName + " (keycolumn VARCHAR(5), column1 integer)";
assertFalse(stmt.execute(sql));
// Insert a single decimal
try (PreparedStatement pstmt = conn.prepareStatement("INSERT INTO " + tableName
+ " values (?, ?)")) {
ParameterMetaData metadata = pstmt.getParameterMetaData();
assertNotNull(metadata);
assertFalse("Varchar should not be signed", metadata.isSigned(1));
assertTrue("Integer should be signed", metadata.isSigned(2));
pstmt.setString(1, "asdfg");
pstmt.setInt(2, 10);
assertEquals(1, pstmt.executeUpdate());
}
ResultSet results = stmt.executeQuery("SELECT * FROM " + tableName);
assertNotNull(results);
assertTrue(results.next());
assertEquals("asdfg", results.getString(1));
assertEquals(10, results.getInt(2));
ResultSetMetaData resultMetadata = results.getMetaData();
assertNotNull(resultMetadata);
assertFalse("Varchar should not be signed", resultMetadata.isSigned(1));
assertTrue("Integer should be signed", resultMetadata.isSigned(2));
}
}
@Test public void testDateParameterWithGMT0() throws Exception {
final String tableName = "dateParameters";
try (Connection conn = getLocalConnection();
Statement stmt = conn.createStatement()) {
assertFalse(stmt.execute("DROP TABLE IF EXISTS " + tableName));
String sql = "CREATE TABLE " + tableName + " (keycolumn VARCHAR(5), column1 date)";
assertFalse(stmt.execute(sql));
TimeZone tzUtc = TimeZone.getTimeZone("UTC");
Calendar cUtc = Calendar.getInstance(tzUtc, Locale.ROOT);
cUtc.set(Calendar.YEAR, 1970);
cUtc.set(Calendar.MONTH, Calendar.JANUARY);
cUtc.set(Calendar.DAY_OF_MONTH, 1);
cUtc.set(Calendar.HOUR_OF_DAY, 0);
cUtc.set(Calendar.MINUTE, 0);
cUtc.set(Calendar.SECOND, 0);
cUtc.set(Calendar.MILLISECOND, 0);
Date inputDate = new Date(cUtc.getTimeInMillis());
// Insert a single date
try (PreparedStatement pstmt = conn.prepareStatement("INSERT INTO " + tableName
+ " values (?, ?)")) {
ParameterMetaData metadata = pstmt.getParameterMetaData();
assertNotNull(metadata);
pstmt.setString(1, "asdfg");
pstmt.setDate(2, inputDate, cUtc);
assertEquals(1, pstmt.executeUpdate());
}
ResultSet results = stmt.executeQuery("SELECT * FROM " + tableName);
assertNotNull(results);
assertTrue(results.next());
assertEquals("asdfg", results.getString(1));
Date outputDate = results.getDate(2, cUtc);
assertEquals(inputDate.getTime(), outputDate.getTime());
assertEquals(0, outputDate.getTime());
}
}
@Test public void testDateParameterWithGMTN() throws Exception {
final String tableName = "dateParameters";
try (Connection conn = getLocalConnection();
Statement stmt = conn.createStatement()) {
assertFalse(stmt.execute("DROP TABLE IF EXISTS " + tableName));
String sql = "CREATE TABLE " + tableName + " (keycolumn VARCHAR(5), column1 date)";
assertFalse(stmt.execute(sql));
TimeZone tzUtc = TimeZone.getTimeZone("GMT+8");
Calendar cUtc = Calendar.getInstance(tzUtc, Locale.ROOT);
cUtc.set(Calendar.YEAR, 1970);
cUtc.set(Calendar.MONTH, Calendar.JANUARY);
cUtc.set(Calendar.DAY_OF_MONTH, 1);
cUtc.set(Calendar.HOUR_OF_DAY, 0);
cUtc.set(Calendar.MINUTE, 0);
cUtc.set(Calendar.SECOND, 0);
cUtc.set(Calendar.MILLISECOND, 0);
Date inputDate = new Date(cUtc.getTimeInMillis());
// Insert a single date
try (PreparedStatement pstmt = conn.prepareStatement("INSERT INTO " + tableName
+ " values (?, ?)")) {
ParameterMetaData metadata = pstmt.getParameterMetaData();
assertNotNull(metadata);
pstmt.setString(1, "gfdsa");
pstmt.setDate(2, inputDate, cUtc);
assertEquals(1, pstmt.executeUpdate());
}
ResultSet results = stmt.executeQuery("SELECT * FROM " + tableName);
assertNotNull(results);
assertTrue(results.next());
assertEquals("gfdsa", results.getString(1));
Date outputDate = results.getDate(2, cUtc);
assertEquals(inputDate.getTime(), outputDate.getTime());
assertEquals(-28800000, outputDate.getTime());
}
}
/**
* 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 fully-local Protobuf service.
*/
public static class QuasiRemotePBJdbcServiceFactory implements Service.Factory {
private static Service service;
private static RequestInspection requestInspection;
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 LoggingLocalProtobufService(localService, new ProtobufTranslationImpl());
requestInspection = (RequestInspection) service;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override public Service create(AvaticaConnection connection) {
assert null != service;
return service;
}
}
/**
* Proxy that logs all requests passed into the {@link LocalProtobufService}.
*/
public static class LoggingLocalProtobufService extends LocalProtobufService
implements RequestInspection {
private static final ThreadLocal<RequestLogger> THREAD_LOG =
new ThreadLocal<RequestLogger>() {
@Override protected RequestLogger initialValue() {
return new RequestLogger();
}
};
public LoggingLocalProtobufService(Service service, ProtobufTranslation translation) {
super(service, translation);
}
@Override public RequestLogger getRequestLogger() {
return THREAD_LOG.get();
}
@Override public Response _apply(Request request) {
final RequestLogger logger = THREAD_LOG.get();
try {
String jsonRequest = JsonService.MAPPER.writeValueAsString(request);
logger.requestStart(jsonRequest);
Response response = super._apply(request);
String jsonResponse = JsonService.MAPPER.writeValueAsString(response);
logger.requestEnd(jsonRequest, jsonResponse);
return response;
} catch (Exception 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;
private static RequestInspection requestInspection;
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 LoggingLocalJsonService(localService);
requestInspection = (RequestInspection) service;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override public Service create(AvaticaConnection connection) {
assert service != null;
return service;
}
}
/**
* Implementation that reaches into current connection state via reflection to extract certain
* internal information.
*/
public static class QuasiRemoteJdbcServiceInternals implements ConnectionInternals {
@Override public 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);
// Use the explicitly class to avoid issues with LoggingLocalJsonService
Field remoteMetaServiceServiceF = LocalJsonService.class.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
@SuppressWarnings("unchecked")
Cache<Integer, Object> cache = (Cache<Integer, Object>) jdbcMetaStatementMapF.get(serverMeta);
return cache;
}
@Override public 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);
// Get the field explicitly off the correct class to avoid LocalLoggingJsonService.class
Field remoteMetaServiceServiceF = LocalJsonService.class.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
@SuppressWarnings("unchecked")
Cache<String, Connection> cache =
(Cache<String, Connection>) jdbcMetaConnectionCacheF.get(serverMeta);
return cache;
}
}
/**
* Implementation that reaches into current connection state via reflection to extract certain
* internal information.
*/
public static class QuasiRemoteProtobufJdbcServiceInternals implements ConnectionInternals {
@Override public 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);
LocalProtobufService remoteMetaService =
(LocalProtobufService) remoteMetaServiceF.get(clientMeta);
// Use the explicitly class to avoid issues with LoggingLocalJsonService
Field remoteMetaServiceServiceF = LocalProtobufService.class.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
@SuppressWarnings("unchecked")
Cache<Integer, Object> cache = (Cache<Integer, Object>) jdbcMetaStatementMapF.get(serverMeta);
return cache;
}
@Override public 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);
LocalProtobufService remoteMetaService =
(LocalProtobufService) remoteMetaServiceF.get(clientMeta);
// Get the field explicitly off the correct class to avoid LocalLoggingJsonService.class
Field remoteMetaServiceServiceF = LocalProtobufService.class.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
@SuppressWarnings("unchecked")
Cache<String, Connection> cache =
(Cache<String, Connection>) jdbcMetaConnectionCacheF.get(serverMeta);
return cache;
}
}
/**
* Provides access to a log of requests.
*/
interface RequestInspection {
RequestLogger getRequestLogger();
}
/** Extension to {@link LocalJsonService} that writes requests and responses
* into a thread-local. */
private static class LoggingLocalJsonService extends LocalJsonService
implements RequestInspection {
private static final ThreadLocal<RequestLogger> THREAD_LOG =
new ThreadLocal<RequestLogger>() {
@Override protected RequestLogger initialValue() {
return new RequestLogger();
}
};
private LoggingLocalJsonService(LocalService localService) {
super(localService);
}
@Override public String apply(String request) {
final RequestLogger logger = THREAD_LOG.get();
logger.requestStart(request);
final String response = super.apply(request);
logger.requestEnd(request, response);
return response;
}
@Override public RequestLogger getRequestLogger() {
return THREAD_LOG.get();
}
}
/** Logs request and response strings if enabled. */
private static class RequestLogger {
final List<String[]> requestResponses = new ArrayList<>();
boolean enabled;
void enableAndClear() {
enabled = true;
requestResponses.clear();
}
void requestStart(String request) {
if (enabled) {
requestResponses.add(new String[]{request, null});
}
}
void requestEnd(String request, String response) {
if (enabled) {
String[] last = requestResponses.get(requestResponses.size() - 1);
if (!request.equals(last[0])) {
throw new AssertionError();
}
last[1] = response;
}
}
List<String[]> getAndDisable() {
enabled = false;
return new ArrayList<>(requestResponses);
}
}
}
// End RemoteDriverTest.java