From aaa404a22c4812bc965a24393c7310634ef464c0 Mon Sep 17 00:00:00 2001 From: nicktorwald Date: Tue, 9 Apr 2019 01:36:14 +0700 Subject: [PATCH] Raise SQLException on methods of a closed Connection Add missed checks on methods of the closed connection. Closes: #72 Affects: #119, #74 --- .../org/tarantool/jdbc/SQLConnection.java | 125 ++++++++++++++- .../java/org/tarantool/util/SQLStates.java | 3 +- .../org/tarantool/jdbc/JdbcConnectionIT.java | 151 +++++++++++++++++- .../org/tarantool/jdbc/SqlAssertions.java | 15 ++ 4 files changed, 282 insertions(+), 12 deletions(-) create mode 100644 src/test/java/org/tarantool/jdbc/SqlAssertions.java diff --git a/src/main/java/org/tarantool/jdbc/SQLConnection.java b/src/main/java/org/tarantool/jdbc/SQLConnection.java index f89ac84f..a8bcd3fb 100644 --- a/src/main/java/org/tarantool/jdbc/SQLConnection.java +++ b/src/main/java/org/tarantool/jdbc/SQLConnection.java @@ -19,6 +19,7 @@ import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; +import java.sql.ClientInfoStatus; import java.sql.Clob; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -36,6 +37,9 @@ import java.sql.Statement; import java.sql.Struct; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; @@ -213,22 +217,24 @@ public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) thr @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @Override public CallableStatement prepareCall(String sql) throws SQLException { - throw new SQLFeatureNotSupportedException(); + return prepareCall(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { - throw new SQLFeatureNotSupportedException(); + return prepareCall(sql, resultSetType, resultSetConcurrency, getHoldability()); } @Override @@ -237,16 +243,19 @@ public CallableStatement prepareCall(String sql, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @Override public String nativeSQL(String sql) throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @Override public void setAutoCommit(boolean autoCommit) throws SQLException { + checkNotClosed(); if (!autoCommit) { throw new SQLFeatureNotSupportedException(); } @@ -254,21 +263,43 @@ public void setAutoCommit(boolean autoCommit) throws SQLException { @Override public boolean getAutoCommit() throws SQLException { + checkNotClosed(); return true; } @Override public void commit() throws SQLException { + checkNotClosed(); + if (getAutoCommit()) { + throw new SQLNonTransientException( + "Cannot commit when auto-commit is enabled.", + SQLStates.INVALID_TRANSACTION_STATE.getSqlState() + ); + } throw new SQLFeatureNotSupportedException(); } @Override public void rollback() throws SQLException { + checkNotClosed(); + if (getAutoCommit()) { + throw new SQLNonTransientException( + "Cannot rollback when auto-commit is enabled.", + SQLStates.INVALID_TRANSACTION_STATE.getSqlState() + ); + } throw new SQLFeatureNotSupportedException(); } @Override public void rollback(Savepoint savepoint) throws SQLException { + checkNotClosed(); + if (getAutoCommit()) { + throw new SQLNonTransientException( + "Cannot roll back to a savepoint when auto-commit is enabled.", + SQLStates.INVALID_TRANSACTION_STATE.getSqlState() + ); + } throw new SQLFeatureNotSupportedException(); } @@ -293,25 +324,30 @@ public DatabaseMetaData getMetaData() throws SQLException { @Override public void setReadOnly(boolean readOnly) throws SQLException { - + checkNotClosed(); + throw new SQLFeatureNotSupportedException(); } @Override public boolean isReadOnly() throws SQLException { + checkNotClosed(); return false; } @Override public void setCatalog(String catalog) throws SQLException { + checkNotClosed(); } @Override public String getCatalog() throws SQLException { + checkNotClosed(); return null; } @Override public void setTransactionIsolation(int level) throws SQLException { + checkNotClosed(); if (level != Connection.TRANSACTION_NONE) { throw new SQLFeatureNotSupportedException(); } @@ -319,26 +355,30 @@ public void setTransactionIsolation(int level) throws SQLException { @Override public int getTransactionIsolation() throws SQLException { + checkNotClosed(); return Connection.TRANSACTION_NONE; } @Override public SQLWarning getWarnings() throws SQLException { + checkNotClosed(); return null; } @Override public void clearWarnings() throws SQLException { - + checkNotClosed(); } @Override public Map> getTypeMap() throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @Override public void setTypeMap(Map> map) throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @@ -360,36 +400,55 @@ public int getHoldability() throws SQLException { @Override public Savepoint setSavepoint() throws SQLException { + checkNotClosed(); + if (getAutoCommit()) { + throw new SQLNonTransientException( + "Cannot set a savepoint when auto-commit is enabled.", + SQLStates.INVALID_TRANSACTION_STATE.getSqlState() + ); + } throw new SQLFeatureNotSupportedException(); } @Override public Savepoint setSavepoint(String name) throws SQLException { + checkNotClosed(); + if (getAutoCommit()) { + throw new SQLNonTransientException( + "Cannot set a savepoint when auto-commit is enabled.", + SQLStates.INVALID_TRANSACTION_STATE.getSqlState() + ); + } throw new SQLFeatureNotSupportedException(); } @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @Override public Clob createClob() throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @Override public Blob createBlob() throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @Override public NClob createNClob() throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @Override public SQLXML createSQLXML() throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @@ -431,45 +490,99 @@ private boolean checkConnection(int timeout) { @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { - throw new SQLClientInfoException(); + try { + checkNotClosed(); + } catch (SQLException cause) { + throwUnknownReasonClientProperties("Connection is closed", Collections.singleton(name), cause); + } + throwUnknownClientProperties(Collections.singleton(name)); } @Override public void setClientInfo(Properties properties) throws SQLClientInfoException { - throw new SQLClientInfoException(); + try { + checkNotClosed(); + } catch (SQLException cause) { + throwUnknownReasonClientProperties("Connection is closed", properties.keySet(), cause); + } + throwUnknownClientProperties(properties.keySet()); + } + + /** + * Throws an exception caused by {@code cause} and marks all properties + * as {@link ClientInfoStatus#REASON_UNKNOWN}. + * + * @param reason reason mesage + * @param properties client properties + * @param cause original cause + * + * @throws SQLClientInfoException wrapped exception + */ + private void throwUnknownReasonClientProperties(String reason, + Collection properties, + SQLException cause) throws SQLClientInfoException { + Map failedProperties = new HashMap<>(); + properties.forEach(property -> { + failedProperties.put(property.toString(), ClientInfoStatus.REASON_UNKNOWN); + }); + throw new SQLClientInfoException(reason, cause.getSQLState(), failedProperties, cause); + } + + /** + * Throws exception for unrecognizable properties. + * + * @param properties unknown property names. + * + * @throws SQLClientInfoException wrapped exception + */ + private void throwUnknownClientProperties(Collection properties) throws SQLClientInfoException { + Map failedProperties = new HashMap<>(); + properties.forEach(property -> { + failedProperties.put(property.toString(), ClientInfoStatus.REASON_UNKNOWN_PROPERTY); + }); + throw new SQLClientInfoException(failedProperties); } @Override public String getClientInfo(String name) throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @Override public Properties getClientInfo() throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { + checkNotClosed(); throw new SQLFeatureNotSupportedException(); } @Override public void setSchema(String schema) throws SQLException { + checkNotClosed(); } @Override public String getSchema() throws SQLException { + checkNotClosed(); return null; } @Override public void abort(Executor executor) throws SQLException { + if (isClosed()) { + return; + } throw new SQLFeatureNotSupportedException(); } diff --git a/src/main/java/org/tarantool/util/SQLStates.java b/src/main/java/org/tarantool/util/SQLStates.java index 347234fc..89ac309d 100644 --- a/src/main/java/org/tarantool/util/SQLStates.java +++ b/src/main/java/org/tarantool/util/SQLStates.java @@ -6,7 +6,8 @@ public enum SQLStates { NO_DATA("02000"), CONNECTION_DOES_NOT_EXIST("08003"), INVALID_PARAMETER_VALUE("22023"), - INVALID_CURSOR_STATE("24000"); + INVALID_CURSOR_STATE("24000"), + INVALID_TRANSACTION_STATE("25000"); private final String sqlState; diff --git a/src/test/java/org/tarantool/jdbc/JdbcConnectionIT.java b/src/test/java/org/tarantool/jdbc/JdbcConnectionIT.java index 93f8c72d..f67b4a39 100644 --- a/src/test/java/org/tarantool/jdbc/JdbcConnectionIT.java +++ b/src/test/java/org/tarantool/jdbc/JdbcConnectionIT.java @@ -6,20 +6,26 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.tarantool.jdbc.SqlAssertions.assertSqlExceptionHasStatus; import org.tarantool.TarantoolConnection; +import org.tarantool.util.SQLStates; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; import java.lang.reflect.Field; import java.net.Socket; +import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; +import java.util.Collections; +import java.util.Properties; public class JdbcConnectionIT extends AbstractJdbcIT { @@ -150,11 +156,6 @@ public void testSetAndGetHoldability() throws SQLException { () -> conn.setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT) ); assertThrows(SQLException.class, () -> conn.setHoldability(Integer.MAX_VALUE)); - - assertThrows(SQLException.class, () -> { - conn.close(); - conn.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT); - }); } @Test @@ -461,5 +462,145 @@ public void testGeneratedKeys() throws SQLException { ); } + @Test + public void testUnavailableMethodsAfterClose() throws SQLException { + conn.close(); + + SQLException sqlException; + sqlException = assertThrows(SQLException.class, () -> conn.clearWarnings()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + + sqlException = assertThrows(SQLException.class, () -> conn.getWarnings()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + + sqlException = assertThrows(SQLException.class, () -> conn.createArrayOf("INTEGER", new Object[] { 1, 2, 3 })); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.createBlob()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.createClob()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.createNClob()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.createSQLXML()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + + sqlException = assertThrows(SQLException.class, () -> conn.createStatement()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> + conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY) + ); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> + conn.createStatement( + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.HOLD_CURSORS_OVER_COMMIT + ) + ); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + + sqlException = assertThrows(SQLException.class, () -> conn.prepareStatement("")); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> + conn.prepareStatement("", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY) + ); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> + conn.prepareStatement( + "", + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.HOLD_CURSORS_OVER_COMMIT + ) + ); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.prepareStatement("", Statement.NO_GENERATED_KEYS)); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.prepareStatement("", new int[] { })); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.prepareStatement("", new String[] { })); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + + sqlException = assertThrows(SQLException.class, () -> conn.prepareCall("")); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> + conn.prepareCall("", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY) + ); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> + conn.prepareCall( + "", + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.HOLD_CURSORS_OVER_COMMIT + ) + ); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + + sqlException = assertThrows(SQLException.class, () -> conn.getHoldability()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.getMetaData()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.getNetworkTimeout()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + + sqlException = assertThrows(SQLException.class, () -> conn.nativeSQL("")); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + + sqlException = assertThrows(SQLException.class, () -> conn.commit()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.rollback()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.rollback(null)); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.setAutoCommit(true)); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.getAutoCommit()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.setSavepoint()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.setSavepoint("")); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.isReadOnly()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.setReadOnly(true)); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.getTransactionIsolation()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows( + SQLException.class, + () -> conn.setTransactionIsolation(Connection.TRANSACTION_NONE) + ); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + + sqlException = assertThrows(SQLClientInfoException.class, () -> conn.setClientInfo(new Properties())); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLClientInfoException.class, () -> conn.setClientInfo("key", "value")); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.getClientInfo("param1")); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.getClientInfo()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + + sqlException = assertThrows(SQLException.class, () -> conn.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT)); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.setNetworkTimeout(null, 1000)); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + + sqlException = assertThrows(SQLException.class, () -> conn.getSchema()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.getCatalog()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.setCatalog("")); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.setSchema("")); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + + sqlException = assertThrows(SQLException.class, () -> conn.getTypeMap()); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + sqlException = assertThrows(SQLException.class, () -> conn.setTypeMap(Collections.emptyMap())); + assertSqlExceptionHasStatus(sqlException, SQLStates.CONNECTION_DOES_NOT_EXIST); + } + } diff --git a/src/test/java/org/tarantool/jdbc/SqlAssertions.java b/src/test/java/org/tarantool/jdbc/SqlAssertions.java new file mode 100644 index 00000000..93770a49 --- /dev/null +++ b/src/test/java/org/tarantool/jdbc/SqlAssertions.java @@ -0,0 +1,15 @@ +package org.tarantool.jdbc; + +import org.tarantool.util.SQLStates; + +import org.junit.jupiter.api.Assertions; + +import java.sql.SQLException; + +public class SqlAssertions { + + public static void assertSqlExceptionHasStatus(SQLException exception, SQLStates state) { + Assertions.assertEquals(state.getSqlState(), exception.getSQLState()); + } + +}