Skip to content

feat(sdk): Phase 11.8 multi-handle SDK shape (SQLR-22)#129

Merged
joaoh82 merged 1 commit into
mainfrom
worktree-phase-11-8-multi-handle-sdk
May 11, 2026
Merged

feat(sdk): Phase 11.8 multi-handle SDK shape (SQLR-22)#129
joaoh82 merged 1 commit into
mainfrom
worktree-phase-11-8-multi-handle-sdk

Conversation

@joaoh82
Copy link
Copy Markdown
Owner

@joaoh82 joaoh82 commented May 11, 2026

Summary

Closes the end-to-end gap from 11.7. The retry-error machinery (BusyError / errorKind / ErrBusy) was reachable through every SDK after 11.7 — but each sqlrite.connect() / new Database() / sql.Open() built an isolated backing DB, so the retry idioms were untriggerable. This slice exposes the engine's Connection::connect() through the public SDK surfaces so apps can finally mint sibling handles that share state.

Picked ahead of plan-doc 11.5 (durability) for the same reason 11.5–11.7 jumped the queue: visible user value first. After this PR, the canonical BEGIN CONCURRENT retry-loop pattern works end-to-end through Python and Node.

What ships

C FFI (sqlrite-ffi/src/lib.rs)

  • New sqlrite_connect_sibling(existing, out) function. Thin wrapper around the engine's Connection::connect. Sibling has its own pointer + lifecycle but shares Database state.
  • Header regenerated via build.rs.

Python SDK (sdk/python/src/lib.rs)

import sqlrite
conn = sqlrite.connect(":memory:")
conn.execute("PRAGMA journal_mode = mvcc")
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, v INTEGER)")
sibling = conn.connect()  # ← new in 11.8
# `sibling` shares the same backing DB. The two handles can each
# hold their own `BEGIN CONCURRENT` and the 11.7 BusyError retry
# pattern finally works end-to-end.

Connection.connect() instance method acquires the inner Mutex<RustConnection>, calls inner.connect(), and wraps the result in a fresh pyclass. Inherits the parent's ask_config.

Node.js SDK (sdk/nodejs/src/lib.rs)

const db = new Database(':memory:');
db.exec('PRAGMA journal_mode = mvcc');
db.exec('CREATE TABLE t (id INTEGER PRIMARY KEY, v INTEGER)');
const sibling = db.connect();  // ← new in 11.8

Same shape — sibling Database with its own RefCell + ask_config clone.

Go SDK — deliberately skipped

Go's database/sql pool model already gives sibling-like behavior across db.Conn(ctx) calls within a single sql.Open, but exposing a cross-pool sibling shape through database/sql would need a process-level path → Database registry. Genuinely non-obvious; deferred to roadmap 11.11.

WASM SDK — deliberately skipped

Single-threaded browser + wasm-bindgen lifetime complications. Same deferral as 11.7.

Test plan

  • cargo build --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --exclude sqlrite-benchmarks --all-targets — clean
  • cargo test --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --exclude sqlrite-benchmarks654/654 (2 new FFI tests)
  • cargo clippy — no new warnings on changed files
  • cargo fmt --all -- --check — clean
  • cargo doc — no new warnings on changed files
  • Python + Node SDK CI jobs will run the new sibling-handle tests on the next CI cycle

New tests

  • FFI (2): connect_sibling_mints_a_handle_that_shares_state exercises a real cross-handle BEGIN CONCURRENT conflict through the public sqlrite_connect_sibling function. connect_sibling_rejects_null_inputs covers the null-pointer guard rail.
  • Python (4): sibling shares DB, sibling outlives closed parent, sibling on closed parent raises, and the headline BEGIN CONCURRENT busy round-trip via siblings.
  • Node (4): equivalent set, using errorKind from 11.7 to confirm the busy classification works end-to-end.

