Skip to content

feat: streaming Execution primitive — read a result row without materializing the rest #683

Description

@dpsiderius

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

  • Execution public and exported; run() reimplemented over it
  • cargo test --locked shows no change in pass/fail counts against
    the pre-change baseline (spike measured 1562/0 both sides)
  • PRAGMA integrity_check multi-row ordering has an explicit test
  • ADR added and indexed
  • make lint, cargo fmt --check, make check-mod-files clean
  • make assurance introduces no dead links
  • CHANGELOG.md entry

Refs: 013/Req-7, #682, #678

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions