Context
Spec 013 (Embedding API, PR #678, Status: Proposed) specifies a public
Connection/Statement/Transaction facade. Two of its seven
requirements are architectural rather than ergonomic, and a wrong choice
in either forces a rewrite of the facade built on top:
- Req 4 — a
Send + Sync handle over a connection-owned worker
thread, because Rc<dyn PageSource>/Rc<RefCell<Pager>> are !Send
(23 sites in src/).
- Req 7 — incremental row access, because every entry point today
(execute_with_db, execute_with_db_and_params,
execute_transaction_step) returns a fully materialized
Vec<Vec<Value>>.
These interact: streaming across a thread boundary means the statement
handle holds cursor state on the worker while the caller holds a Send
handle. That interaction is the actual risk, and neither requirement's
cost is known.
A static read of the tree already narrows it. Vm is public
(Vm::with_db, bind_params, rows), but the step loop is not:
src/vdbe/exec.rs:704 — fn dispatch(...) is private
src/vdbe/exec.rs — fn run(...) is private
src/vdbe/exec.rs:619 — ResultRow calls emit_row, which pushes
into a Vec inside the Vm; Vm::rows() hands back the
accumulated slice
So Req 7 is not implementable as an external layer over today's
public API, and Req 4 probably is. This spike measures both instead of
assuming either.
Hypothesis
A Send + Sync connection handle over a worker-thread-owned Rc engine
graph can serve incremental per-row results and a rows-affected
count, without modifying the engine's Rc/RefCell ownership or the
VDBE opcode-dispatch loop.
The static read above predicts the hypothesis fails on its second
clause — streaming will require an additive engine change. The spike's
job is to find the minimal such change, prove it is additive (no
behaviour change for existing callers), and measure what streaming costs
or saves.
Design
Three prototypes over one fixture, mirroring spike 013's
three-implementation shape:
batch — control. Today's public API
(execute_with_db_and_params) on the caller's thread. No engine
change.
worker_batch — connection owns a thread; commands cross a
channel; the whole result set returns per statement. Isolates Req 4.
Expected to need no engine change.
worker_stream — same worker, rows yielded one at a time, cursor
state living on the worker. Exercises Reqs 4 and 7 together. Requires
the minimal engine change, which is part of the deliverable.
Correctness gates timing: all three prototypes must return byte-identical
rows for the same query before any measurement is trusted (spike 013's
all_three_comparators_agree_on_every_pair precedent).
Success Criteria
Send + Sync on the handle proven at compile time (static assertion),
not asserted in prose.
- All three prototypes agree on every row of every fixture query.
- Per-row latency and total throughput measured for all three.
- Peak memory measured for a large result set — Req 7's "without
materializing" is a claim that needs a number.
- Drop-mid-iteration releases the worker's cursors (Req 7's second
scenario) and a dead engine surfaces as an error, not a hang.
- The minimal
src/ diff for streaming is stated as a diff, with a
verdict on whether it is additive for existing callers.
- A go/no-go on spec 013's Req 4 + Req 7 as written, including any
correction the spec needs before it merges.
Spike Output
tests/spike/014_embedding_api/ — standalone crate, own [workspace],
path dep on the parent, Makefile (test/bench), spike-014 target
in the root Makefile.
README.md — Hypothesis / Setup / Design / Results / Finding /
Conclusion, with an explicit ADR-proposed-or-not verdict.
- Any
src/ patch stays on the spike branch, never merged; the
shippable change lands later via its own ticket.
Complexity
Estimate: medium
Reasoning: Three prototypes, but only one is novel — batch is a
copy of examples/query.rs, and worker_batch is a standard
channel-plus-thread wrapper. The cost sits in worker_stream: finding
the minimal step/yield primitive in src/vdbe/exec.rs without
disturbing the dispatch loop, plus a criterion harness and a memory
measurement. Bounded by being throwaway code that ships nothing.
Context
Feeds PR #678's review — the spec is still Status: Proposed, so a
correction found here is cheaper than one found after merge. Does not
block, and is not blocked by, the spec-010/Req-8 autoindex corruption
bug, which is independent.
Refs: 013/Req-4, 013/Req-7, 010/Req-8 (independent), #678, #621
Context
Spec 013 (Embedding API, PR #678,
Status: Proposed) specifies a publicConnection/Statement/Transactionfacade. Two of its sevenrequirements are architectural rather than ergonomic, and a wrong choice
in either forces a rewrite of the facade built on top:
Send + Synchandle over a connection-owned workerthread, because
Rc<dyn PageSource>/Rc<RefCell<Pager>>are!Send(23 sites in
src/).(
execute_with_db,execute_with_db_and_params,execute_transaction_step) returns a fully materializedVec<Vec<Value>>.These interact: streaming across a thread boundary means the statement
handle holds cursor state on the worker while the caller holds a
Sendhandle. That interaction is the actual risk, and neither requirement's
cost is known.
A static read of the tree already narrows it.
Vmis public(
Vm::with_db,bind_params,rows), but the step loop is not:src/vdbe/exec.rs:704—fn dispatch(...)is privatesrc/vdbe/exec.rs—fn run(...)is privatesrc/vdbe/exec.rs:619—ResultRowcallsemit_row, which pushesinto a
Vecinside theVm;Vm::rows()hands back theaccumulated slice
So Req 7 is not implementable as an external layer over today's
public API, and Req 4 probably is. This spike measures both instead of
assuming either.
Hypothesis
A
Send + Syncconnection handle over a worker-thread-ownedRcenginegraph can serve incremental per-row results and a rows-affected
count, without modifying the engine's
Rc/RefCellownership or theVDBE opcode-dispatch loop.
The static read above predicts the hypothesis fails on its second
clause — streaming will require an additive engine change. The spike's
job is to find the minimal such change, prove it is additive (no
behaviour change for existing callers), and measure what streaming costs
or saves.
Design
Three prototypes over one fixture, mirroring spike 013's
three-implementation shape:
batch— control. Today's public API(
execute_with_db_and_params) on the caller's thread. No enginechange.
worker_batch— connection owns a thread; commands cross achannel; the whole result set returns per statement. Isolates Req 4.
Expected to need no engine change.
worker_stream— same worker, rows yielded one at a time, cursorstate living on the worker. Exercises Reqs 4 and 7 together. Requires
the minimal engine change, which is part of the deliverable.
Correctness gates timing: all three prototypes must return byte-identical
rows for the same query before any measurement is trusted (spike 013's
all_three_comparators_agree_on_every_pairprecedent).Success Criteria
Send + Syncon the handle proven at compile time (static assertion),not asserted in prose.
materializing" is a claim that needs a number.
scenario) and a dead engine surfaces as an error, not a hang.
src/diff for streaming is stated as a diff, with averdict on whether it is additive for existing callers.
correction the spec needs before it merges.
Spike Output
tests/spike/014_embedding_api/— standalone crate, own[workspace],path dep on the parent,
Makefile(test/bench),spike-014targetin the root
Makefile.README.md— Hypothesis / Setup / Design / Results / Finding /Conclusion, with an explicit ADR-proposed-or-not verdict.
src/patch stays on the spike branch, never merged; theshippable change lands later via its own ticket.
Complexity
Estimate: medium
Reasoning: Three prototypes, but only one is novel —
batchis acopy of
examples/query.rs, andworker_batchis a standardchannel-plus-thread wrapper. The cost sits in
worker_stream: findingthe minimal step/yield primitive in
src/vdbe/exec.rswithoutdisturbing the dispatch loop, plus a criterion harness and a memory
measurement. Bounded by being throwaway code that ships nothing.
Context
Feeds PR #678's review — the spec is still
Status: Proposed, so acorrection found here is cheaper than one found after merge. Does not
block, and is not blocked by, the spec-010/Req-8 autoindex corruption
bug, which is independent.
Refs: 013/Req-4, 013/Req-7, 010/Req-8 (independent), #678, #621