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);