From fbabb791b6375447748ca1c501ba9d675f44c80a Mon Sep 17 00:00:00 2001 From: Magnus Persson <1431685+magnusp@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:29:48 +0200 Subject: [PATCH] test(jdbc): pin closeOnCompletion result-set close per java.sql Closing a result set on a statement with closeOnCompletion set closes the statement, per java.sql. The driver already did this through LibsqlResultSet; add lifecycle cases for the query, execute, and prepared paths, keep the metadata statement case from #119 in the pin, and document the rule. A probe against the pinned xerial 3.53.4.0 shows it also auto-closes (JDBC4ResultSet.close checks JDBC4Statement.closeOnCompletion, and prepared statements inherit JDBC4Statement), so no divergence exists to record. --- .../magnusp/libsql/jdbc/LibsqlStatement.java | 3 + .../jdbc/LibsqlStatementLifecycleTest.java | 67 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlStatement.java b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlStatement.java index 1fc946b..535e2cd 100644 --- a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlStatement.java +++ b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlStatement.java @@ -31,6 +31,9 @@ * {@link Statement#KEEP_CURRENT_RESULT} and {@link Statement#CLOSE_ALL_RESULTS} throw * {@link SQLFeatureNotSupportedException} naming both constants, and any other value throws * {@code SQLException("Invalid argument")}. The method always returns {@code false}. + *
  • Closing a result set closes the statement when {@link #closeOnCompletion()} was called, + * per {@code java.sql}. sqlite-jdbc 3.53.4.0 does the same, including for prepared + * statements. *
  • {@link #closeOnCompletion()} and {@link #isCloseOnCompletion()} throw {@code * SQLException("Statement is closed")} after the statement is closed. *
  • {@link #close()} closes the current result set and generated-key results. diff --git a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlStatementLifecycleTest.java b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlStatementLifecycleTest.java index 94747c2..02aea64 100644 --- a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlStatementLifecycleTest.java +++ b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlStatementLifecycleTest.java @@ -125,6 +125,73 @@ void closeOnCompletionAndIsCloseOnCompletionThrowAfterClose() throws SQLExceptio .hasMessage("statement is closed"); } + @Test + void closingQueryResultSetClosesCloseOnCompletionStatement() throws SQLException { + RecordingConnection connection = new RecordingConnection(ROWS); + Statement statement = connection.createStatement(); + statement.closeOnCompletion(); + + ResultSet resultSet = statement.executeQuery("select value from t"); + resultSet.close(); + + assertThat(resultSet.isClosed()).isTrue(); + assertThat(statement.isClosed()).isTrue(); + } + + @Test + void closingExecuteResultSetClosesCloseOnCompletionStatement() throws SQLException { + RecordingConnection connection = new RecordingConnection(ROWS); + Statement statement = connection.createStatement(); + statement.closeOnCompletion(); + + assertThat(statement.execute("select value from t")).isTrue(); + ResultSet resultSet = statement.getResultSet(); + resultSet.close(); + + assertThat(resultSet.isClosed()).isTrue(); + assertThat(statement.isClosed()).isTrue(); + } + + @Test + void closingPreparedResultSetClosesCloseOnCompletionStatement() throws SQLException { + RecordingConnection connection = new RecordingConnection(ROWS); + LibsqlPreparedStatement statement = + new LibsqlPreparedStatement(connection, "select value from t"); + statement.closeOnCompletion(); + + ResultSet resultSet = statement.executeQuery(); + resultSet.close(); + + assertThat(resultSet.isClosed()).isTrue(); + assertThat(statement.isClosed()).isTrue(); + } + + @Test + void closingResultSetLeavesStatementWithoutCloseOnCompletionOpen() throws SQLException { + RecordingConnection connection = new RecordingConnection(ROWS); + Statement statement = connection.createStatement(); + + ResultSet resultSet = statement.executeQuery("select value from t"); + resultSet.close(); + + assertThat(resultSet.isClosed()).isTrue(); + assertThat(statement.isClosed()).isFalse(); + } + + @Test + void metadataStatementClosesWithoutCloseOnCompletion() throws SQLException { + RecordingConnection connection = new RecordingConnection(ROWS); + + ResultSet resultSet = connection.getMetaData().getTableTypes(); + LibsqlStatement statement = (LibsqlStatement) resultSet.getStatement(); + assertThat(statement.isCloseOnCompletion()).isFalse(); + + resultSet.close(); + + assertThat(resultSet.isClosed()).isTrue(); + assertThat(statement.isClosed()).isTrue(); + } + @Test void executeAndGetConnectionStillWorkAfterClose() throws SQLException { RecordingConnection connection = new RecordingConnection(ROWS);