Description
Ship the streaming execution primitive spike 014 (#682) prototyped and
measured. Every entry point today (execute_with_db,
execute_with_db_and_params, execute_transaction_step) returns a fully
materialized Vec<Vec<Value>>, and the step loop is private
(fn dispatch, src/vdbe/exec.rs:704; fn run), so a caller cannot
read row 0 of a large result without building row N first.
Spike 014 measured what that costs on a 1,000,000-row result:
137.7 MB peak heap for batch against 8.68 MB for streaming, and
5.36 ms to first row against 44.7 µs. It also proved the fix is
small and additive.
This is the engine half only. The Connection/Statement facade
(spec 013 Reqs 2/3, PR #678, still Status: Proposed) is a separate
ticket that sits on top — and per #682's ordering finding it must come
after this one, because Statement::next_row has to be built on the
primitive rather than retrofitted onto execute_with_db.
Scope
src/vdbe/exec.rs:
pub struct Execution<'p> holding the loop state (vm, program,
pc, steps, done, and a pending FIFO), with new,
next_row and autocommit.
- Rewrite
run() as a wrapper that collects next_row into the same
Vec it already returned. This is the load-bearing part: batch and
streaming become literally the same loop, so they cannot drift.
- Export
Execution from src/vdbe.rs.
Tests:
Execution yields the same rows in the same order as run for a
scan, a LIMIT, an aggregate and an empty result.
PRAGMA integrity_check order is preserved. Vm::emit_row has
three callers and pragma::integrity_check
(src/vdbe/pragma.rs:110) emits N rows from a single dispatch. A
drain assuming one row per step would silently reverse that output.
This is the regression guard for the pending FIFO and the reason it
exists.
- Errors are terminal: a caller that keeps polling after an error or a
halt gets None, not a re-entered program.
- Halt-0's implicit commit still flushes exactly once, on the
streaming path as well as the batch one.
Plus an ADR (see below) and a CHANGELOG.md entry.
Non-goals
ADR
An ADR is required: choosing "one streaming primitive with the batch
path as its wrapper" closes off the alternative of a second, parallel
execution path, which is exactly what the ADR convention covers. It
should also record why pending is a FIFO rather than a pop, since
that is non-obvious and a future reader would otherwise simplify it
into a bug.
Complexity
Estimate: small
Reasoning: The implementation already exists, compiles, and is
measured on spike/014_embedding_api (+128/-49, one public struct).
This ticket re-lands it off main with the tests and ADR a spike
branch deliberately skipped. No design work left — #682 did it.
Acceptance Criteria
Refs: 013/Req-7, #682, #678
Description
Ship the streaming execution primitive spike 014 (#682) prototyped and
measured. Every entry point today (
execute_with_db,execute_with_db_and_params,execute_transaction_step) returns a fullymaterialized
Vec<Vec<Value>>, and the step loop is private(
fn dispatch,src/vdbe/exec.rs:704;fn run), so a caller cannotread row 0 of a large result without building row N first.
Spike 014 measured what that costs on a 1,000,000-row result:
137.7 MB peak heap for batch against 8.68 MB for streaming, and
5.36 ms to first row against 44.7 µs. It also proved the fix is
small and additive.
This is the engine half only. The
Connection/Statementfacade(spec 013 Reqs 2/3, PR #678, still
Status: Proposed) is a separateticket that sits on top — and per #682's ordering finding it must come
after this one, because
Statement::next_rowhas to be built on theprimitive rather than retrofitted onto
execute_with_db.Scope
src/vdbe/exec.rs:pub struct Execution<'p>holding the loop state (vm,program,pc,steps,done, and apendingFIFO), withnew,next_rowandautocommit.run()as a wrapper that collectsnext_rowinto the sameVecit already returned. This is the load-bearing part: batch andstreaming become literally the same loop, so they cannot drift.
Executionfromsrc/vdbe.rs.Tests:
Executionyields the same rows in the same order asrunfor ascan, a
LIMIT, an aggregate and an empty result.PRAGMA integrity_checkorder is preserved.Vm::emit_rowhasthree callers and
pragma::integrity_check(
src/vdbe/pragma.rs:110) emits N rows from a single dispatch. Adrain assuming one row per step would silently reverse that output.
This is the regression guard for the
pendingFIFO and the reason itexists.
halt gets
None, not a re-entered program.streaming path as well as the batch one.
Plus an ADR (see below) and a
CHANGELOG.mdentry.Non-goals
Connection/Statement/Transactionfacade (spec 013 Reqs2/3) — separate ticket, lands after this.
per row and that chunking at ~1024 erases it, but that is a facade
concern:
Executionitself has no channel. Recorded here so thefacade ticket does not have to rediscover it.
ValueSend(spike: 014 embedding-API kernel — Send+Sync handle over a streaming VDBE #682 finding 1). Real, and it belongs to Req4's ticket, not this one.
ADR
An ADR is required: choosing "one streaming primitive with the batch
path as its wrapper" closes off the alternative of a second, parallel
execution path, which is exactly what the ADR convention covers. It
should also record why
pendingis a FIFO rather than a pop, sincethat is non-obvious and a future reader would otherwise simplify it
into a bug.
Complexity
Estimate: small
Reasoning: The implementation already exists, compiles, and is
measured on
spike/014_embedding_api(+128/-49, one public struct).This ticket re-lands it off
mainwith the tests and ADR a spikebranch deliberately skipped. No design work left — #682 did it.
Acceptance Criteria
Executionpublic and exported;run()reimplemented over itcargo test --lockedshows no change in pass/fail counts againstthe pre-change baseline (spike measured 1562/0 both sides)
PRAGMA integrity_checkmulti-row ordering has an explicit testmake lint,cargo fmt --check,make check-mod-filescleanmake assuranceintroduces no dead linksCHANGELOG.mdentryRefs: 013/Req-7, #682, #678