compute: answer an index peek from one budgeted scan - #38508
Conversation
|
Review findings, posted by Claude Code on behalf of @antiguru. Line numbers are at No defect that breaks shipped behaviour. The state machine does not wedge, both terminal outcomes latch where the contract says they do, and the row and byte accounting across the phase boundary is correct. Two documentation-against-code contradictions do matter, because the next layer's driver is written against them. 1. The struct doc states a memory bound the type does not enforce
Either scope the claim to a stash-eligible scan and name 2.
|
|
Findings addressed, posted by Claude Code on behalf of @antiguru. A failed scan keeps no rows. The ceiling arm added a row's bytes to Two doc claims corrected. The prefix is bounded by the stash threshold only for a peek that may use the stash, since a peek with an Bounds freshness is marked, not settled. Three of the four bounds freeze at open and the row-iteration limit is re-read per step, on an argument that covers all four. Inert while a scan lives no longer than one driver call, so it carries a TODO rather than a signature change through three layers. Worth a decision before a scan outlives a call. The scan type has a name. Correction to my own earlier report on this PR: I claimed Test-gap items closed here: nothing pinned that |
df32860 to
b50a802
Compare
QA LLM Review1. MEDIUM --
|
|
Confirmed and fixed in The divergence is real and it is this diff's doing. At the base both routes charged One detail worth stating, because it makes the persist ruler worse than a constant overcount.
Pinned with a test rather than left to the next reader: let charged: usize = rows.iter().map(entry_byte_len).sum();
let collection = RowCollection::new(/* the same rows, each with count 1 */, &[]);
assert_eq!(charged, collection.byte_len());Verified red before green: with A golden test in that file would have been the natural home, but On the second half, which I have deliberately not changed: for a peek that is not stash-eligible, Posted by Claude Code on behalf of @moritz*.* |
petrosagg
left a comment
There was a problem hiding this comment.
Approving, just a tiny nit
| /// | ||
| /// Either phase can end it: the error walk by answering the peek, the ok walk by either of | ||
| /// its outcomes. | ||
| fn ended_outcome(&self) -> Option<ScanOutcome> { |
There was a problem hiding this comment.
Let's store a single Option<ScanOutcome> field in PeekScan that latches the result.
There was a problem hiding this comment.
Done in 8183fa1a16. PeekScan now holds ended: Option<ScanOutcome>, set in step for either Finished outcome, and a stepped scan that has ended returns ended.clone() after the soft_panic_or_log. ok_walk_end and ended_outcome() are gone.
ErrorPhase lost its payload with them: it is Scanning(ErrorScan), Clean or Failed. Failed stays a distinct variant rather than folding into the latch because it also drops the error cursor, so a peek its error trace answered stops pinning error batches, and error_trace_clean() still has to tell a clean phase from a failed one when the ok walk later errs.
🤖 Posted by Claude Code on behalf of @antiguru
Makes one object own both phases of a fast-path index peek. `PeekScan` holds the errs cursor, the oks cursor, the literal state, the accumulated rows and the size accounting, and spends a single budget across the error trace and the ok walk. It performs no IO and never awaits, so the same scan runs wherever a driver puts it, and a driver that stops it between two cursor positions picks it up again without repeating work. Before this, each phase was individually suspendable but nothing spanned the boundary, so no caller could hold a partially finished peek. `ScanOutcome::Suspended` carries no payload. The scan keeps its accumulated prefix and a driver pulls it with `take_batch`, which yields a batch only once accumulation crosses the stash threshold. Nothing is handed by value to a driver with no way to dispose of it, so "committed rows are never dropped" is a property of the interface rather than a rule each driver keeps. This is a refactor. Every peek answers exactly as it did, including the diversion to the peek stash, which still restarts the walk. One behaviour does change, deliberately: a scan whose accumulation crosses the stash threshold now suspends rather than continuing to accumulate. The result-size ceiling does not gate a stash-bound prefix, so without it an inline driver would hold an entire result in memory where it previously stopped at the threshold.
Makes one object own both phases of a fast-path index peek.
PeekScanholds the errs cursor, the oks cursor, the literal state, the accumulated rows, and the size accounting, and spends a single budget across the error trace and the ok walk. It performs no IO and never awaits, so the same scan runs wherever a driver puts it, and a driver that stops it between two cursor positions picks it up again without repeating work.Before this, each phase was individually suspendable but nothing spanned the boundary, so no caller could hold a partially finished peek.
ScanOutcome::Suspendedcarries no payload. The scan keeps its accumulated prefix and a driver pulls it withtake_batch, which yields a batch only once accumulation crosses the stash threshold. Nothing is handed by value to a driver with no way to dispose of it, so "committed rows are never dropped" is a property of the interface rather than a rule each driver keeps.This is a refactor. Every peek answers exactly as it did, including the diversion to the peek stash, which still restarts the walk. Two full sqllogictest sweeps pass, one ordinary and one with the stash threshold at zero so that every streamable peek takes the diversion.
One behaviour does change, deliberately: a scan whose accumulation crosses the stash threshold now suspends rather than continuing to accumulate. The result-size ceiling does not gate a stash-bound prefix, so without it an inline driver would hold an entire result in memory where it previously stopped at the threshold.
🤖 Opened by Claude Code on behalf of @antiguru
Replaces #38477, which GitHub closed when the design document moved from the bottom of the stack to the top and its branch was force-pushed past these commits. Same content, new base.