Skip to content

fix(jdbc): stop batches at the first failure and throw BatchUpdateException (#104) - #124

Merged
magnusp merged 2 commits into
mainfrom
fix/i104-batch-conditions
Oct 22, 2026
Merged

magnusp merged 2 commits into
mainfrom
fix/i104-batch-conditions

Conversation

@magnusp

@magnusp magnusp commented Oct 22, 2026

Copy link
Copy Markdown
Owner

Fixes #104

Contract

Hrana 3 is the source of truth for transport, values, and metadata. java.sql is the source of truth for the JDBC surface. xerial/sqlite-jdbc is the behavioral reference and conformance foundation, never a copy target.

Protocol basis: Hrana Batch, BatchCond, and BatchResult. Each step after the first carries {"type":"ok","step":i-1}, so sqld skips every later step once one fails and reports the skipped steps as nulls. The driver reads step_results and step_errors and throws the JDBC contract on top.

What changed

  • LibsqlConnection.executeBatch gives every step after the first an ok condition on the previous step.
  • extractBatchResults throws BatchUpdateException at the first step error instead of returning Statement.EXECUTE_FAILED in the array.
  • A step with neither a result nor an error is now a protocol error. A null step is legitimate only when an earlier step failed, and that case throws before the loop reaches it.

Partial-count semantics

BatchUpdateException.getUpdateCounts() is the prefix of counts for the steps that ran, so it has exactly one entry per successfully executed step before the failure. A failure in the first step yields an empty array, a failure in the middle yields the leading counts, and a failure in the last step yields all earlier counts. Later steps do not run. In auto-commit each step commits on its own, so the steps before the failure stay committed. In transaction mode the caller decides with commit() or rollback().

Evidence

Red at trunk (cb1c609, new tests, old LibsqlConnection): Tests run: 21, Failures: 7 in LibsqlConnectionTest. Every new batch case failed with "Expecting code to raise a throwable", plus the protocol-error case. Log: /tmp/xerial-parity-i104/lane-1-regression.log.

Green at head (e2bc38c): Tests run: 21, Failures: 0 in LibsqlConnectionTest; module suite Tests run: 205, Failures: 0; root ./mvnw test BUILD SUCCESS; spotless:check BUILD SUCCESS; libsql-itests -am verify 22 tests BUILD SUCCESS; libsql-conformance -am verify 115 tests (1 skipped) BUILD SUCCESS; -Pxerial-control verify 100 tests (1 skipped) BUILD SUCCESS; package -DskipTests builds the jar.

Live replay against real sqld (throwaway itest, run then removed so the committed diff stays within the plan's file list): a batch of 1, 1 (duplicate), 2 throws BatchUpdateException with counts [1], and only row 1 exists afterwards in both auto-commit and transaction mode. Rollback in transaction mode leaves the table empty. Log: /tmp/xerial-parity-i104/lane-2-specific.log.

Perf

In-memory 1000-statement executeBatch, 200 warmup batches then 300 measured, three interleaved runs per side: trunk medians 53.823, 53.833, 55.005 us; head medians 49.845, 48.754, 49.725 us. Trunk median 53.833 us, head median 49.725 us, so head is 7.6 percent faster, inside the 10 percent rule. Round trips are unchanged at one pipeline request per batch on both sides (requests=500 for 500 batches). The conditions add about 37 bytes of JSON per step after the first, so the encoded request grows from 67,971 to 104,824 bytes for 1000 steps in this probe.

Divergence

None. The driver now matches the JDBC and sqlite-jdbc stop-at-first-failure behavior. LibsqlErrorMapper still has an unused BatchUpdateException import from before this change; removing it is outside this PR's file list and is left as a follow-up.

Base: cb1c609d2c1a0b13c3b3e55cfeb66c7e94da9e7c
Head: e2bc38ce0cf3af4b7bdca70e21a30334da97d5e1

@magnusp
magnusp merged commit 8c9381b into main Oct 22, 2026
5 checks passed
@magnusp
magnusp deleted the fix/i104-batch-conditions branch October 22, 2026 19:47
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.

executeBatch reports step failures as EXECUTE_FAILED and throws no exception

1 participant