Skip to main content

differential_dataflow/trace/chunk/
cursor.rs

1//! Cursor navigation over chunks and chunk batches.
2//!
3//! Implementing [`NavigableChunk`] lets a [`ChunkBatch`] expose a [`ChunkBatchCursor`].
4//! This cursor joins key and value groups that span chunk boundaries, delegating navigation within each chunk to its cursor.
5//!
6//! Searches across chunks read resident [`bounds`](NavigableChunk::bounds) metadata.
7//! Key seeks gallop from a remembered chunk, and boundary crossings compare adjacent chunks' bounds.
8//! Chunk bodies are read when navigation reaches them.
9//! Implementors must keep `bounds` cheap even when a chunk's body is paged out.
10
11use crate::trace::Navigable;
12use crate::trace::cursor::Cursor;
13use crate::trace::implementations::BatchContainer;
14
15use super::{Chunk, ChunkBatch};
16
17/// The navigation capability: a [`Chunk`] whose contents can be read by cursor.
18///
19/// This is optional. Batch formation and trace maintenance need only [`Chunk`];
20/// implementing this trait additionally lets [`ChunkBatch`] offer the straddle
21/// cursor ([`ChunkBatchCursor`]), which is how cursor-driven operator paths read
22/// an arrangement. Chunks consumed only by whole-chunk logic (tactics) can skip it.
23///
24/// `bounds` must stay cheap even when a chunk's body is paged out: the straddle
25/// cursor consults chunk bounds throughout navigation — seeks binary-search them,
26/// boundary crossings compare against them — and opens a chunk's body only when a
27/// query touches it.
28pub trait NavigableChunk: Chunk + Navigable<Cursor: Cursor<Time = <Self as Chunk>::Time>> {
29    /// The first and last `(key, val, time)` triples in the chunk.
30    fn bounds(&self) -> (
31        (<Self::Cursor as Cursor>::Key<'_>, <Self::Cursor as Cursor>::Val<'_>, <Self::Cursor as Cursor>::TimeGat<'_>),
32        (<Self::Cursor as Cursor>::Key<'_>, <Self::Cursor as Cursor>::Val<'_>, <Self::Cursor as Cursor>::TimeGat<'_>),
33    );
34}
35
36type KeyCon<C> = <<C as Navigable>::Cursor as Cursor>::KeyContainer;
37type ValCon<C> = <<C as Navigable>::Cursor as Cursor>::ValContainer;
38
39impl<C: NavigableChunk> crate::trace::Navigable for ChunkBatch<C> {
40    type Cursor = ChunkBatchCursor<C>;
41    fn cursor(&self) -> Self::Cursor {
42        ChunkBatchCursor { key_chunk: 0, chunk: 0, inner: self.chunks.first().map(C::cursor) }
43    }
44}
45
46/// A cursor over a [`ChunkBatch`], merging the per-chunk cursors.
47///
48/// Chunk breakpoints are unconstrained, so a single key — or `(key, val)` — may
49/// straddle consecutive chunks. But the chunks are one globally-sorted sequence
50/// merely cut at arbitrary points, so the operation is *concatenation*, never a
51/// merge: across a boundary a key's vals concatenate and a `(key, val)`'s times
52/// concatenate. The cursor exploits this. It holds the chunk currently being read
53/// and a cursor into it; it seeks by galloping the chunks' resident
54/// [`bounds`](NavigableChunk::bounds) from a remembered hint (the current key's first
55/// chunk), and at boundaries it *continues* into the next chunk rather than merging —
56/// consulting the two neighbouring chunks' bounds to detect when a key or `(key, val)`
57/// spills forward, without touching chunk contents. No state is materialized up front:
58/// a monotone seek sweep costs `O(log Δ)` bounds reads per seek and a sequential pass two
59/// per boundary, so cursor construction is free.
60pub struct ChunkBatchCursor<C: NavigableChunk> {
61    /// First chunk of the current key's run; where `rewind_vals` returns to.
62    key_chunk: usize,
63    /// Chunk currently being read; `>= key_chunk`, within the current key's span.
64    chunk: usize,
65    /// Cursor into `chunk`; `None` once `chunk` is past the last chunk.
66    inner: Option<C::Cursor>,
67}
68
69impl<C: NavigableChunk> ChunkBatchCursor<C> {
70    /// Move the active chunk to `c`, opening a fresh inner cursor at its start.
71    fn goto(&mut self, c: usize, storage: &ChunkBatch<C>) {
72        self.chunk = c;
73        self.inner = storage.chunks.get(c).map(C::cursor);
74    }
75
76    /// Does key `k` span the boundary between chunks `c` and `c + 1` — chunk `c`
77    /// ends with it and chunk `c + 1` begins with it?
78    ///
79    /// Two resident [`bounds`](NavigableChunk::bounds) reads; the `reborrow`s
80    /// unify the (invariant) item lifetimes with `k`'s.
81    fn key_spills(s: &ChunkBatch<C>, c: usize, k: <C::Cursor as Cursor>::Key<'_>) -> bool {
82        <KeyCon<C> as BatchContainer>::reborrow(s.chunks[c].bounds().1.0) == <KeyCon<C> as BatchContainer>::reborrow(k)
83            && <KeyCon<C> as BatchContainer>::reborrow(s.chunks[c + 1].bounds().0.0) == <KeyCon<C> as BatchContainer>::reborrow(k)
84    }
85
86    /// Does `(k, v)` span the boundary between chunks `c` and `c + 1`?
87    fn val_spills(s: &ChunkBatch<C>, c: usize, k: <C::Cursor as Cursor>::Key<'_>, v: <C::Cursor as Cursor>::Val<'_>) -> bool {
88        Self::key_spills(s, c, k)
89            && <ValCon<C> as BatchContainer>::reborrow(s.chunks[c].bounds().1.1) == <ValCon<C> as BatchContainer>::reborrow(v)
90            && <ValCon<C> as BatchContainer>::reborrow(s.chunks[c + 1].bounds().0.1) == <ValCon<C> as BatchContainer>::reborrow(v)
91    }
92
93    /// The first chunk, at or after `hint`, whose last key is `>= key`: where `key`'s run
94    /// begins. `hint` — the current key's first chunk (`key_chunk`) — is a valid lower bound
95    /// for a forward seek; a backward seek is detected and served by a full search from the
96    /// front. Galloping keeps a monotone seek sweep at `O(log Δ)` bounds reads per seek rather
97    /// than `O(log chunks)`; only resident [`bounds`](NavigableChunk::bounds) are read.
98    fn locate_key(s: &ChunkBatch<C>, hint: usize, key: <C::Cursor as Cursor>::Key<'_>) -> usize {
99        let n = s.chunks.len();
100        // `last_key(i) < key`, from chunk `i`'s resident bounds.
101        let lt = |i: usize| <KeyCon<C> as BatchContainer>::reborrow(s.chunks[i].bounds().1.0)
102            .lt(&<KeyCon<C> as BatchContainer>::reborrow(key));
103        let hint = hint.min(n);
104        // The hint can skip the answer only on a backward seek; then search from the front.
105        let lo = if hint == 0 || lt(hint - 1) { hint } else { 0 };
106        if lo >= n || !lt(lo) { return lo; }
107        // Exponential search from `lo`, then binary within the final bracket.
108        let (mut prev, mut step) = (lo, 1usize);
109        while prev + step < n && lt(prev + step) { prev += step; step <<= 1; }
110        let (mut a, mut b) = (prev + 1, (prev + step).min(n));
111        while a < b { let m = a + (b - a) / 2; if lt(m) { a = m + 1; } else { b = m; } }
112        a
113    }
114}
115
116impl<C: NavigableChunk> Cursor for ChunkBatchCursor<C> {
117    type Storage = ChunkBatch<C>;
118
119    type KeyContainer = <C::Cursor as Cursor>::KeyContainer;
120    type Key<'a> = <C::Cursor as Cursor>::Key<'a>;
121    type ValContainer = <C::Cursor as Cursor>::ValContainer;
122    type Val<'a> = <C::Cursor as Cursor>::Val<'a>;
123    type ValOwn = <C::Cursor as Cursor>::ValOwn;
124    type TimeContainer = <C::Cursor as Cursor>::TimeContainer;
125    type TimeGat<'a> = <C::Cursor as Cursor>::TimeGat<'a>;
126    type Time = <C::Cursor as Cursor>::Time;
127    type DiffContainer = <C::Cursor as Cursor>::DiffContainer;
128    type DiffGat<'a> = <C::Cursor as Cursor>::DiffGat<'a>;
129    type Diff = <C::Cursor as Cursor>::Diff;
130
131    fn key_valid(&self, s: &Self::Storage) -> bool { self.chunk < s.chunks.len() && self.inner.as_ref().is_some_and(|i| i.key_valid(&s.chunks[self.chunk])) }
132    fn val_valid(&self, s: &Self::Storage) -> bool { self.chunk < s.chunks.len() && self.inner.as_ref().is_some_and(|i| i.val_valid(&s.chunks[self.chunk])) }
133    fn key<'a>(&self, s: &'a Self::Storage) -> Self::Key<'a> { self.inner.as_ref().unwrap().key(&s.chunks[self.chunk]) }
134    fn val<'a>(&self, s: &'a Self::Storage) -> Self::Val<'a> { self.inner.as_ref().unwrap().val(&s.chunks[self.chunk]) }
135    fn get_key<'a>(&self, s: &'a Self::Storage) -> Option<Self::Key<'a>> { if self.key_valid(s) { Some(self.key(s)) } else { None } }
136    fn get_val<'a>(&self, s: &'a Self::Storage) -> Option<Self::Val<'a>> { if self.val_valid(s) { Some(self.val(s)) } else { None } }
137
138    fn map_times<L: FnMut(Self::TimeGat<'_>, Self::DiffGat<'_>)>(&mut self, s: &Self::Storage, mut logic: L) {
139        if !self.val_valid(s) { return; }
140        let (k, v) = (self.key(s), self.val(s));
141        self.inner.as_mut().unwrap().map_times(&s.chunks[self.chunk], &mut logic);
142        // Follow the (key, val) forward across boundaries while it spills.
143        let mut c = self.chunk;
144        while c + 1 < s.chunks.len() && Self::val_spills(s, c, k, v) {
145            c += 1;
146            s.chunks[c].cursor().map_times(&s.chunks[c], &mut logic);
147        }
148    }
149
150    fn step_key(&mut self, s: &Self::Storage) {
151        if !self.key_valid(s) { return; }
152        let n = s.chunks.len();
153        let k = self.key(s);
154        // Advance to the last chunk the key spans.
155        while self.chunk + 1 < n && Self::key_spills(s, self.chunk, k) {
156            self.goto(self.chunk + 1, s);
157        }
158        // Step past the key within its last chunk.
159        {
160            let inner = self.inner.as_mut().unwrap();
161            inner.seek_key(&s.chunks[self.chunk], k);
162            inner.step_key(&s.chunks[self.chunk]);
163        }
164        // If that exhausted the chunk, the next key (if any) starts the next chunk.
165        if !self.inner.as_ref().unwrap().key_valid(&s.chunks[self.chunk]) && self.chunk + 1 < n {
166            self.goto(self.chunk + 1, s);
167        }
168        self.key_chunk = self.chunk;
169    }
170
171    fn seek_key(&mut self, s: &Self::Storage, key: Self::Key<'_>) {
172        let n = s.chunks.len();
173        // First chunk whose last key is `>= key`: where `key`'s run begins. Gallop from the
174        // current key's first chunk (`key_chunk`), a lower bound for forward seeks.
175        let lo = Self::locate_key(s, self.key_chunk, key);
176        self.goto(lo, s);
177        self.key_chunk = lo;
178        if lo < n { self.inner.as_mut().unwrap().seek_key(&s.chunks[lo], key); }
179    }
180
181    fn step_val(&mut self, s: &Self::Storage) {
182        if !self.val_valid(s) { return; }
183        let n = s.chunks.len();
184        let (k, v) = (self.key(s), self.val(s));
185        // Advance to the last chunk the (key, val) spans.
186        while self.chunk + 1 < n && Self::val_spills(s, self.chunk, k, v) {
187            self.goto(self.chunk + 1, s);
188        }
189        // Step past the (key, val) within that chunk.
190        self.inner.as_mut().unwrap().step_val(&s.chunks[self.chunk]);
191        // If the key's vals are exhausted here but the key spills, roll forward.
192        if !self.inner.as_ref().unwrap().val_valid(&s.chunks[self.chunk])
193            && self.chunk + 1 < n && Self::key_spills(s, self.chunk, k)
194        {
195            self.goto(self.chunk + 1, s);
196            self.inner.as_mut().unwrap().seek_key(&s.chunks[self.chunk], k);
197        }
198    }
199
200    fn seek_val(&mut self, s: &Self::Storage, val: Self::Val<'_>) {
201        if !self.key_valid(s) { return; }
202        let n = s.chunks.len();
203        let k = self.key(s);
204        loop {
205            self.inner.as_mut().unwrap().seek_val(&s.chunks[self.chunk], val);
206            if self.inner.as_ref().unwrap().val_valid(&s.chunks[self.chunk]) { return; }
207            // Key's vals exhausted in this chunk; if the key spills, retry in the next.
208            if self.chunk + 1 < n && Self::key_spills(s, self.chunk, k) {
209                self.goto(self.chunk + 1, s);
210                self.inner.as_mut().unwrap().seek_key(&s.chunks[self.chunk], k);
211            } else {
212                return;
213            }
214        }
215    }
216
217    fn rewind_keys(&mut self, s: &Self::Storage) {
218        self.key_chunk = 0;
219        self.goto(0, s);
220    }
221
222    fn rewind_vals(&mut self, s: &Self::Storage) {
223        if !self.key_valid(s) { return; }
224        let k = self.key(s);
225        let kc = self.key_chunk;
226        self.goto(kc, s);
227        self.inner.as_mut().unwrap().seek_key(&s.chunks[kc], k);
228    }
229}