Skip to content

fix(jdbc): reset generated keys and result sets on every execution path - #142

Merged
magnusp merged 1 commit into
mainfrom
fix/i106-generated-keys-reset
Oct 23, 2026
Merged

magnusp merged 1 commit into
mainfrom
fix/i106-generated-keys-reset

Conversation

@magnusp

@magnusp magnusp commented Oct 23, 2026

Copy link
Copy Markdown
Owner

Fixes #106

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.

Base 57e1b53b6ee813ad719a93cb23a05f2af4f70c80, head f0fbdf53fc33648716f19a77ca38728184d9692c.

What changed

Every execution path now starts from beginExecution(): it closes the current result set, discards the generated keys, and resets the update count. The paths are executeSql (plain execute/executeQuery/executeUpdate), executeCurrent (prepared statements), and executeBatch.

Generated keys come from StmtResult.last_insert_rowid with no follow-up query:

  • Single execution: one key row when the SQL is an insert.
  • Batch: one key row per insert step, built from that step's BatchResult result. The update count is the total affected rows across the step results.
  • executeQuery on an insert (for example INSERT ... RETURNING) reports that statement's key; a non-insert query clears the keys.
  • A failed batch throws BatchUpdateException after the state reset, so no half-populated keys survive.

Divergences, with reasons

  • xerial's executeQuery neither clears nor updates generated keys, so it can report a previous statement's key. Cleared here because the issue names that as a bug and java.sql keys belong to the current statement.
  • xerial's executeBatch leaves the previous keys and update count in place, and its docs say it cannot return keys for multiple rows. The batch path reports every insert step's last_insert_rowid and sets the update count to the total affected rows. java.sql leaves getUpdateCount unspecified after a batch, so this is a recorded choice.
  • Empty batch: state is still reset, the returned array is empty, and the update count is 0 (the sum of no step results).
  • Generated-keys shape stays the java.sql shape already pinned by the tests: one column named last_insert_rowid(), one row per key; the no-keys result keeps xerial's 1 column shape.

Files outside the plan's Files block

  • libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlConnection.java: executeBatch discarded the BatchResult step results and returned only int[]. Added executeBatchResults returning the step results and made executeBatch delegate to it. The Build box ("from each BatchResult step result") is not implementable without this.
  • libsql-itests/src/test/java/com/github/magnusp/libsql/itest/GeneratedKeysIT.java: the issue says this file has no batch case, and lane 2 runs it. Added the live batch and RETURN_GENERATED_KEYS-batch case.

Evidence

Red, trunk (origin/main, same assertions through the trunk connection seam): LibsqlGeneratedKeysTrunkProbeTest 6/6 fail — stale key 5 after batch, stale key 3 after executeQuery RETURNING, stale count 1, batch leaves the previous result set open, failed batch leaves the old key.
Log: /tmp/xerial-parity-i106/lane-1-regression.log.

Green at head: LibsqlGeneratedKeysTest 16/16 pass; GeneratedKeysIT 4/4 live against sqld, including the new batch case.

Lanes (all at head, logs under /tmp/xerial-parity-i106/):

  1. Trunk red / head green: 6 failures at trunk, 16/16 at head.
  2. GeneratedKeysIT live lane: 4/4, BUILD SUCCESS (lane-2-specific.log).
  3. LibsqlStatementLifecycleTest plus generated keys: pass (lane-3-lifecycle.log).
  4. -pl libsql-jdbc test: BUILD SUCCESS (lane-4-module-suite.log).
  5. -pl libsql-conformance -am verify: BUILD SUCCESS, no new divergence line (lane-5-conformance.log).
  6. -Pxerial-control verify: BUILD SUCCESS, differences recorded above (lane-6-control.log).
  7. spotless:check: BUILD SUCCESS (lane-7-spotless.log).
  8. ./mvnw test: BUILD SUCCESS (lane-8-reactor.log).
  9. Failing batch through getGeneratedKeys: pass, no half-populated keys (lane-9-specific.log).
  10. -pl libsql-jdbc -am package -DskipTests: BUILD SUCCESS (lane-10-package.log).

Perf: three interleaved time ./mvnw -pl libsql-jdbc test runs per side. Trunk median 2.07 s, head median 2.09 s (+1.0%, within the 10% rule). Raw values under /tmp/xerial-parity-i106/perf-*.txt.

Docker note for lane 2: DOCKER_CONFIG=/tmp/wt-106/libsql-conformance/src/test/docker-config, per AGENTS.md section 4.

@magnusp
magnusp merged commit 662d066 into main Oct 23, 2026
6 checks passed
@magnusp
magnusp deleted the fix/i106-generated-keys-reset branch October 23, 2026 00:27
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.

getGeneratedKeys and the current result set go stale after executeBatch and executeQuery

1 participant