Repository navigation
test(jdbc): pin post-close statement transitions per java.sql - #148
Merged
Merged
Conversation
Enumerate the post-close addBatch, getGeneratedKeys, and getResultSet transitions for plain and prepared statements, and pin each with a LibsqlStatementLifecycleTest case. The driver already follows java.sql, so the production change is the class-javadoc enumeration only.
magnusp
added a commit
that referenced
this pull request
Oct 23, 2026
Fixes #81 Contract: Hrana 3 is the source of truth for transport, values, metadata, batching, transactions, and error reporting; `java.sql` is the source of truth for the JDBC surface; xerial/sqlite-jdbc is a behavioral reference and conformance foundation, never a copy target. ## What changed `LibsqlPreparedStatement.executeCurrent()` called `checkClosed(); beginExecution(); checkClosed();`. The second check runs after the state transition and before `connection.executeStatement`, which already checks the connection itself. Removed it, leaving the single `checkClosed()` before the state transition. One file changed, one line deleted, no behavior change. ## Divergence from the plan's Files block `LibsqlStatement.java` is in the plan's Files block but needed no edit: its second `checkClosed()` was already removed in `4b30b46` (#85), which matched xerial's tolerance of `execute` after `statement.close()`. That behavior is pinned by `LibsqlStatementLifecycleTest.executeAndGetConnectionStillWorkAfterClose` (#148), so adding a `checkClosed()` to the statement execute path would be a behavior change and was not done. `executeSql` reads `beginExecution();` with the connection check inside `LibsqlConnection.executeStatement`. ## Evidence Red at trunk (`origin/main` `de31dc6`): `executeCurrent` at lines 231-235 reads `checkClosed(); beginExecution(); checkClosed();`. Green at head (`2621b71`): one check. Log: `/tmp/xerial-parity-i81/lane-3-specific.log`. Lanes at head (logs under `/tmp/xerial-parity-i81/`): 1. Regression lane: not applicable. The plan's unit box says no new tests, so there is no new failing case; the diff check stands in for it (lane 3). 2. Lifecycle and prepared suites, trunk vs head: identical, 21 `LibsqlStatementLifecycleTest` + 6 `LibsqlPreparedStatementTest` = 27/27 pass on both sides (`trunk-unit.log`, `head-unit.log`). 3. No double check remains in either file (`lane-3-specific.log`). 4. `-pl libsql-jdbc test`: BUILD SUCCESS, 293/293 (`lane-4-module-suite.log`); trunk is also 293/293 (`trunk-module-suite.log`). 5. `-pl libsql-conformance -am verify`: BUILD SUCCESS, 123 tests, 0 failures, 1 skipped, no divergence line (`lane-5-conformance.log`). 6. `-Pxerial-control verify`: BUILD SUCCESS, 100 tests, 0 failures, 1 skipped; differences are the pre-existing reference notes, none introduced here (`lane-6-control.log`). 7. `spotless:check`: BUILD SUCCESS (`lane-7-spotless.log`). 8. `./mvnw test`: BUILD SUCCESS, 293/293 in `libsql-jdbc` (`lane-8-reactor.log`). 9. Statement surface: BUILD SUCCESS, 30/30 (`lane-9-specific.log`). 10. `-pl libsql-jdbc -am package -DskipTests`: BUILD SUCCESS, driver jar built (`lane-10-package.log`). Perf: three interleaved `./mvnw -pl libsql-jdbc test -q` runs per side, trunk first. Trunk median 2.161 s, head median 2.167 s (+0.3%, within the 10% rule). Raw values in `perf-raw.txt`. Base `de31dc6`, head `2621b71e5c77c5a9976519942e2b8d5b2044ff29`. No merge; stopping at merge-ready.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #87
Contract
Hrana 3 is the source of truth for transport, values, metadata, batching, transactions, and error reporting.
java.sqlis the source of truth for the JDBC surface. xerial/sqlite-jdbc is a behavioral reference and conformance foundation, never a copy target. This change is protocol-neutral:java.sqldefines post-close behavior.What changed
The driver already obeyed
java.sqlfor every transition the issue names, so no runtime behavior changed. This PR enumerates the transitions, pins them inLibsqlStatementLifecycleTest(one case per transition), and records the enumeration in the class javadoc ofLibsqlStatementandLibsqlPreparedStatement. The #47 execute-after-close exception is unchanged and scoped: a closed statement still letsexecutereach the server for xerial drop-in compatibility, but it never exposes a result set afterwards.Statement.addBatch(String)SQLException("Statement is closed"), nothing queuedPreparedStatement.addBatch()SQLException("Statement is closed"), nothing queuedStatement.getGeneratedKeys()SQLException("Statement is closed")PreparedStatement.getGeneratedKeys()SQLException("Statement is closed")Statement.getResultSet()afterclose()SQLException("Statement is closed")Statement.getResultSet()after a post-close re-executeSQLException("Statement is closed"); the re-execute still runs per #47, andgetResultSetstill rejectsPreparedStatement.getResultSet()after a rejected post-closeexecute()SQLException("Statement is closed"); the failed execute does not reopen the statementRed and green
LibsqlStatementLifecycleTest, 21/21 pass at head8e2bfcd(/tmp/xerial-parity-i87/lane-1-head.log).origin/main9bf5d0f, because the runtime behavior pre-existed; the coverage gap, not a behavior gap, was real (lane-1-trunk.log).checkClosed()guard fromaddBatch,addBatch(),getGeneratedKeys, andgetResultSetin a throwaway trunk worktree fails exactly the 7 new cases (7 failures) and leaves the 14 pre-existing cases green (lane-1-mutation.log). So each case fails for the asserted reason when its guard is absent.xerial reference notes
Probed directly against the pinned xerial 3.53.4.0 jar (
/tmp/xerial-parity-i87/xerial-probe.log). Every row is a difference, recorded as a reference, not a target:addBatch(String)after close: xerial accepts and queues silently. Driver throws perjava.sql.getGeneratedKeys()after close, plain and prepared: xerial returns a new open result set (empty or with keys). Driver throws.execute+getResultSetafter close: xerial reopens its internal pointer and hands out a live result set. Driver keeps the Statement lifecycle and getMoreResults argument handling differ #47 re-execute but throws fromgetResultSet, because the JDBC statement is still closed.executeafter close: xerial throwsSQLException("statement is not executing"); the driver throwsSQLException("Statement is closed")and itsgetResultSetalso throws.statement is not executing. The driver usesStatement is closed(andstatement is closedfor the closeOnCompletion auto-closes on ResultSet.close; xerial only tracks the flag #86 close-on-completion pair), matching its established Statement lifecycle and getMoreResults argument handling differ #47/closeOnCompletion auto-closes on ResultSet.close; xerial only tracks the flag #86 wording and describing the JDBC state rather than an internal execution pointer. Lane 9's message cases pass against the driver's wording; the xerial wording difference is this note.No vendored xerial test covers
addBatch/getGeneratedKeys/getResultSetafter close, so Lane 2 used the nearest lifecycle tests (MetadataLeakTest,ResultSetWithoutResultsTest) in control mode plus the direct probe above.Evidence
Base
9bf5d0f(origin/main), head8e2bfcd6975924a1b51aa76367dc85632695a591. Logs in/tmp/xerial-parity-i87/.-Pxerial-control verify -Dit.test=MetadataLeakTest,ResultSetWithoutResultsTest-pl libsql-jdbc test -Dtest=LibsqlStatementLifecycleTest-pl libsql-jdbc test-pl libsql-conformance -am verify-pl libsql-conformance -am -Pxerial-control verifyspotless:check./mvnw test-Dtest='LibsqlStatementLifecycleTest#*AfterClose*+*PostClose*'-pl libsql-jdbc -am package -DskipTestsPerf:
-pl libsql-jdbc testwall clock, three interleaved runs each side. Trunk median 2.09 s, head median 2.11 s (+0.96%, within the 10% rule). No hot-path code changed.Note: the plan names
mise exec --; this environment's brief andAGENTS.mdsay to runjavaand./mvnwdirectly. Every lane above ran withoutmise. Docker was available for the conformance lanes.