From bf4989344151ab33b502366e4682485b16cd35c9 Mon Sep 17 00:00:00 2001 From: nicktorwald Date: Mon, 13 May 2019 01:37:10 +0700 Subject: [PATCH] Support Statement.closeOnCompletion. Check a statement after all its dependent result set are closed. Extract TarantoolStatement as tarantool specific extension interface to be used for internal purposes (incompatible vendor API). Closes: #180 --- .../tarantool/jdbc/SQLDatabaseMetadata.java | 6 +- .../java/org/tarantool/jdbc/SQLResultSet.java | 20 ++-- .../java/org/tarantool/jdbc/SQLStatement.java | 51 ++++++--- .../tarantool/jdbc/TarantoolStatement.java | 30 ++++++ .../org/tarantool/jdbc/JdbcResultSetIT.java | 6 +- .../org/tarantool/jdbc/JdbcStatementIT.java | 101 ++++++++++++++++++ 6 files changed, 187 insertions(+), 27 deletions(-) create mode 100644 src/main/java/org/tarantool/jdbc/TarantoolStatement.java diff --git a/src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java b/src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java index ee7e8710..b3b0d024 100644 --- a/src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java +++ b/src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java @@ -1126,8 +1126,8 @@ private SQLNullResultSet sqlNullResultSet(List columnNames, List T ensureType(Class cls, Object v) throws Exception { @@ -1152,7 +1152,7 @@ private SQLNullResultSet emptyResultSet(List colNames) throws SQLExcepti protected class SQLNullResultSet extends SQLResultSet { - public SQLNullResultSet(SQLResultHolder holder, SQLStatement ownerStatement) throws SQLException { + public SQLNullResultSet(SQLResultHolder holder, TarantoolStatement ownerStatement) throws SQLException { super(holder, ownerStatement); } diff --git a/src/main/java/org/tarantool/jdbc/SQLResultSet.java b/src/main/java/org/tarantool/jdbc/SQLResultSet.java index f707da7b..2771965f 100644 --- a/src/main/java/org/tarantool/jdbc/SQLResultSet.java +++ b/src/main/java/org/tarantool/jdbc/SQLResultSet.java @@ -38,13 +38,13 @@ public class SQLResultSet implements ResultSet { - private final CursorIterator> iterator; - private final SQLResultSetMetaData metaData; + private CursorIterator> iterator; + private SQLResultSetMetaData metaData; private Map columnByNameLookups; private boolean lastColumnWasNull; - private final Statement statement; + private final TarantoolStatement statement; private final int maxRows; private AtomicBoolean isClosed = new AtomicBoolean(false); @@ -53,7 +53,7 @@ public class SQLResultSet implements ResultSet { private final int concurrencyLevel; private final int holdability; - public SQLResultSet(SQLResultHolder holder, SQLStatement ownerStatement) throws SQLException { + public SQLResultSet(SQLResultHolder holder, TarantoolStatement ownerStatement) throws SQLException { metaData = new SQLResultSetMetaData(holder.getSqlMetadata()); statement = ownerStatement; scrollType = statement.getResultSetType(); @@ -108,7 +108,13 @@ protected Number getNullableNumber(int columnIndex) throws SQLException { @Override public void close() throws SQLException { if (isClosed.compareAndSet(false, true)) { - iterator.close(); + try { + iterator.close(); + iterator = null; + metaData = null; + } finally { + statement.checkCompletion(); + } } } @@ -393,11 +399,13 @@ public String getCursorName() throws SQLException { @Override public ResultSetMetaData getMetaData() throws SQLException { + checkNotClosed(); return metaData; } @Override public int findColumn(String columnLabel) throws SQLException { + checkNotClosed(); return findColumnIndex(columnLabel); } @@ -1122,7 +1130,7 @@ public T unwrap(Class type) throws SQLException { if (isWrapperFor(type)) { return type.cast(this); } - throw new SQLNonTransientException("ResultSet does not wrap " + type.getName()); + throw new SQLNonTransientException("SQLResultSet does not wrap " + type.getName()); } @Override diff --git a/src/main/java/org/tarantool/jdbc/SQLStatement.java b/src/main/java/org/tarantool/jdbc/SQLStatement.java index f5ae17d3..142c3f60 100644 --- a/src/main/java/org/tarantool/jdbc/SQLStatement.java +++ b/src/main/java/org/tarantool/jdbc/SQLStatement.java @@ -21,7 +21,7 @@ * types of cursors. * Supports only {@link ResultSet#HOLD_CURSORS_OVER_COMMIT} holdability type. */ -public class SQLStatement implements Statement { +public class SQLStatement implements TarantoolStatement { protected final SQLConnection connection; @@ -31,6 +31,8 @@ public class SQLStatement implements Statement { protected SQLResultSet resultSet; protected int updateCount; + private boolean isCloseOnCompletion; + private final int resultSetType; private final int resultSetConcurrency; private final int resultSetHoldability; @@ -310,15 +312,36 @@ public boolean isPoolable() throws SQLException { throw new SQLFeatureNotSupportedException(); } + /** + * {@inheritDoc} + *

