diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index def8b3a2..29d933b0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [7, 8, 11] + java: [8, 11] steps: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 diff --git a/.kokoro/nightly/java7.cfg b/.kokoro/nightly/java7.cfg deleted file mode 100644 index cb24f44e..00000000 --- a/.kokoro/nightly/java7.cfg +++ /dev/null @@ -1,7 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/java7" -} diff --git a/.kokoro/presubmit/java7.cfg b/.kokoro/presubmit/java7.cfg deleted file mode 100644 index cb24f44e..00000000 --- a/.kokoro/presubmit/java7.cfg +++ /dev/null @@ -1,7 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/java7" -} diff --git a/pom.xml b/pom.xml index e7504f55..1fcd4358 100644 --- a/pom.xml +++ b/pom.xml @@ -197,6 +197,18 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + UTF-8 + -Xlint:unchecked + -Xlint:deprecation + true + + org.apache.maven.plugins maven-surefire-plugin diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java index 09409979..0507d5d5 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java @@ -31,6 +31,7 @@ import java.sql.ResultSet; import java.sql.RowId; import java.sql.SQLException; +import java.sql.SQLType; import java.sql.SQLXML; import java.sql.Time; import java.sql.Timestamp; @@ -321,6 +322,21 @@ public void setObject(int parameterIndex, Object value, int targetSqlType, int s parameters.setParameter(parameterIndex, value, targetSqlType, scaleOrLength); } + @Override + public void setObject(int parameterIndex, Object value, SQLType targetSqlType) + throws SQLException { + checkClosed(); + parameters.setParameter(parameterIndex, value, targetSqlType.getVendorTypeNumber()); + } + + @Override + public void setObject(int parameterIndex, Object value, SQLType targetSqlType, int scaleOrLength) + throws SQLException { + checkClosed(); + parameters.setParameter( + parameterIndex, value, targetSqlType.getVendorTypeNumber(), scaleOrLength); + } + @Override public void setAsciiStream(int parameterIndex, InputStream value, long length) throws SQLException { diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcResultSet.java b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcResultSet.java index 6016bd2c..74d5ccde 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcResultSet.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcResultSet.java @@ -29,6 +29,7 @@ import java.sql.RowId; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLType; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Time; @@ -233,11 +234,22 @@ public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQ throw new SQLFeatureNotSupportedException(); } + @Override + public void updateObject(int columnIndex, Object x, SQLType type, int scaleOrLength) + throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + @Override public void updateObject(int columnIndex, Object x) throws SQLException { throw new SQLFeatureNotSupportedException(); } + @Override + public void updateObject(int columnIndex, Object x, SQLType type) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + @Override public void updateNull(String columnLabel) throws SQLException { throw new SQLFeatureNotSupportedException(); @@ -335,6 +347,17 @@ public void updateObject(String columnLabel, Object x) throws SQLException { throw new SQLFeatureNotSupportedException(); } + @Override + public void updateObject(String columnLabel, Object x, SQLType type) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public void updateObject(String columnLabel, Object x, SQLType type, int scaleOrLength) + throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + @Override public void insertRow() throws SQLException { throw new SQLFeatureNotSupportedException(); diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java index 5217396f..9863f47c 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java @@ -201,20 +201,32 @@ private ResultSet executeQuery( * Executes a SQL statement on the connection of this {@link Statement} as an update (DML) * statement. * - * @param statement The SQL statement to execute. - * @return the number of rows that was inserted/updated/deleted. + * @param statement The SQL statement to execute + * @return the number of rows that was inserted/updated/deleted * @throws SQLException if a database error occurs, or if the number of rows affected is larger - * than {@link Integer#MAX_VALUE}. + * than {@link Integer#MAX_VALUE} */ int executeUpdate(com.google.cloud.spanner.Statement statement) throws SQLException { + long count = executeLargeUpdate(statement); + if (count > Integer.MAX_VALUE) { + throw JdbcSqlExceptionFactory.of( + "update count too large for executeUpdate: " + count, Code.OUT_OF_RANGE); + } + return (int) count; + } + + /** + * Executes a SQL statement on the connection of this {@link Statement} as an update (DML) + * statement. + * + * @param statement The SQL statement to execute + * @return the number of rows that was inserted/updated/deleted + * @throws SQLException if a database error occurs + */ + long executeLargeUpdate(com.google.cloud.spanner.Statement statement) throws SQLException { StatementTimeout originalTimeout = setTemporaryStatementTimeout(); try { - long count = connection.getSpannerConnection().executeUpdate(statement); - if (count > Integer.MAX_VALUE) { - throw JdbcSqlExceptionFactory.of( - "update count too large for executeUpdate: " + count, Code.OUT_OF_RANGE); - } - return (int) count; + return connection.getSpannerConnection().executeUpdate(statement); } catch (SpannerException e) { throw JdbcSqlExceptionFactory.of(e); } finally { @@ -357,11 +369,22 @@ public int getMaxRows() throws SQLException { return 0; } + @Override + public long getLargeMaxRows() throws SQLException { + checkClosed(); + return 0L; + } + @Override public void setMaxRows(int max) throws SQLException { checkClosed(); } + @Override + public void setLargeMaxRows(long max) throws SQLException { + checkClosed(); + } + @Override public void setEscapeProcessing(boolean enable) throws SQLException { checkClosed(); diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java index 65556687..9f975245 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java @@ -66,7 +66,8 @@ static String getSpannerTypeName(int sqlType) { if (sqlType == Types.NUMERIC || sqlType == Types.DECIMAL) return Type.numeric().getCode().name(); if (sqlType == Types.NVARCHAR) return Type.string().getCode().name(); - if (sqlType == Types.TIMESTAMP) return Type.timestamp().getCode().name(); + if (sqlType == Types.TIMESTAMP || sqlType == Types.TIMESTAMP_WITH_TIMEZONE) + return Type.timestamp().getCode().name(); if (sqlType == Types.ARRAY) return Code.ARRAY.name(); return OTHER_NAME; @@ -84,7 +85,8 @@ static String getClassName(int sqlType) { || sqlType == Types.TINYINT) return Long.class.getName(); if (sqlType == Types.NUMERIC || sqlType == Types.DECIMAL) return BigDecimal.class.getName(); if (sqlType == Types.NVARCHAR) return String.class.getName(); - if (sqlType == Types.TIMESTAMP) return Timestamp.class.getName(); + if (sqlType == Types.TIMESTAMP || sqlType == Types.TIMESTAMP_WITH_TIMEZONE) + return Timestamp.class.getName(); if (sqlType == Types.ARRAY) return Object.class.getName(); return null; diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java index a5d79b14..191d991e 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java @@ -1630,4 +1630,15 @@ public ResultSet getPseudoColumns( public boolean generatedKeyAlwaysReturned() throws SQLException { return false; } + + @Override + public long getMaxLogicalLobSize() throws SQLException { + // BYTES(MAX) + return 10485760L; + } + + @Override + public boolean supportsRefCursors() throws SQLException { + return false; + } } diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java index 3f4a4335..dbb2df5c 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java @@ -106,7 +106,8 @@ */ public class JdbcDriver implements Driver { private static final String JDBC_API_CLIENT_LIB_TOKEN = "sp-jdbc"; - static final int MAJOR_VERSION = 1; + // Updated to version 2 when upgraded to Java 8 (JDBC 4.2) + static final int MAJOR_VERSION = 2; static final int MINOR_VERSION = 0; private static final String JDBC_URL_FORMAT = "jdbc:" + ConnectionOptions.Builder.SPANNER_URI_FORMAT; @@ -245,12 +246,12 @@ private String parseUriProperty(String uri, String property, String defaultValue @Override public int getMajorVersion() { - return 1; + return MAJOR_VERSION; } @Override public int getMinorVersion() { - return 0; + return MINOR_VERSION; } @Override diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java index 10de0f83..2c529c4e 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterStore.java @@ -204,7 +204,9 @@ private boolean isTypeSupported(int sqlType) { case Types.LONGNVARCHAR: case Types.DATE: case Types.TIME: + case Types.TIME_WITH_TIMEZONE: case Types.TIMESTAMP: + case Types.TIMESTAMP_WITH_TIMEZONE: case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: @@ -249,7 +251,9 @@ private boolean isValidTypeAndValue(Object value, int sqlType) { || value instanceof URL; case Types.DATE: case Types.TIME: + case Types.TIME_WITH_TIMEZONE: case Types.TIMESTAMP: + case Types.TIMESTAMP_WITH_TIMEZONE: return value instanceof Date || value instanceof Time || value instanceof Timestamp; case Types.BINARY: case Types.VARBINARY: @@ -522,7 +526,9 @@ private Builder setParamWithKnownType(ValueBinder binder, Object value, } throw JdbcSqlExceptionFactory.of(value + " is not a valid date", Code.INVALID_ARGUMENT); case Types.TIME: + case Types.TIME_WITH_TIMEZONE: case Types.TIMESTAMP: + case Types.TIMESTAMP_WITH_TIMEZONE: if (value instanceof Date) { return binder.to(JdbcTypeConverter.toGoogleTimestamp((Date) value)); } else if (value instanceof Time) { @@ -715,7 +721,9 @@ private Builder setArrayValue(ValueBinder binder, int type, Object valu case Types.DATE: return binder.toDateArray((Iterable) null); case Types.TIME: + case Types.TIME_WITH_TIMEZONE: case Types.TIMESTAMP: + case Types.TIMESTAMP_WITH_TIMEZONE: return binder.toTimestampArray((Iterable) null); case Types.BINARY: case Types.VARBINARY: @@ -843,8 +851,9 @@ private Builder setNullValue(ValueBinder binder, Integer sqlType) throw case Types.SQLXML: return binder.to((String) null); case Types.TIME: - return binder.to((com.google.cloud.Timestamp) null); + case Types.TIME_WITH_TIMEZONE: case Types.TIMESTAMP: + case Types.TIMESTAMP_WITH_TIMEZONE: return binder.to((com.google.cloud.Timestamp) null); case Types.TINYINT: return binder.to((Long) null); diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java index d8eeb623..b0032e44 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java @@ -74,6 +74,11 @@ public int executeUpdate() throws SQLException { return executeUpdate(createStatement()); } + public long executeLargeUpdate() throws SQLException { + checkClosed(); + return executeLargeUpdate(createStatement()); + } + @Override public boolean execute() throws SQLException { checkClosed(); diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcSqlExceptionFactory.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcSqlExceptionFactory.java index b7d1130d..33d3bb42 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcSqlExceptionFactory.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcSqlExceptionFactory.java @@ -127,6 +127,16 @@ private JdbcSqlBatchUpdateException(int[] updateCounts, SpannerBatchUpdateExcept this.code = Code.forNumber(cause.getCode()); } + private JdbcSqlBatchUpdateException(long[] updateCounts, SpannerBatchUpdateException cause) { + super( + cause.getMessage(), + cause.getErrorCode().toString(), + cause.getCode(), + updateCounts, + cause); + this.code = Code.forNumber(cause.getCode()); + } + @Override public Code getCode() { return code; @@ -315,4 +325,10 @@ static BatchUpdateException batchException( int[] updateCounts, SpannerBatchUpdateException cause) { return new JdbcSqlBatchUpdateException(updateCounts, cause); } + + /** Creates a {@link JdbcSqlException} for large batch update exceptions. */ + static BatchUpdateException batchException( + long[] updateCounts, SpannerBatchUpdateException cause) { + return new JdbcSqlBatchUpdateException(updateCounts, cause); + } } diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcStatement.java index b4eaa0d1..5a8f6916 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcStatement.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcStatement.java @@ -68,6 +68,21 @@ public ResultSet executeQuery(String sql) throws SQLException { */ @Override public int executeUpdate(String sql) throws SQLException { + long result = executeLargeUpdate(sql); + if (result > Integer.MAX_VALUE) { + throw JdbcSqlExceptionFactory.of("update count too large: " + result, Code.OUT_OF_RANGE); + } + return (int) result; + } + + /** + * @see java.sql.Statement#executeLargeUpdate(String) + *

This method allows both DML and DDL statements to be executed. It assumes that the user + * knows what kind of statement is being executed, and the method will therefore return 0 for + * both DML statements that changed 0 rows as well as for all DDL statements. + */ + @Override + public long executeLargeUpdate(String sql) throws SQLException { checkClosed(); Statement statement = Statement.of(sql); StatementResult result = execute(statement); @@ -76,13 +91,9 @@ public int executeUpdate(String sql) throws SQLException { throw JdbcSqlExceptionFactory.of( "The statement is not an update or DDL statement", Code.INVALID_ARGUMENT); case UPDATE_COUNT: - if (result.getUpdateCount() > Integer.MAX_VALUE) { - throw JdbcSqlExceptionFactory.of( - "update count too large: " + result.getUpdateCount(), Code.OUT_OF_RANGE); - } - return result.getUpdateCount().intValue(); + return result.getUpdateCount(); case NO_RESULT: - return 0; + return 0L; default: throw JdbcSqlExceptionFactory.of( "unknown result: " + result.getResultType(), Code.FAILED_PRECONDITION); @@ -138,6 +149,18 @@ public int getUpdateCount() throws SQLException { return (int) currentUpdateCount; } + /** + * Returns the update count of the last update statement as a {@link Long}. Will return {@link + * JdbcConstants#STATEMENT_RESULT_SET} if the last statement returned a {@link ResultSet} and will + * return {@link JdbcConstants#STATEMENT_NO_RESULT} if the last statement did not have any return + * value, such as for example DDL statements. + */ + @Override + public long getLargeUpdateCount() throws SQLException { + checkClosed(); + return currentUpdateCount; + } + @Override public boolean getMoreResults() throws SQLException { checkClosed(); @@ -234,19 +257,28 @@ public void clearBatch() throws SQLException { @Override public int[] executeBatch() throws SQLException { + return convertUpdateCounts(executeBatch(false)); + } + + public long[] executeLargeBatch() throws SQLException { + return executeBatch(true); + } + + private long[] executeBatch(boolean large) throws SQLException { checkClosed(); checkConnectionHasNoActiveBatch(); try { switch (this.currentBatchType) { case DML: try { - long[] updateCounts = - getConnection().getSpannerConnection().executeBatchUpdate(batchedStatements); - int[] res = convertUpdateCounts(updateCounts); - return res; + return getConnection().getSpannerConnection().executeBatchUpdate(batchedStatements); } catch (SpannerBatchUpdateException e) { - int[] updateCounts = convertUpdateCounts(e.getUpdateCounts()); - throw JdbcSqlExceptionFactory.batchException(updateCounts, e); + if (large) { + throw JdbcSqlExceptionFactory.batchException(e.getUpdateCounts(), e); + } else { + throw JdbcSqlExceptionFactory.batchException( + convertUpdateCounts(e.getUpdateCounts()), e); + } } catch (SpannerException e) { throw JdbcSqlExceptionFactory.of(e); } @@ -257,20 +289,24 @@ public int[] executeBatch() throws SQLException { execute(statement); } getConnection().getSpannerConnection().runBatch(); - int[] res = new int[batchedStatements.size()]; + long[] res = new long[batchedStatements.size()]; Arrays.fill(res, java.sql.Statement.SUCCESS_NO_INFO); return res; } catch (SpannerBatchUpdateException e) { - int[] res = new int[batchedStatements.size()]; + long[] res = new long[batchedStatements.size()]; Arrays.fill(res, java.sql.Statement.EXECUTE_FAILED); convertUpdateCountsToSuccessNoInfo(e.getUpdateCounts(), res); - throw JdbcSqlExceptionFactory.batchException(res, e); + if (large) { + throw JdbcSqlExceptionFactory.batchException(res, e); + } else { + throw JdbcSqlExceptionFactory.batchException(convertUpdateCounts(res), e); + } } catch (SpannerException e) { throw JdbcSqlExceptionFactory.of(e); } case NONE: // There is no batch on this statement, this is a no-op. - return new int[0]; + return new long[0]; default: throw JdbcSqlExceptionFactory.unsupported( String.format("Unknown batch type: %s", this.currentBatchType.name())); @@ -296,16 +332,11 @@ int[] convertUpdateCounts(long[] updateCounts) throws SQLException { } @VisibleForTesting - void convertUpdateCountsToSuccessNoInfo(long[] updateCounts, int[] res) throws SQLException { + void convertUpdateCountsToSuccessNoInfo(long[] updateCounts, long[] res) throws SQLException { Preconditions.checkNotNull(updateCounts); Preconditions.checkNotNull(res); Preconditions.checkArgument(res.length >= updateCounts.length); for (int index = 0; index < updateCounts.length; index++) { - if (updateCounts[index] > Integer.MAX_VALUE) { - throw JdbcSqlExceptionFactory.of( - String.format("Update count too large for int: %d", updateCounts[index]), - Code.OUT_OF_RANGE); - } if (updateCounts[index] > 0L) { res[index] = java.sql.Statement.SUCCESS_NO_INFO; } else { @@ -349,6 +380,27 @@ public int executeUpdate(String sql, String[] columnNames) throws SQLException { return executeUpdate(sql); } + @Override + public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + checkClosed(); + JdbcPreconditions.checkSqlFeatureSupported( + autoGeneratedKeys == java.sql.Statement.NO_GENERATED_KEYS, + JdbcConnection.ONLY_NO_GENERATED_KEYS); + return executeLargeUpdate(sql); + } + + @Override + public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException { + checkClosed(); + return executeLargeUpdate(sql); + } + + @Override + public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException { + checkClosed(); + return executeLargeUpdate(sql); + } + @Override public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { checkClosed(); diff --git a/src/test/java/com/google/cloud/spanner/jdbc/AbstractJdbcResultSetTest.java b/src/test/java/com/google/cloud/spanner/jdbc/AbstractJdbcResultSetTest.java index 960287ce..2be79bf2 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/AbstractJdbcResultSetTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/AbstractJdbcResultSetTest.java @@ -40,6 +40,7 @@ import java.sql.RowId; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLType; import java.sql.SQLXML; import java.sql.Statement; import java.sql.Time; @@ -508,14 +509,28 @@ public void run() throws SQLException { new SqlRunnable() { @Override public void run() throws SQLException { - rs.updateObject("test", new Object()); + rs.updateObject(1, new Object(), 1); } }); assertUnsupported( new SqlRunnable() { @Override public void run() throws SQLException { - rs.updateObject(1, new Object(), 1); + rs.updateObject(1, new Object(), mock(SQLType.class)); + } + }); + assertUnsupported( + new SqlRunnable() { + @Override + public void run() throws SQLException { + rs.updateObject(1, new Object(), mock(SQLType.class), 0); + } + }); + assertUnsupported( + new SqlRunnable() { + @Override + public void run() throws SQLException { + rs.updateObject("test", new Object()); } }); assertUnsupported( @@ -525,6 +540,20 @@ public void run() throws SQLException { rs.updateObject("test", new Object(), 1); } }); + assertUnsupported( + new SqlRunnable() { + @Override + public void run() throws SQLException { + rs.updateObject("test", new Object(), mock(SQLType.class)); + } + }); + assertUnsupported( + new SqlRunnable() { + @Override + public void run() throws SQLException { + rs.updateObject("test", new Object(), mock(SQLType.class), 1); + } + }); assertUnsupported( new SqlRunnable() { @Override diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java index ac8ab7bf..e7e634a2 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java @@ -20,6 +20,8 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -267,6 +269,8 @@ public void testTrivialMethods() throws SQLException { }) { assertThat(meta.supportsTransactionIsolationLevel(level), is(false)); } + assertEquals(10485760L, meta.getMaxLogicalLobSize()); + assertFalse(meta.supportsRefCursors()); // trivial tests that guarantee that the function works, but the return value doesn't matter assertThat(meta.getNumericFunctions(), is(notNullValue())); diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDriverTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDriverTest.java index 014d9bde..c6709b34 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDriverTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDriverTest.java @@ -17,6 +17,7 @@ package com.google.cloud.spanner.jdbc; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import com.google.cloud.spanner.MockSpannerServiceImpl; @@ -82,6 +83,12 @@ public void testClientLibToken() { assertThat(JdbcDriver.getClientLibToken()).isEqualTo("sp-jdbc"); } + @Test + public void testVersion() throws SQLException { + assertEquals(JdbcDriver.MAJOR_VERSION, JdbcDriver.getRegisteredDriver().getMajorVersion()); + assertEquals(JdbcDriver.MINOR_VERSION, JdbcDriver.getRegisteredDriver().getMinorVersion()); + } + @Test public void testRegister() throws SQLException { assertThat(JdbcDriver.isRegistered()).isTrue(); diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java index 502ef545..7f686013 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcParameterStoreTest.java @@ -84,10 +84,18 @@ public void testSetParameterWithType() throws SQLException, IOException { verifyParameter(params, Value.date(com.google.cloud.Date.fromYearMonthDay(1970, 1, 1))); params.setParameter(1, new Time(0L), Types.TIME); assertEquals(new Time(0L), params.getParameter(1)); + verifyParameter( + params, Value.timestamp(com.google.cloud.Timestamp.ofTimeSecondsAndNanos(0L, 0))); + params.setParameter(1, new Time(0L), Types.TIME_WITH_TIMEZONE); + assertEquals(new Time(0L), params.getParameter(1)); verifyParameter( params, Value.timestamp(com.google.cloud.Timestamp.ofTimeSecondsAndNanos(0L, 0))); params.setParameter(1, new Timestamp(0L), Types.TIMESTAMP); assertEquals(new Timestamp(0L), params.getParameter(1)); + verifyParameter( + params, Value.timestamp(com.google.cloud.Timestamp.ofTimeSecondsAndNanos(0L, 0))); + params.setParameter(1, new Timestamp(0L), Types.TIMESTAMP_WITH_TIMEZONE); + assertEquals(new Timestamp(0L), params.getParameter(1)); verifyParameter( params, Value.timestamp(com.google.cloud.Timestamp.ofTimeSecondsAndNanos(0L, 0))); params.setParameter(1, new byte[] {1, 2, 3}, Types.BINARY); @@ -184,7 +192,10 @@ public void testSetParameterWithType() throws SQLException, IOException { } // types that should lead to timestamp - for (int type : new int[] {Types.TIME, Types.TIMESTAMP}) { + for (int type : + new int[] { + Types.TIME, Types.TIME_WITH_TIMEZONE, Types.TIMESTAMP, Types.TIMESTAMP_WITH_TIMEZONE + }) { params.setParameter(1, new Date(0L), type); assertEquals(new Date(0L), params.getParameter(1)); verifyParameter( @@ -321,7 +332,10 @@ public void testSetInvalidParameterWithType() throws SQLException, IOException { } // types that should lead to timestamp - for (int type : new int[] {Types.TIME, Types.TIMESTAMP}) { + for (int type : + new int[] { + Types.TIME, Types.TIME_WITH_TIMEZONE, Types.TIMESTAMP, Types.TIMESTAMP_WITH_TIMEZONE + }) { assertInvalidParameter(params, "1", type); assertInvalidParameter(params, new Object(), type); assertInvalidParameter(params, Boolean.TRUE, type); diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementTest.java index 5a1235f5..b19aa54c 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementTest.java @@ -39,6 +39,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.sql.Date; +import java.sql.JDBCType; import java.sql.PreparedStatement; import java.sql.Ref; import java.sql.ResultSetMetaData; @@ -101,7 +102,7 @@ private JdbcConnection createMockConnection(Connection spanner) throws SQLExcept @SuppressWarnings("deprecation") @Test public void testParameters() throws SQLException, MalformedURLException { - final int numberOfParams = 48; + final int numberOfParams = 51; String sql = generateSqlWithParameters(numberOfParams); JdbcConnection connection = createMockConnection(); @@ -151,6 +152,8 @@ public void testParameters() throws SQLException, MalformedURLException { ps.setUnicodeStream(47, new ByteArrayInputStream("TEST".getBytes()), 4); ps.setURL(48, new URL("https://spanner.google.com")); ps.setObject(49, UUID.fromString("83b988cf-1f4e-428a-be3d-cc712621942e")); + ps.setObject(50, "TEST", JDBCType.NVARCHAR); + ps.setObject(51, "TEST", JDBCType.NVARCHAR, 20); testSetUnsupportedTypes(ps); @@ -204,6 +207,8 @@ public void testParameters() throws SQLException, MalformedURLException { assertEquals(ByteArrayInputStream.class.getName(), pmd.getParameterClassName(47)); assertEquals(URL.class.getName(), pmd.getParameterClassName(48)); assertEquals(UUID.class.getName(), pmd.getParameterClassName(49)); + assertEquals(String.class.getName(), pmd.getParameterClassName(50)); + assertEquals(String.class.getName(), pmd.getParameterClassName(51)); ps.clearParameters(); pmd = ps.getParameterMetaData(); @@ -237,41 +242,46 @@ private void testSetUnsupportedTypes(PreparedStatement ps) { @Test public void testSetNullValues() throws SQLException { - String sql = generateSqlWithParameters(27); + final int numberOfParameters = 27; + String sql = generateSqlWithParameters(numberOfParameters); try (JdbcPreparedStatement ps = new JdbcPreparedStatement(createMockConnection(), sql)) { - ps.setNull(1, Types.BLOB); - ps.setNull(2, Types.NVARCHAR); - ps.setNull(4, Types.BINARY); - ps.setNull(5, Types.BOOLEAN); - ps.setNull(6, Types.TINYINT); - ps.setNull(7, Types.DATE); - ps.setNull(8, Types.DOUBLE); - ps.setNull(9, Types.FLOAT); - ps.setNull(10, Types.INTEGER); - ps.setNull(11, Types.BIGINT); - ps.setNull(12, Types.SMALLINT); - ps.setNull(13, Types.TIME); - ps.setNull(14, Types.TIMESTAMP); - ps.setNull(15, Types.CHAR); - ps.setNull(16, Types.CLOB); - ps.setNull(17, Types.LONGNVARCHAR); - ps.setNull(18, Types.LONGVARBINARY); - ps.setNull(19, Types.LONGVARCHAR); - ps.setNull(20, Types.NCHAR); - ps.setNull(21, Types.NCLOB); - ps.setNull(23, Types.NVARCHAR); - ps.setNull(24, Types.REAL); - ps.setNull(25, Types.BIT); - ps.setNull(26, Types.VARBINARY); - ps.setNull(27, Types.VARCHAR); + int index = 0; + ps.setNull(++index, Types.BLOB); + ps.setNull(++index, Types.NVARCHAR); + ps.setNull(++index, Types.BINARY); + ps.setNull(++index, Types.BOOLEAN); + ps.setNull(++index, Types.TINYINT); + ps.setNull(++index, Types.DATE); + ps.setNull(++index, Types.DOUBLE); + ps.setNull(++index, Types.FLOAT); + ps.setNull(++index, Types.INTEGER); + ps.setNull(++index, Types.BIGINT); + ps.setNull(++index, Types.SMALLINT); + ps.setNull(++index, Types.TIME); + ps.setNull(++index, Types.TIME_WITH_TIMEZONE); + ps.setNull(++index, Types.TIMESTAMP); + ps.setNull(++index, Types.TIMESTAMP_WITH_TIMEZONE); + ps.setNull(++index, Types.CHAR); + ps.setNull(++index, Types.CLOB); + ps.setNull(++index, Types.LONGNVARCHAR); + ps.setNull(++index, Types.LONGVARBINARY); + ps.setNull(++index, Types.LONGVARCHAR); + ps.setNull(++index, Types.NCHAR); + ps.setNull(++index, Types.NCLOB); + ps.setNull(++index, Types.NVARCHAR); + ps.setNull(++index, Types.REAL); + ps.setNull(++index, Types.BIT); + ps.setNull(++index, Types.VARBINARY); + ps.setNull(++index, Types.VARCHAR); + assertEquals(numberOfParameters, index); JdbcParameterMetaData pmd = ps.getParameterMetaData(); - assertEquals(27, pmd.getParameterCount()); - assertEquals(Timestamp.class.getName(), pmd.getParameterClassName(14)); + assertEquals(numberOfParameters, pmd.getParameterCount()); + assertEquals(Timestamp.class.getName(), pmd.getParameterClassName(15)); ps.clearParameters(); pmd = ps.getParameterMetaData(); - assertEquals(27, pmd.getParameterCount()); + assertEquals(numberOfParameters, pmd.getParameterCount()); } } diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementWithMockedServerTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementWithMockedServerTest.java new file mode 100644 index 00000000..3f36f000 --- /dev/null +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementWithMockedServerTest.java @@ -0,0 +1,197 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed 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 com.google.cloud.spanner.jdbc; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.cloud.spanner.ErrorCode; +import com.google.cloud.spanner.MockSpannerServiceImpl; +import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; +import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.connection.SpannerPool; +import com.google.cloud.spanner.jdbc.JdbcSqlExceptionFactory.JdbcSqlBatchUpdateException; +import io.grpc.Server; +import io.grpc.Status; +import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collection; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class JdbcPreparedStatementWithMockedServerTest { + private static MockSpannerServiceImpl mockSpanner; + private static Server server; + private static InetSocketAddress address; + + @Parameter public boolean executeLarge; + + @Parameters(name = "executeLarge = {0}") + public static Collection data() { + return Arrays.asList(new Object[][] {{false}, {true}}); + } + + @BeforeClass + public static void startStaticServer() throws IOException { + mockSpanner = new MockSpannerServiceImpl(); + mockSpanner.setAbortProbability(0.0D); + address = new InetSocketAddress("localhost", 0); + server = NettyServerBuilder.forAddress(address).addService(mockSpanner).build().start(); + } + + @AfterClass + public static void stopServer() throws Exception { + server.shutdown(); + server.awaitTermination(); + } + + @After + public void reset() { + SpannerPool.closeSpannerPool(); + mockSpanner.removeAllExecutionTimes(); + mockSpanner.reset(); + } + + private String createUrl() { + return String.format( + "jdbc:cloudspanner://localhost:%d/projects/%s/instances/%s/databases/%s?usePlainText=true", + server.getPort(), "proj", "inst", "db"); + } + + private Connection createConnection() throws SQLException { + return DriverManager.getConnection(createUrl()); + } + + @Test + public void testExecuteBatch() throws SQLException { + Statement.Builder insertBuilder = + Statement.newBuilder("INSERT INTO Test (Col1, Col2) VALUES (@p1, @p2)"); + mockSpanner.putStatementResult( + StatementResult.update( + insertBuilder.bind("p1").to(1L).bind("p2").to("test 1").build(), 1L)); + mockSpanner.putStatementResult( + StatementResult.update( + insertBuilder.bind("p1").to(2L).bind("p2").to("test 2").build(), 1L)); + try (Connection connection = createConnection()) { + try (PreparedStatement statement = + connection.prepareStatement("INSERT INTO Test (Col1, Col2) VALUES (?, ?)")) { + statement.setLong(1, 1L); + statement.setString(2, "test 1"); + statement.addBatch(); + statement.setLong(1, 2L); + statement.setString(2, "test 2"); + statement.addBatch(); + if (executeLarge) { + assertThat(statement.executeLargeBatch()).asList().containsExactly(1L, 1L); + } else { + assertThat(statement.executeBatch()).asList().containsExactly(1, 1); + } + } + } + } + + @Test + public void testExecuteBatch_withOverflow() throws SQLException { + Statement.Builder insertBuilder = + Statement.newBuilder("INSERT INTO Test (Col1, Col2) VALUES (@p1, @p2)"); + mockSpanner.putStatementResult( + StatementResult.update( + insertBuilder.bind("p1").to(1L).bind("p2").to("test 1").build(), 1L)); + mockSpanner.putStatementResult( + StatementResult.update( + insertBuilder.bind("p1").to(2L).bind("p2").to("test 2").build(), + Integer.MAX_VALUE + 1L)); + try (Connection connection = createConnection()) { + try (PreparedStatement statement = + connection.prepareStatement("INSERT INTO Test (Col1, Col2) VALUES (?, ?)")) { + statement.setLong(1, 1L); + statement.setString(2, "test 1"); + statement.addBatch(); + statement.setLong(1, 2L); + statement.setString(2, "test 2"); + statement.addBatch(); + if (executeLarge) { + assertThat(statement.executeLargeBatch()) + .asList() + .containsExactly(1L, Integer.MAX_VALUE + 1L); + } else { + try { + statement.executeBatch(); + fail("missing expected OutOfRange exception"); + } catch (SQLException e) { + assertTrue(e instanceof JdbcSqlException); + JdbcSqlException sqlException = (JdbcSqlException) e; + assertEquals( + ErrorCode.OUT_OF_RANGE.getGrpcStatusCode().value(), sqlException.getErrorCode()); + } + } + } + } + } + + @Test + public void testExecuteBatch_withException() throws SQLException { + Statement.Builder insertBuilder = + Statement.newBuilder("INSERT INTO Test (Col1, Col2) VALUES (@p1, @p2)"); + mockSpanner.putStatementResult( + StatementResult.update( + insertBuilder.bind("p1").to(1L).bind("p2").to("test 1").build(), 1L)); + mockSpanner.putStatementResult( + StatementResult.exception( + insertBuilder.bind("p1").to(2L).bind("p2").to("test 2").build(), + Status.ALREADY_EXISTS.asRuntimeException())); + try (Connection connection = createConnection()) { + try (PreparedStatement statement = + connection.prepareStatement("INSERT INTO Test (Col1, Col2) VALUES (?, ?)")) { + statement.setLong(1, 1L); + statement.setString(2, "test 1"); + statement.addBatch(); + statement.setLong(1, 2L); + statement.setString(2, "test 2"); + statement.addBatch(); + try { + if (executeLarge) { + statement.executeLargeBatch(); + } else { + statement.executeBatch(); + } + } catch (JdbcSqlBatchUpdateException e) { + if (executeLarge) { + assertThat(e.getLargeUpdateCounts()).asList().containsExactly(1L); + } else { + assertThat(e.getUpdateCounts()).asList().containsExactly(1); + } + } + } + } + } +} diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java index 7349bfce..ee21dda5 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcStatementTest.java @@ -187,14 +187,15 @@ public void testExecuteWithUpdateStatement() throws SQLException { assertThat(res).isFalse(); assertThat(statement.getResultSet()).isNull(); assertThat(statement.getUpdateCount()).isEqualTo(1); + assertThat(statement.execute(LARGE_UPDATE)).isFalse(); + assertThat(statement.getResultSet()).isNull(); try { - assertThat(statement.execute(LARGE_UPDATE)).isFalse(); - assertThat(statement.getResultSet()).isNull(); statement.getUpdateCount(); fail("missing expected exception"); } catch (JdbcSqlExceptionImpl e) { assertThat(e.getCode()).isEqualTo(Code.OUT_OF_RANGE); } + assertThat(statement.getLargeUpdateCount()).isEqualTo(Integer.MAX_VALUE + 1L); } @Test @@ -270,6 +271,65 @@ public void testExecuteUpdate() throws SQLException { } } + @Test + public void testInternalExecuteUpdate() throws SQLException { + JdbcConnection connection = mock(JdbcConnection.class); + Connection spannerConnection = mock(Connection.class); + when(connection.getSpannerConnection()).thenReturn(spannerConnection); + com.google.cloud.spanner.Statement updateStatement = + com.google.cloud.spanner.Statement.of(UPDATE); + com.google.cloud.spanner.Statement largeUpdateStatement = + com.google.cloud.spanner.Statement.of(LARGE_UPDATE); + when(spannerConnection.executeUpdate(updateStatement)).thenReturn(1L); + when(spannerConnection.executeUpdate(largeUpdateStatement)).thenReturn(Integer.MAX_VALUE + 1L); + try (JdbcStatement statement = new JdbcStatement(connection)) { + assertThat(statement.executeUpdate(updateStatement)).isEqualTo(1); + try { + statement.executeUpdate(largeUpdateStatement); + fail("missing expected exception"); + } catch (JdbcSqlExceptionImpl e) { + assertThat(e.getCode()).isEqualTo(Code.OUT_OF_RANGE); + } + } + } + + @Test + public void testInternalExecuteLargeUpdate() throws SQLException { + JdbcConnection connection = mock(JdbcConnection.class); + Connection spannerConnection = mock(Connection.class); + when(connection.getSpannerConnection()).thenReturn(spannerConnection); + com.google.cloud.spanner.Statement updateStatement = + com.google.cloud.spanner.Statement.of(UPDATE); + com.google.cloud.spanner.Statement largeUpdateStatement = + com.google.cloud.spanner.Statement.of(LARGE_UPDATE); + when(spannerConnection.executeUpdate(updateStatement)).thenReturn(1L); + when(spannerConnection.executeUpdate(largeUpdateStatement)).thenReturn(Integer.MAX_VALUE + 1L); + try (JdbcStatement statement = new JdbcStatement(connection)) { + assertThat(statement.executeLargeUpdate(updateStatement)).isEqualTo(1); + assertThat(statement.executeLargeUpdate(largeUpdateStatement)) + .isEqualTo(Integer.MAX_VALUE + 1L); + } + } + + @Test + public void testExecuteLargeUpdate() throws SQLException { + Statement statement = createStatement(); + assertThat(statement.executeLargeUpdate(UPDATE)).isEqualTo(1L); + assertThat(statement.executeLargeUpdate(LARGE_UPDATE)).isEqualTo(Integer.MAX_VALUE + 1L); + + assertThat(statement.executeLargeUpdate(UPDATE, Statement.NO_GENERATED_KEYS)).isEqualTo(1L); + assertThat(statement.executeLargeUpdate(LARGE_UPDATE, Statement.NO_GENERATED_KEYS)) + .isEqualTo(Integer.MAX_VALUE + 1L); + + assertThat(statement.executeLargeUpdate(UPDATE, new int[0])).isEqualTo(1L); + assertThat(statement.executeLargeUpdate(LARGE_UPDATE, new int[0])) + .isEqualTo(Integer.MAX_VALUE + 1L); + + assertThat(statement.executeLargeUpdate(UPDATE, new String[0])).isEqualTo(1L); + assertThat(statement.executeLargeUpdate(LARGE_UPDATE, new String[0])) + .isEqualTo(Integer.MAX_VALUE + 1L); + } + @Test public void testExecuteUpdateWithSelectStatement() throws SQLException { Statement statement = createStatement(); @@ -364,6 +424,19 @@ public void testDmlBatch() throws SQLException { } } + @Test + public void testLargeDmlBatch() throws SQLException { + try (Statement statement = createStatement()) { + // Verify that multiple batches can be executed on the same statement. + for (int i = 0; i < 2; i++) { + statement.addBatch("INSERT INTO FOO (ID, NAME) VALUES (1, 'TEST')"); + statement.addBatch("INSERT INTO FOO (ID, NAME) VALUES (2, 'TEST')"); + statement.addBatch("INSERT INTO FOO (ID, NAME) VALUES (3, 'TEST')"); + assertThat(statement.executeLargeBatch()).asList().containsExactly(1L, 1L, 1L); + } + } + } + @Test public void testConvertUpdateCounts() throws SQLException { try (JdbcStatement statement = new JdbcStatement(mock(JdbcConnection.class))) { @@ -382,30 +455,39 @@ public void testConvertUpdateCounts() throws SQLException { @Test public void testConvertUpdateCountsToSuccessNoInfo() throws SQLException { try (JdbcStatement statement = new JdbcStatement(mock(JdbcConnection.class))) { - int[] updateCounts = new int[3]; + long[] updateCounts = new long[3]; statement.convertUpdateCountsToSuccessNoInfo(new long[] {1L, 2L, 3L}, updateCounts); assertThat(updateCounts) .asList() .containsExactly( - Statement.SUCCESS_NO_INFO, Statement.SUCCESS_NO_INFO, Statement.SUCCESS_NO_INFO); + Long.valueOf(Statement.SUCCESS_NO_INFO), + Long.valueOf(Statement.SUCCESS_NO_INFO), + Long.valueOf(Statement.SUCCESS_NO_INFO)); statement.convertUpdateCountsToSuccessNoInfo(new long[] {0L, 0L, 0L}, updateCounts); assertThat(updateCounts) .asList() .containsExactly( - Statement.EXECUTE_FAILED, Statement.EXECUTE_FAILED, Statement.EXECUTE_FAILED); + Long.valueOf(Statement.EXECUTE_FAILED), + Long.valueOf(Statement.EXECUTE_FAILED), + Long.valueOf(Statement.EXECUTE_FAILED)); statement.convertUpdateCountsToSuccessNoInfo(new long[] {1L, 0L, 2L}, updateCounts); assertThat(updateCounts) .asList() .containsExactly( - Statement.SUCCESS_NO_INFO, Statement.EXECUTE_FAILED, Statement.SUCCESS_NO_INFO); + Long.valueOf(Statement.SUCCESS_NO_INFO), + Long.valueOf(Statement.EXECUTE_FAILED), + Long.valueOf(Statement.SUCCESS_NO_INFO)); statement.convertUpdateCountsToSuccessNoInfo( - new long[] {1L, Integer.MAX_VALUE + 1L}, updateCounts); - fail("missing expected exception"); - } catch (SQLException e) { - assertThat(JdbcExceptionMatcher.matchCode(Code.OUT_OF_RANGE).matches(e)).isTrue(); + new long[] {1L, Integer.MAX_VALUE + 1L, 2L}, updateCounts); + assertThat(updateCounts) + .asList() + .containsExactly( + Long.valueOf(Statement.SUCCESS_NO_INFO), + Long.valueOf(Statement.SUCCESS_NO_INFO), + Long.valueOf(Statement.SUCCESS_NO_INFO)); } } }