From 4811c7dce2e69e67c64ade868a43b5963cf93d80 Mon Sep 17 00:00:00 2001 From: Magnus Persson <1431685+magnusp@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:10:54 +0200 Subject: [PATCH] test(jdbc): pin lazy-begin state after a failed BEGIN A failed BEGIN during a statement must leave auto-commit off with no open Hrana stream, so the next statement retries BEGIN on a new stream. commit and rollback with no work stay no-ops after the failure, and the JDBC mode is unchanged. The lazy-begin state derived from #99 already satisfies this, so only the state-pin case is added. Fixes #108 --- .../libsql/jdbc/LibsqlConnectionTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlConnectionTest.java b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlConnectionTest.java index 5d85fca..4f76465 100644 --- a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlConnectionTest.java +++ b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlConnectionTest.java @@ -209,6 +209,48 @@ void aFailedBeginLeavesTheBeginPendingAndNoStatementRuns() throws SQLException { assertThat(client.requests.get(2).baton()).isNull(); } + @Test + void aFailedBeginPinsTheLazyBeginStateUntilTheRetrySucceeds() throws SQLException { + AtomicInteger begins = new AtomicInteger(); + ScriptedClient client = new ScriptedClient((request, index) -> { + if ("BEGIN".equals(executedSql(request)) && begins.incrementAndGet() == 1) { + return errorResponse(new HranaError(SERVER_ERROR, "SQLITE_ERROR")); + } + return successResponse(request); + }); + LibsqlConnection connection = new LibsqlConnection(CONFIG, client); + connection.setAutoCommit(false); + + assertThatThrownBy(() -> connection.executeStatement(new Stmt("SELECT 1"))) + .isInstanceOf(SQLException.class) + .hasMessageContaining(SERVER_ERROR); + + assertThat(connection.getAutoCommit()) + .as("the JDBC mode is unchanged by a failed begin") + .isFalse(); + assertThat(client.requests.get(1).requests()) + .as("the failed begin closes its stream, so no phantom transaction stays open") + .containsExactly(StreamRequest.Close.INSTANCE); + + connection.commit(); + connection.rollback(); + + assertThat(client.requests) + .as("commit and rollback with no work send nothing after a failed begin") + .hasSize(2); + assertThat(connection.getAutoCommit()).isFalse(); + + connection.executeStatement(new Stmt("SELECT 1")); + + assertThat(client.executedStatements()).containsExactly("BEGIN", "BEGIN", "SELECT 1"); + assertThat(client.requests.get(2).baton()) + .as("the retry begins the transaction on a new stream") + .isNull(); + assertThat(client.requests.get(3).baton()) + .as("the statement runs on the stream the retried begin opened") + .isNotNull(); + } + @Test void enablingAutoCommitAfterWorkCommitsAndClosesTheOpenTransaction() throws SQLException { ScriptedClient client = new ScriptedClient((request, index) -> successResponse(request));