+ * Impl Note: this method doesn't affect + * execution methods which close the last result set implicitly. + * It is applied only when {@link ResultSet#close()} is invoked + * explicitly by the app. + * + * @throws SQLException if this method is called on a closed + * {@code Statement} + */ @Override public void closeOnCompletion() throws SQLException { - + checkNotClosed(); + isCloseOnCompletion = true; } @Override public boolean isCloseOnCompletion() throws SQLException { checkNotClosed(); - return false; + return isCloseOnCompletion; + } + + @Override + public void checkCompletion() throws SQLException { + if (isCloseOnCompletion && + resultSet != null && + resultSet.isClosed()) { + close(); + } } @Override @@ -326,7 +349,7 @@ public T unwrap(Class type) throws SQLException { if (isWrapperFor(type)) { return type.cast(this); } - throw new SQLNonTransientException("Statement does not wrap " + type.getName()); + throw new SQLNonTransientException("SQLStatement does not wrap " + type.getName()); } @Override @@ -338,15 +361,18 @@ public boolean isWrapperFor(Class type) throws SQLException { * Clears the results of the most recent execution. */ protected void discardLastResults() throws SQLException { + final SQLResultSet lastResultSet = resultSet; + clearWarnings(); updateCount = -1; - if (resultSet != null) { + resultSet = null; + + if (lastResultSet != null) { try { - resultSet.close(); + lastResultSet.close(); } catch (Exception ignored) { // No-op. } - resultSet = null; } } @@ -375,16 +401,7 @@ protected boolean executeInternal(String sql, Object... params) throws SQLExcept return holder.isQueryResult(); } - /** - * Returns {@link ResultSet} which will be initialized by data. - * - * @param data predefined result to be wrapped by {@link ResultSet} - * - * @return wrapped result - * - * @throws SQLException if a database access error occurs or - * this method is called on a closed Statement - */ + @Override public ResultSet executeMetadata(SQLResultHolder data) throws SQLException { checkNotClosed(); return createResultSet(data); diff --git a/src/main/java/org/tarantool/jdbc/TarantoolStatement.java b/src/main/java/org/tarantool/jdbc/TarantoolStatement.java new file mode 100644 index 00000000..879a03d6 --- /dev/null +++ b/src/main/java/org/tarantool/jdbc/TarantoolStatement.java @@ -0,0 +1,30 @@ +package org.tarantool.jdbc; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * Tarantool specific statement extensions. + */ +public interface TarantoolStatement extends Statement { + + /** + * Checks for statement completion and closes itself, + * according to {@link Statement#closeOnCompletion()}. + */ + void checkCompletion() throws SQLException; + + /** + * Returns {@link ResultSet} which will be initialized by data. + * + * @param data predefined result to be wrapped by {@link ResultSet} + * + * @return wrapped result + * + * @throws SQLException if a database access error occurs or + * this method is called on a closed Statement + */ + ResultSet executeMetadata(SQLResultHolder data) throws SQLException; + +} diff --git a/src/test/java/org/tarantool/jdbc/JdbcResultSetIT.java b/src/test/java/org/tarantool/jdbc/JdbcResultSetIT.java index 2f355aae..b4cf15c9 100644 --- a/src/test/java/org/tarantool/jdbc/JdbcResultSetIT.java +++ b/src/test/java/org/tarantool/jdbc/JdbcResultSetIT.java @@ -167,8 +167,12 @@ public void testResultSetMetadataAfterClose() throws SQLException { assertNotNull(resultSet); ResultSetMetaData metaData = resultSet.getMetaData(); assertNotNull(metaData); + + int expectedColumnSize = 2; + assertEquals(expectedColumnSize, metaData.getColumnCount()); + resultSet.close(); - assertEquals(metaData, resultSet.getMetaData()); + assertEquals(expectedColumnSize, metaData.getColumnCount()); } @Test diff --git a/src/test/java/org/tarantool/jdbc/JdbcStatementIT.java b/src/test/java/org/tarantool/jdbc/JdbcStatementIT.java index 3292cebb..955d15fa 100644 --- a/src/test/java/org/tarantool/jdbc/JdbcStatementIT.java +++ b/src/test/java/org/tarantool/jdbc/JdbcStatementIT.java @@ -157,12 +157,14 @@ public void execute() throws Throwable { @Test public void testUnwrap() throws SQLException { + assertEquals(stmt, stmt.unwrap(TarantoolStatement.class)); assertEquals(stmt, stmt.unwrap(SQLStatement.class)); assertThrows(SQLException.class, () -> stmt.unwrap(Integer.class)); } @Test public void testIsWrapperFor() throws SQLException { + assertTrue(stmt.isWrapperFor(TarantoolStatement.class)); assertTrue(stmt.isWrapperFor(SQLStatement.class)); assertFalse(stmt.isWrapperFor(Integer.class)); } @@ -247,4 +249,103 @@ void testStatementConnection() throws SQLException { Statement statement = conn.createStatement(); assertEquals(conn, statement.getConnection()); } + + @Test + void testCloseOnCompletion() throws SQLException { + assertFalse(stmt.isCloseOnCompletion()); + stmt.closeOnCompletion(); + assertTrue(stmt.isCloseOnCompletion()); + } + + @Test + void testCloseOnCompletionDisabled() throws SQLException { + ResultSet resultSet = stmt.executeQuery("SELECT val FROM test WHERE id=1"); + assertFalse(stmt.isClosed()); + assertFalse(resultSet.isClosed()); + + resultSet.close(); + assertTrue(resultSet.isClosed()); + assertFalse(stmt.isClosed()); + } + + @Test + void testCloseOnCompletionEnabled() throws SQLException { + stmt.closeOnCompletion(); + ResultSet resultSet = stmt.executeQuery("SELECT val FROM test WHERE id=1"); + + assertFalse(stmt.isClosed()); + assertFalse(resultSet.isClosed()); + + resultSet.close(); + assertTrue(resultSet.isClosed()); + assertTrue(stmt.isClosed()); + } + + @Test + void testCloseOnCompletionAfterResultSet() throws SQLException { + ResultSet resultSet = stmt.executeQuery("SELECT val FROM test WHERE id=1"); + stmt.closeOnCompletion(); + + assertFalse(stmt.isClosed()); + assertFalse(resultSet.isClosed()); + + resultSet.close(); + assertTrue(resultSet.isClosed()); + assertTrue(stmt.isClosed()); + } + + @Test + void testCloseOnCompletionMultipleResultSets() throws SQLException { + stmt.closeOnCompletion(); + ResultSet resultSet = stmt.executeQuery("SELECT val FROM test WHERE id=1"); + ResultSet anotherResultSet = stmt.executeQuery("SELECT val FROM test WHERE id=2"); + + assertTrue(resultSet.isClosed()); + assertFalse(anotherResultSet.isClosed()); + assertFalse(stmt.isClosed()); + + anotherResultSet.close(); + assertTrue(anotherResultSet.isClosed()); + assertTrue(stmt.isClosed()); + } + + @Test + void testCloseOnCompletionUpdateQueries() throws SQLException { + stmt.closeOnCompletion(); + + int updateCount = stmt.executeUpdate("INSERT INTO test(id, val) VALUES (5, 'five')"); + assertEquals(1, updateCount); + assertFalse(stmt.isClosed()); + + updateCount = stmt.executeUpdate("INSERT INTO test(id, val) VALUES (6, 'six')"); + assertEquals(1, updateCount); + assertFalse(stmt.isClosed()); + } + + @Test + void testCloseOnCompletionMixedQueries() throws SQLException { + stmt.closeOnCompletion(); + + int updateCount = stmt.executeUpdate("INSERT INTO test(id, val) VALUES (7, 'seven')"); + assertEquals(1, updateCount); + assertFalse(stmt.isClosed()); + + ResultSet resultSet = stmt.executeQuery("SELECT val FROM test WHERE id=7"); + assertFalse(resultSet.isClosed()); + assertFalse(stmt.isClosed()); + + updateCount = stmt.executeUpdate("INSERT INTO test(id, val) VALUES (8, 'eight')"); + assertEquals(1, updateCount); + assertTrue(resultSet.isClosed()); + assertFalse(stmt.isClosed()); + + resultSet = stmt.executeQuery("SELECT val FROM test WHERE id=8"); + assertFalse(resultSet.isClosed()); + assertFalse(stmt.isClosed()); + + resultSet.close(); + assertTrue(resultSet.isClosed()); + assertTrue(stmt.isClosed()); + } + }