SequentialReader now walks Arc<ReadPlan> instead of re-parsing the
BAST typed tree per field (the 400x read-path gap, review #004 H1);
materialize_packed walks the same plan, unifying the two packed
read-side consumers on one compiled form.
- SequentialReader::new(Arc<ReadPlan>) -> Self, infallible: the
fallible BastDoc parse moved to ReadPlan::compile (phase 1). The
reader holds the plan Arc + cursor state only; schema() returns the
Arc<Value> retained on the plan (review #005 H2 — no
self-referential struct); new plan() accessor exposes the shared
plan.
- ReadPlan carries schema: Arc<Value> (set at compile; sub-plans hold
a Null placeholder — only the root plan is handed out).
- materialize_packed(&ReadPlan, &[u8]): plan-walking packed
materializer. The aligned path keeps walking BastDoc with the
retained dummy_field_for/ty_source/materialize_typeref_packed
helpers (phase 5 Scope Boundary: aligned structure walk is the
permanent 0.3.0 design).
- Engine: Layout::Packed stores Arc<ReadPlan> alongside the builder;
sequential_reader() is an Arc::clone (was a full-document Value
clone); packed validate_bytes calls materialize_packed(&self.plan).
- Stride (deferred decision 4): FieldValue::Array now reports the
true stride for fixed-size struct/nested-array elements (0.2.0
returned 0); doc comment documents the behavioral change; no
existing test asserted the 0, so none needed changing.
- Two parity subtleties found and preserved:
(a) materialize_plan_composite unwraps the plan's anonymous
single-field wrapper for primitive array elements/record values
— without it, materialized records nest each leaf under a
synthetic object (caught by the record parity test);
(b) field-disc unions keep 0.2.0's materialized key order
(__discriminator first), observable under preserve_order.
Both are now covered by plan-phase tests or construction.
Bench (alktty wire_vs_bast, 1024 chunks/stream): packed read
2.27 us/chunk (review #004) -> 98 ns/chunk p64 / 100 ns/chunk p4k
(~23x; the 400x gap closes to ~17x vs hand-rolled 5.6 ns/chunk).
Residual gap is the per-field String allocation mandated by the
unchanged (String, FieldValue) read_next signature (2 allocs/chunk)
plus data_access bounds checks. sequential_reader() construction:
15.7 ns (was a whole-document clone).
Verification: 465 tests pass unchanged (the existing reader/
materialize/engine suites drive the rewrite through the public API —
only constructor call sites moved to ReadPlan::compile); clippy
-D warnings clean; cargo doc zero warnings; wasm32 release build
green.