Skip to content

feat(jdbc): accept transactionMode in the JDBC URL and connection properties #294

Closed

Description

@magnusp

Context

sqld runs SQLite in WAL mode. A transaction that reads before it writes holds a snapshot; if another connection commits in between, SQLite refuses the read-to-write upgrade with SQLITE_BUSY_SNAPSHOT (SQLite error: database is locked), and busy_timeout cannot retry it because the snapshot is stale. The application-level fix is to start write transactions with BEGIN IMMEDIATE: take the write lock at BEGIN and let concurrent writers queue instead of failing an upgrade.

LibsqlConnection.setTransactionMode(TransactionMode) already does the right thing (0.2.1): once set, ensureTransactionStarted sends BEGIN IMMEDIATE / BEGIN EXCLUSIVE. But the mode cannot be selected from configuration. LibsqlUrlParser recognises only authToken, busyTimeout, connectTimeout, jwt, password, socketTimeout, ssl and tls, so applications that want the mode have to wrap their DataSource, get at the concrete LibsqlConnection, and call the setter themselves.

A Connection.setReadOnly(true) check is not a substitute. For example, Spring's @Transactional(readOnly = true) goes through Hibernate's setDefaultReadOnly, which does not call Connection.setReadOnly for JPA transactions, so the driver cannot infer a deferred transaction from the JDBC read-only flag. The mode has to be explicit.

Proposal

Accept transactionMode as a JDBC URL query parameter and as a connection property, matching the existing busyTimeout handling:

jdbc:libsql:http://localhost:8080?transactionMode=immediate
  • Values: deferred, immediate, exclusive, case-insensitive, mirroring LibsqlConnection.TransactionMode (and xerial's SQLiteConfig.TransactionMode).
  • Default: deferred, preserving current behaviour.
  • An unknown value fails at connect with a message that lists the accepted values, rather than silently falling back to deferred.
  • Apply it to the connection state in LibsqlDriver.connect (or carry it through LibsqlClientConfig if that is the cleaner home). It is per-connection behaviour, not transport configuration.

Acceptance

  • jdbc:libsql:http://host?transactionMode=immediate makes the connection's first transaction begin with BEGIN IMMEDIATE.
  • URL and Properties forms behave the same; the precedence between them is documented.
  • Parser unit tests cover every accepted value, case insensitivity, the default, and rejection of an unknown value.
  • An integration test in libsql-itests runs a read-then-write transaction with immediate and asserts it commits while a second connection writes in between; conformance tests stay green.
  • The parameter is documented next to the other URL options in the README/JDBC reference.

Downstream

Applications currently work around the missing option by wrapping the DataSource and calling setTransactionMode on each connection. A URL parameter would keep their configuration declarative and remove that wrapper.

Activity

  1. added
    enhancementNew feature or request
    P2Normal priority: edge case, feature gap, or test/process improvement
    on Nov 2, 2026
  2. magnusp commented on Nov 2, 2026

    @magnusp
    OwnerAuthor

    Done in 0.2.2. #295 added transactionMode as a URL query parameter and a Properties key, with case-insensitive deferred/immediate/exclusive, a deferred default, a connect-time error that lists the accepted values, URL-over-Properties precedence, parser tests for every case, and the README parameter table. The tag v0.2.2 published the artifact and CI stayed green, including the conformance suite.

    The read-then-write integration test the acceptance asked for landed separately in #297: it reads inside an immediate transaction, proves a second connection's insert waits, writes and commits in the first transaction, and checks both rows.

    The auth-refusal transaction-state reset found while testing the driver is follow-up #296, not part of this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal priority: edge case, feature gap, or test/process improvementenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions