@@ -35,3 +35,51 @@ namespaces are not retained.
3535The conformance copy of an upstream test diverges from xerial where Hrana connection
3636lifecycle makes the original behavior impossible; each divergence is recorded in
3737` ../upstream-xerial_sqlite-jdbc.manifest ` .
38+
39+ ## Divergences from a local SQLite connection
40+
41+ Hrana over HTTP has no connection. A stream is identified only by the ` baton `
42+ in the pipeline request: ` baton: null ` creates a new stream, and the server
43+ closes streams after a short period of inactivity because there is no
44+ connection that would let it detect an abandoned stream
45+ (` ../vendor/HRANA_3_SPEC.md ` , "Hrana over HTTP"). Two observable divergences
46+ from xerial ` sqlite-jdbc ` , which owns one SQLite connection per JDBC
47+ connection, follow from that model. Per-test divergences in the vendored
48+ suite are recorded separately in ` ../upstream-xerial_sqlite-jdbc.manifest ` .
49+
50+ ### Auto-commit holds no stream
51+
52+ An auto-commit statement runs as a one-shot pipeline that executes the
53+ statement and closes the stream in the same round trip. The driver keeps no
54+ session stream open between statements, so an idle connection consumes no
55+ server stream slot and holds no baton for the idle expiry to invalidate.
56+ Keeping one open only to preserve session state would reintroduce
57+ stale-baton failures on idle connections.
58+
59+ Because ` StmtResult.last_insert_rowid ` describes the insert on one stream
60+ and nothing else, a later ` SELECT last_insert_rowid() ` runs on a fresh
61+ stream and returns ` 0 ` , even when the same ` Statement ` inserted a row
62+ immediately before. xerial returns that insert's rowid instead. Use
63+ ` Statement.getGeneratedKeys() ` after the insert, which the driver builds
64+ from ` StmtResult.last_insert_rowid ` with no extra round trip, or run the
65+ insert and the query inside one explicit transaction so both run on the
66+ same stream. ` LibsqlGeneratedKeysTest ` pins the generated-keys path.
67+
68+ ### Transactions begin lazily
69+
70+ ` setAutoCommit(false) ` sends nothing. The next statement begins the
71+ transaction on a new stream with ` BEGIN ` , ` BEGIN IMMEDIATE ` , or
72+ ` BEGIN EXCLUSIVE ` according to
73+ ` LibsqlConnection.setTransactionMode(TransactionMode) ` (default ` DEFERRED ` ),
74+ sent as its own pipeline so a failed begin cannot be followed by the
75+ statement running outside a transaction. ` commit() ` and ` rollback() ` close
76+ the stream and do not begin the next transaction, so an idle non-auto-commit
77+ connection likewise holds no stream.
78+
79+ The divergence is timing only: an ` IMMEDIATE ` or ` EXCLUSIVE ` transaction
80+ takes its write lock when the first statement begins it, not at
81+ ` setAutoCommit(false) ` . Select that mode when the lock must be held from
82+ transaction start; with the default ` DEFERRED ` , two readers that both
83+ upgrade can still see ` SQLITE_BUSY ` , as in SQLite. A transaction left open
84+ and idle longer than the server timeout can still fail on a stale baton,
85+ so keep transactions short and retry the work in a fresh one.
0 commit comments