Subtle / informational

  • Python naming overload: sqlrite.connect(path) (module-level free function, opens a fresh DB) and Connection.connect() (instance method, mints a sibling) now both exist. Mirrors the Rust API (Connection::open + Connection::connect) so it stays consistent across languages, but worth a callout in the README when the docs sweep lands (11.12).
  • Node test imports: had to add errorKind / ErrorKind to the top-level ESM import; require() inside a .mjs test body is invalid.

Roadmap renumbering (third time, getting practiced at this)

  • plan-doc 11.5 (checkpoint) → roadmap 11.9 (was 11.8)
  • plan-doc 11.7 (indexes) → roadmap 11.10 (was 11.9)
  • REPL .spawn + bench workload + Go multi-handle → roadmap 11.11
  • plan-doc 11.9 (docs) → roadmap 11.12 (was 11.11)

Called out in the roadmap entries so plan-doc references remain readable.

🤖 Generated with Claude Code

Closes the end-to-end gap from 11.7. The retry-error machinery
(BusyError / errorKind / ErrBusy) was reachable through every
SDK after 11.7 — but each `sqlrite.connect()` / `new Database()`
/ `sql.Open()` built an *isolated* backing DB, so the retry
idioms were untriggerable. This slice exposes the engine's
`Connection::connect()` through the public SDK surfaces so apps
can finally mint sibling handles that share state.

Picked ahead of plan-doc 11.5 (durability) for the same reason
11.5–11.7 jumped the queue: visible user value first. After
this PR, the canonical `BEGIN CONCURRENT` retry loop pattern
works end-to-end through Python and Node.

C FFI ([`sqlrite-ffi/src/lib.rs`]):
- new `sqlrite_connect_sibling(existing, out)` function. Thin
  wrapper around the engine's `Connection::connect`. Sibling has
  its own pointer + lifecycle but shares Database state.
- header regenerated via build.rs.
- 2 new tests: connect_sibling_mints_a_handle_that_shares_state
  (real cross-handle BEGIN CONCURRENT conflict through the public
  API) and connect_sibling_rejects_null_inputs.

Python SDK ([`sdk/python/src/lib.rs`]):
- new `Connection.connect()` instance method. Acquires the
  inner Mutex<RustConnection>, calls `inner.connect()`, wraps
  result in a fresh pyclass with its own Mutex. Inherits the
  parent's ask_config.
- 4 new tests: sibling sharing, outliving closed parent, raise on
  closed connect, and the headline busy-round-trip-via-siblings.

Node.js SDK ([`sdk/nodejs/src/lib.rs`]):
- new `Database.connect()` method on the napi-rs class. Same
  shape — sibling Database with its own RefCell + ask_config clone.
- 4 new tests including the cross-sibling BusyError round trip
  using 11.7's errorKind classifier.

Go SDK: deliberately skipped. database/sql's pool model already
gives sibling-like behavior across `db.Conn(ctx)` calls within a
single `sql.Open`, but exposing a *cross-pool* sibling shape
through `database/sql` would need a process-level path → Database
registry. Deferred to a follow-up (roadmap 11.11).

WASM SDK: deliberately skipped (single-threaded browser +
wasm-bindgen lifetime complications). Same deferral as 11.7.

Workspace: 654/654 Rust tests pass (was 652 + 2 new FFI).
Python + Node SDK CI jobs will exercise the new sibling-handle
tests on the next CI run. fmt + clippy + doc clean on changed
files.

Roadmap renumbered again:
- plan-doc 11.5 (checkpoint) → roadmap 11.9 (was 11.8)
- plan-doc 11.7 (indexes) → roadmap 11.10 (was 11.9)
- REPL .spawn + bench workload + Go multi-handle = roadmap 11.11
- plan-doc 11.9 (docs) → roadmap 11.12 (was 11.11)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
rust-sqlite Ready Ready Preview, Comment May 11, 2026 7:09am

Request Review

@joaoh82 joaoh82 merged commit e84801e into main May 11, 2026
17 of 18 checks passed
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.

1 participant