Skip to content

fix(jdbc): keep one connection-scoped Hrana stream for auto-commit - #270

Merged
magnusp merged 3 commits into
docs/262-liquibase-bootstrapfrom
fix/258-connection-scoped-stream
Oct 31, 2026
Merged

magnusp merged 3 commits into
docs/262-liquibase-bootstrapfrom
fix/258-connection-scoped-stream

Conversation

@magnusp

@magnusp magnusp commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

Part 3 of the 4-PR stack (#268 → #269 → #270 → #271). Closes #258.

What changed

Auto-commit now keeps one lazily opened Hrana stream per JDBC connection instead of running every statement one-shot.

  • last_insert_rowid(), PRAGMA settings, and other session state survive between auto-commit statements on the same connection and do not cross connections.
  • A manual transaction begin is tracked explicitly, so an already-open auto-commit stream cannot make the driver skip BEGIN. If that stream expired while idle, the begin is retried once on a fresh stream.
  • Connection.close() closes the session stream.
  • An expired auto-commit stream is dropped and the statement is retried once on a fresh stream. Session state set before expiry is gone, and that reset is documented.
  • Added SqlScript, a SQLite-aware statement splitter that handles quoted text, comments, and trigger bodies. Multi-statement scripts run in order on the same stream, sum update counts, expose the final result set, and stop at the first failure.
  • Multi-statement input with parameters is rejected with SQLFeatureNotSupportedException.
  • Updated the Flyway, Hibernate, jOOQ, and conformance probes:
    • The two xerial multi-statement cases are un-gated.
    • Hibernate runs with auto-commit at its default.
    • jOOQ auto-commit identity reads expect the generated value.
    • Flyway clean() runs when initSql disables foreign keys on the connection. Flyway wraps clean in a transaction, and SQLite ignores PRAGMA foreign_keys inside one, so the pragma has to run at connection initialization.
  • Replaced the “auto-commit holds no stream” section in the conformance README with the connection-scoped stream contract.

Verification

  • ./mvnw -pl libsql-jdbc -am test
  • Targeted JooqIT, LiquibaseIT, FlywayKnownGapsIT, AutoCommitSessionIT, and TransactionLifecycleIT against the pinned sqld container
  • ./mvnw spotless:check
  • ./tools/export-upstream --verify and --self-test
  • CI (Java 25, Java 26, Xerial Control) is green.

Auto-commit statements now run on a lazily opened interactive stream
instead of a one-shot pipeline. Session state such as PRAGMA settings and
last_insert_rowid() therefore survives between statements on the same
connection, matching SQLite, while generated keys keep using the
statement result.

Track the manual transaction begin explicitly so an auto-commit stream
cannot make it look pending. Split multi-statement SQLite scripts into
statements on the same stream, sum update counts, expose the final result,
and reject scripts that contain parameters. Retry an expired auto-commit
stream once on a fresh one, and document the session-state reset.

Closes #258.
Flyway wraps clean() in a transaction, and SQLite ignores
PRAGMA foreign_keys inside a transaction. Configure the pragma through
initSql so it runs on connection initialization, before Flyway begins its
transaction, and keep the clean probe as the passing acceptance test.
An auto-commit connection can hold a stream when the caller switches to a
manual transaction. If that stream expired while idle, the lazy BEGIN hit
STREAM_EXPIRED. The begin did not run, so drop the stale stream and retry
it once on a fresh one, matching the auto-commit recovery path.
@magnusp
magnusp force-pushed the fix/258-connection-scoped-stream branch from 0b7b5fe to c4b5254 Compare October 31, 2026 02:08
@magnusp
magnusp merged commit e18336e into main Oct 31, 2026
10 checks passed
@magnusp
magnusp deleted the fix/258-connection-scoped-stream branch October 31, 2026 02:15
magnusp added a commit that referenced this pull request Oct 31, 2026
Part 1 of the 4-PR stack (#268 → #269 → #270 → #271). Closes #261 and
#274.

## What changed

- `DatabaseMetaData.getDatabaseProductName()` now returns exactly
`SQLite`. The libSQL identity stays in `getDriverName()` (`libSQL JDBC
Driver`) and `getDatabaseProductVersion()`.
- `DatabaseMetaData.getURL()` now returns the JDBC URL the caller
connected with (`jdbc:libsql:...` or `jdbc:sqlite:...`) instead of the
transport endpoint (`http://...` / `ws://...`). Credential query
parameters (`authToken`, `jwt`, `password`, `token`) and user info are
redacted.
- Liquibase now resolves `SQLiteDatabase` through the normal
`DatabaseFactory` path instead of falling back to `UnsupportedDatabase`.
- jOOQ 3.21 derives its dialect from `DatabaseMetaData.getURL()`. With
the URL fix, the `jdbc:sqlite:` alias resolves to `SQLDialect.SQLITE`;
`jdbc:libsql:` still returns `SQLDialect.DEFAULT` because jOOQ has no
libsql prefix, so that path needs an explicit dialect.
- Updated the conformance guard, the WebSocket metadata assertion, and
the Liquibase/jOOQ probes to pin the new metadata.

## Verification

- `./mvnw -pl libsql-jdbc -am test`
- `./mvnw -pl libsql-conformance -am -Dtest=LibsqlConformanceGuardTest
-Dsurefire.failIfNoSpecifiedTests=false test`
- Targeted `JooqIT` against the pinned sqld container (libSQL →
`DEFAULT`, SQLite alias → `SQLITE`)
- Targeted `WebSocketTransportIT` against the pinned sqld container
- `./mvnw spotless:check`
- CI (Java 25, Java 26, Xerial Control) is green.
magnusp added a commit that referenced this pull request Oct 31, 2026
Part 2 of the 4-PR stack (#268 → #269 → #270 → #271). Closes #262.
Builds on #261.

## What changed

- Added a Liquibase integration section to `README.md` covering the
three supported bootstraps:
- `DatabaseFactory.openDatabase(...)` with the `jdbc:sqlite:` prefix and
`LibsqlDriver` named explicitly.
- Liquibase Maven plugin / `liquibase.properties` with
`driver=io.github.magnusp.libsql.jdbc.LibsqlDriver`.
  - Spring Boot or a pool with an explicit `DataSource` driver class.
- The `jdbc:sqlite:` prefix is the one Liquibase's SQLite implementation
claims, and the product name is exactly `SQLite` (#261), so Liquibase
selects its SQLite dialect through the normal `DatabaseFactory` path.
The driver maps the alias to sqld.
- Added
`LiquibaseIT#databaseFactoryOpensASqliteAliasUrlWithAnExplicitDriver`,
which uses the eight-argument `openDatabase` overload that names the
driver and asserts it resolves a `SQLiteDatabase`.
- Kept the negative pin that shows why a `jdbc:libsql:` URL cannot
resolve a driver without an explicit class.

## Verification

- Targeted `LiquibaseIT` against the pinned sqld container
- `./mvnw spotless:check`
- CI (Java 25, Java 26, Xerial Control) is green.
magnusp added a commit that referenced this pull request Oct 31, 2026
Part 4 of the 4-PR stack (#268 → #269 → #270 → #271). Closes #263.
Builds on #258.

## What changed

- Added a Flyway integration section to `README.md` covering:
- A `DataSource` with either `jdbc:libsql:` or `jdbc:sqlite:` (the
recommended path).
- URL-based configuration with the `jdbc:sqlite:` alias and an explicit
`LibsqlDriver` class, for the Maven plugin or `flyway.properties`.
- Why `jdbc:libsql:` cannot be used with URL-based configuration at all.
- Documented the session-state behavior after #258: auto-commit
statements on one connection share a stream, so `initSql` /
`afterConnect` PRAGMAs apply to later migrations until the idle stream
expires.
- Documented the one Flyway `clean()` requirement: set `PRAGMA
foreign_keys = OFF` through `initSql` (or `afterConnect`), because
Flyway wraps clean in a transaction and SQLite ignores that pragma
inside a transaction. The driver's multi-statement splitting then runs
Flyway's PRAGMA/DROP scripts on one stream.

The two negative pins for URL-based plugin/driver resolution stay in
`FlywayKnownGapsIT`.

## Verification

- `./mvnw -pl libsql-jdbc -am test`
- `./mvnw spotless:check`
- CI (Java 25, Java 26, Xerial Control) is green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Keep a connection-scoped Hrana stream for auto-commit: fix last_insert_rowid() and Flyway clean()

1 participant