Skip to main content

differential_dataflow/columnar/
updates.rs

1//! Trie-structured update storage.
2//!
3//! `UpdatesTyped<U>` is the core trie: four nested `Lists` (keys, vals, times, diffs).
4//! `Consolidating` is a streaming consolidator over sorted `(k,v,t,d)` data.
5//! `UpdatesBuilder` melds sorted, consolidated chunks into a single trie.
6//!
7//! NOTE: `UpdatesTyped::iter` / `form` / `form_unsorted` / `consolidate` / `filter_zero`
8//! are escape hatches that flatten the trie. Prefer trie-native operations where
9//! possible — flattening + rebuilding is a significant cost on hot paths.
10
11use columnar::{Columnar, Container, ContainerOf, Vecs, Borrow, Index, IndexAs, Len, Push};
12use columnar::primitive::offsets::Strides;
13use crate::difference::{Semigroup, IsZero};
14
15use super::layout::ColumnarUpdate as Update;
16
17/// A `Vecs` using strided offsets.
18pub type Lists<C> = Vecs<C, Strides>;
19
20/// Returns the non-empty lists once values are filtered by `keep`, and the bitmap of lists to keep.
21pub fn retain_items<'a, C: Container>(lists: <Lists<C> as Borrow>::Borrowed<'a>, keep: &[bool]) -> (Lists<C>, Vec<bool>) {
22
23    // In principle we can copy runs described in `bools` for bulk copying.
24    let mut output = <Lists::<C> as Container>::with_capacity_for([lists].into_iter());
25    let mut bitmap = Vec::with_capacity(lists.len());
26    assert_eq!(keep.len(), lists.values.len());
27    for list_index in 0 .. lists.len() {
28        let (lower, upper) = lists.bounds.bounds(list_index);
29        for item_index in lower .. upper {
30            if keep[item_index] {
31                output.values.push(lists.values.get(item_index));
32            }
33        }
34        if output.values.len() > columnar::Index::last(&output.bounds.borrow()).unwrap_or(0) as usize {
35            output.bounds.push(output.values.len() as u64);
36            bitmap.push(true);
37        }
38        else { bitmap.push(false); }
39    }
40
41    assert_eq!(bitmap.len(), lists.len());
42    (output, bitmap)
43}
44
45
46/// Trie-structured update storage using columnar containers.
47///
48/// Four nested layers of `Lists`:
49/// - `keys`: lists of keys (outer lists are independent groups)
50/// - `vals`: per-key, lists of vals
51/// - `times`: per-val, lists of times
52/// - `diffs`: per-time, lists of diffs (singletons when consolidated)
53///
54/// A flat unsorted input has stride 1 at every level (one key per entry,
55/// one val per key, one time per val, one diff per time).
56/// A fully consolidated trie has a single outer key list, all lists sorted
57/// and deduplicated, and singleton diff lists.
58pub struct UpdatesTyped<U: Update> {
59    /// Outer key list (one entry per group of keys at the trie root).
60    pub keys:  Lists<ContainerOf<U::Key>>,
61    /// Per-key list of vals.
62    pub vals:  Lists<ContainerOf<U::Val>>,
63    /// Per-val list of times.
64    pub times: Lists<ContainerOf<U::Time>>,
65    /// Per-time list of diffs (one diff per time after consolidation).
66    pub diffs: Lists<ContainerOf<U::Diff>>,
67}
68
69impl<U: Update> Default for UpdatesTyped<U> {
70    fn default() -> Self {
71        Self {
72            keys: Default::default(),
73            vals: Default::default(),
74            times: Default::default(),
75            diffs: Default::default(),
76        }
77    }
78}
79
80impl<U: Update> std::fmt::Debug for UpdatesTyped<U> {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        f.debug_struct("UpdatesTyped").finish()
83    }
84}
85
86impl<U: Update> Clone for UpdatesTyped<U> {
87    fn clone(&self) -> Self {
88        Self {
89            keys: self.keys.clone(),
90            vals: self.vals.clone(),
91            times: self.times.clone(),
92            diffs: self.diffs.clone(),
93        }
94    }
95}
96
97/// Borrowed view of an [`UpdatesTyped<U>`] with the same four-field shape.
98///
99/// Reader code should consume an `UpdatesTyped` through this view rather than reading
100/// fields directly. This decouples readers from the storage representation: the
101/// view's shape stays the same whether the underlying `UpdatesTyped` holds owned
102/// `Lists` or (later) `Stash`-backed columns that may be borrowed from wire bytes.
103pub struct UpdatesView<'a, U: Update> {
104    /// Outer key list (one entry per group of keys at the trie root).
105    pub keys:  <Lists<ContainerOf<U::Key>>  as Borrow>::Borrowed<'a>,
106    /// Per-key list of vals.
107    pub vals:  <Lists<ContainerOf<U::Val>>  as Borrow>::Borrowed<'a>,
108    /// Per-val list of times.
109    pub times: <Lists<ContainerOf<U::Time>> as Borrow>::Borrowed<'a>,
110    /// Per-time list of diffs.
111    pub diffs: <Lists<ContainerOf<U::Diff>> as Borrow>::Borrowed<'a>,
112}
113
114impl<'a, U: Update> Copy for UpdatesView<'a, U> {}
115impl<'a, U: Update> Clone for UpdatesView<'a, U> { fn clone(&self) -> Self { *self } }
116
117impl<'a, U: Update> UpdatesView<'a, U> {
118    /// Iterate all `(key, val, time, diff)` entries as refs.
119    ///
120    /// A streaming cursor over the four columns (see [`UpdatesIter`]) — one cheap
121    /// boundary advance per leaf, with an exact length — rather than a 4-level
122    /// nested `flat_map`. This is the hot flattening path (`consolidate`,
123    /// `form_unsorted`, every `iter()` consumer), so it earns the hand-rolling.
124    pub fn iter(self) -> UpdatesIter<'a, U> {
125        // One output per leaf diff; `index_as(0)` is each first group's end
126        // (== `child_range(_, 0).end`). Guard the empty trie so `next` short-circuits.
127        let leaves = Len::len(&self.diffs.values);
128        let (v_end, t_end, d_end) = if leaves > 0 {
129            (self.vals.bounds.index_as(0) as usize,
130             self.times.bounds.index_as(0) as usize,
131             self.diffs.bounds.index_as(0) as usize)
132        } else { (0, 0, 0) };
133        UpdatesIter { view: self, k: 0, v: 0, t: 0, d: 0, v_end, t_end, d_end, leaves }
134    }
135
136    /// Translate a key-range into the corresponding val-range via `vals.bounds`.
137    pub fn vals_bounds(self, key_range: std::ops::Range<usize>) -> std::ops::Range<usize> {
138        if !key_range.is_empty() {
139            let bounds = self.vals.bounds;
140            let lower = if key_range.start == 0 { 0 } else { bounds.index_as(key_range.start - 1) as usize };
141            let upper = bounds.index_as(key_range.end - 1) as usize;
142            lower..upper
143        } else { key_range }
144    }
145    /// Translate a val-range into the corresponding time-range via `times.bounds`.
146    pub fn times_bounds(self, val_range: std::ops::Range<usize>) -> std::ops::Range<usize> {
147        if !val_range.is_empty() {
148            let bounds = self.times.bounds;
149            let lower = if val_range.start == 0 { 0 } else { bounds.index_as(val_range.start - 1) as usize };
150            let upper = bounds.index_as(val_range.end - 1) as usize;
151            lower..upper
152        } else { val_range }
153    }
154}
155
156/// Streaming cursor over a trie's `(key, val, time, diff)` leaves.
157///
158/// Walks the four columns with running boundary cursors `(k, v, t, d)`: `d`
159/// drives one output per leaf diff, and `k`/`v`/`t` advance lazily as each
160/// group is exhausted (`*_end` is the current group's upper bound, the same
161/// `child_range(_, i).end == bounds.index_as(i)`). Empty groups are skipped by
162/// the cascade; `leaves` bounds the walk and yields an exact `size_hint`.
163pub struct UpdatesIter<'a, U: Update> {
164    view: UpdatesView<'a, U>,
165    k: usize, v: usize, t: usize, d: usize,
166    v_end: usize, t_end: usize, d_end: usize,
167    leaves: usize,
168}
169
170impl<'a, U: Update> Iterator for UpdatesIter<'a, U> {
171    type Item = (
172        columnar::Ref<'a, U::Key>,
173        columnar::Ref<'a, U::Val>,
174        columnar::Ref<'a, U::Time>,
175        columnar::Ref<'a, U::Diff>,
176    );
177
178    #[inline]
179    fn next(&mut self) -> Option<Self::Item> {
180        if self.d >= self.leaves { return None; }
181        // Advance key/val/time cursors until `d` lands in the current time's diff
182        // run. Each `*_end` is the exhausted group's upper bound; contiguity means
183        // the next group's lower bound is exactly where we already sit.
184        while self.d >= self.d_end {
185            self.t += 1;
186            while self.t >= self.t_end {
187                self.v += 1;
188                while self.v >= self.v_end {
189                    self.k += 1;
190                    self.v_end = self.view.vals.bounds.index_as(self.k) as usize;
191                }
192                self.t_end = self.view.times.bounds.index_as(self.v) as usize;
193            }
194            self.d_end = self.view.diffs.bounds.index_as(self.t) as usize;
195        }
196        let item = (
197            self.view.keys.values.get(self.k),
198            self.view.vals.values.get(self.v),
199            self.view.times.values.get(self.t),
200            self.view.diffs.values.get(self.d),
201        );
202        self.d += 1;
203        Some(item)
204    }
205
206    #[inline]
207    fn size_hint(&self) -> (usize, Option<usize>) {
208        let rem = self.leaves - self.d;
209        (rem, Some(rem))
210    }
211}
212
213impl<'a, U: Update> ExactSizeIterator for UpdatesIter<'a, U> {}
214
215impl<U: Update> UpdatesTyped<U> {
216    /// Borrow the four columns as a single `UpdatesView`.
217    pub fn view(&self) -> UpdatesView<'_, U> {
218        UpdatesView {
219            keys:  self.keys.borrow(),
220            vals:  self.vals.borrow(),
221            times: self.times.borrow(),
222            diffs: self.diffs.borrow(),
223        }
224    }
225}
226
227/// `Stash`-backed update storage: each column may be typed (writable) or
228/// borrowed from wire bytes (read-only, zero-copy).
229///
230/// Construction sites work in [`UpdatesTyped`]; convert via `From` at the
231/// boundary. Reader code uses [`UpdatesView`] via [`Updates::view`], which
232/// produces the same shape regardless of whether the columns are typed or
233/// borrowed.
234pub struct Updates<U: Update, B = timely::bytes::arc::Bytes> {
235    /// Outer key list (one entry per group of keys at the trie root).
236    pub keys:  columnar::bytes::stash::Stash<Lists<ContainerOf<U::Key>>,  B>,
237    /// Per-key list of vals.
238    pub vals:  columnar::bytes::stash::Stash<Lists<ContainerOf<U::Val>>,  B>,
239    /// Per-val list of times.
240    pub times: columnar::bytes::stash::Stash<Lists<ContainerOf<U::Time>>, B>,
241    /// Per-time list of diffs.
242    pub diffs: columnar::bytes::stash::Stash<Lists<ContainerOf<U::Diff>>, B>,
243}
244
245impl<U: Update, B> Default for Updates<U, B> {
246    fn default() -> Self {
247        Self {
248            keys:  Default::default(),
249            vals:  Default::default(),
250            times: Default::default(),
251            diffs: Default::default(),
252        }
253    }
254}
255
256impl<U: Update, B: Clone> Clone for Updates<U, B> {
257    fn clone(&self) -> Self {
258        Self {
259            keys:  self.keys.clone(),
260            vals:  self.vals.clone(),
261            times: self.times.clone(),
262            diffs: self.diffs.clone(),
263        }
264    }
265}
266
267impl<U: Update> Updates<U> {
268    /// Reconstruct from bytes produced by [`write_to`](Updates::write_to). Columns
269    /// are wrapped as `Stash::Bytes` (zero-copy, read-only) over the input `bytes`.
270    pub fn read_from(mut bytes: timely::bytes::arc::Bytes) -> Self {
271        use columnar::bytes::stash::Stash;
272        let header = bytes.extract_to(32);
273        let len = |i: usize| u64::from_le_bytes(header[i*8..i*8+8].try_into().unwrap()) as usize;
274        let (kl, vl, tl, dl) = (len(0), len(1), len(2), len(3));
275        let keys  = Stash::try_from_bytes(bytes.extract_to(kl)).expect("keys decode");
276        let vals  = Stash::try_from_bytes(bytes.extract_to(vl)).expect("vals decode");
277        let times = Stash::try_from_bytes(bytes.extract_to(tl)).expect("times decode");
278        let diffs = Stash::try_from_bytes(bytes.extract_to(dl)).expect("diffs decode");
279        Updates { keys, vals, times, diffs }
280    }
281}
282
283impl<U: Update, B> From<UpdatesTyped<U>> for Updates<U, B> {
284    fn from(owned: UpdatesTyped<U>) -> Self {
285        use columnar::bytes::stash::Stash;
286        Self {
287            keys:  Stash::Typed(owned.keys),
288            vals:  Stash::Typed(owned.vals),
289            times: Stash::Typed(owned.times),
290            diffs: Stash::Typed(owned.diffs),
291        }
292    }
293}
294
295impl<U: Update, B: std::ops::Deref<Target = [u8]> + Clone + 'static> Updates<U, B> {
296    /// Borrow the four columns as a single `UpdatesView`.
297    pub fn view(&self) -> UpdatesView<'_, U> {
298        UpdatesView {
299            keys:  self.keys.borrow(),
300            vals:  self.vals.borrow(),
301            times: self.times.borrow(),
302            diffs: self.diffs.borrow(),
303        }
304    }
305
306    /// Total number of updates (records) in the trie.
307    pub fn len(&self) -> usize {
308        self.view().diffs.values.len()
309    }
310
311    /// Whether the trie is empty.
312    pub fn is_empty(&self) -> bool { self.len() == 0 }
313
314    /// Serialize the four columns to `writer`: a 32-byte header of per-column
315    /// byte lengths, then each column's `Stash` encoding. Round-trips with
316    /// [`read_from`](Updates::read_from). Used to spill a chunk to backing storage.
317    pub fn write_to<W: std::io::Write>(&self, writer: &mut W) {
318        let lens = [
319            self.keys.length_in_bytes()  as u64,
320            self.vals.length_in_bytes()  as u64,
321            self.times.length_in_bytes() as u64,
322            self.diffs.length_in_bytes() as u64,
323        ];
324        for l in lens { writer.write_all(&l.to_le_bytes()).unwrap(); }
325        self.keys.write_bytes(writer).unwrap();
326        self.vals.write_bytes(writer).unwrap();
327        self.times.write_bytes(writer).unwrap();
328        self.diffs.write_bytes(writer).unwrap();
329    }
330
331    /// Total serialized size in bytes (matches what [`write_to`](Updates::write_to) emits).
332    pub fn length_in_bytes(&self) -> usize {
333        32 + self.keys.length_in_bytes() + self.vals.length_in_bytes()
334           + self.times.length_in_bytes() + self.diffs.length_in_bytes()
335    }
336
337    /// Convert to fully owned form, copying any `Stash::Bytes` columns into
338    /// typed `Lists`. Already-typed columns pass through with no copy.
339    ///
340    /// This method should be avoided unless typed containers are truly needed.
341    pub fn into_typed(mut self) -> UpdatesTyped<U> {
342        use columnar::bytes::stash::Stash;
343        self.keys.make_typed();
344        self.vals.make_typed();
345        self.times.make_typed();
346        self.diffs.make_typed();
347        let Stash::Typed(keys)  = self.keys  else { unreachable!() };
348        let Stash::Typed(vals)  = self.vals  else { unreachable!() };
349        let Stash::Typed(times) = self.times else { unreachable!() };
350        let Stash::Typed(diffs) = self.diffs else { unreachable!() };
351        UpdatesTyped { keys, vals, times, diffs }
352    }
353}
354
355/// The flat `(key, val, time, diff)` tuple for an [`Update`].
356pub type Tuple<U> = (<U as Update>::Key, <U as Update>::Val, <U as Update>::Time, <U as Update>::Diff);
357
358/// Returns the value-index range for list `i` given cumulative bounds.
359#[inline]
360pub fn child_range<B: IndexAs<u64>>(bounds: B, i: usize) -> std::ops::Range<usize> {
361    let lower = if i == 0 { 0 } else { bounds.index_as(i - 1) as usize };
362    let upper = bounds.index_as(i) as usize;
363    lower..upper
364}
365
366/// A streaming consolidation iterator for sorted `(key, val, time, diff)` data.
367///
368/// Accumulates diffs for equal `(key, val, time)` triples, yielding at most
369/// one output per distinct triple, with a non-zero accumulated diff.
370/// Input must be sorted by `(key, val, time)`.
371pub struct Consolidating<I: Iterator, D> {
372    iter: std::iter::Peekable<I>,
373    diff: D,
374}
375
376impl<K, V, T, D, I> Consolidating<I, D>
377where
378    K: Copy + Eq,
379    V: Copy + Eq,
380    T: Copy + Eq,
381    D: Semigroup + IsZero + Default,
382    I: Iterator<Item = (K, V, T, D)>,
383{
384    /// Wrap a sorted `(K, V, T, D)` iterator so adjacent equal `(K, V, T)`
385    /// runs accumulate into a single output with the summed diff.
386    pub fn new(iter: I) -> Self {
387        Self { iter: iter.peekable(), diff: D::default() }
388    }
389}
390
391impl<K, V, T, D, I> Iterator for Consolidating<I, D>
392where
393    K: Copy + Eq,
394    V: Copy + Eq,
395    T: Copy + Eq,
396    D: Semigroup + IsZero + Default + Clone,
397    I: Iterator<Item = (K, V, T, D)>,
398{
399    type Item = (K, V, T, D);
400    fn next(&mut self) -> Option<Self::Item> {
401        loop {
402            let (k, v, t, d) = self.iter.next()?;
403            self.diff = d;
404            while let Some(&(k2, v2, t2, _)) = self.iter.peek() {
405                if k2 == k && v2 == v && t2 == t {
406                    let (_, _, _, d2) = self.iter.next().unwrap();
407                    self.diff.plus_equals(&d2);
408                } else {
409                    break;
410                }
411            }
412            if !self.diff.is_zero() {
413                return Some((k, v, t, self.diff.clone()));
414            }
415        }
416    }
417}
418
419impl<U: Update> UpdatesTyped<U> {
420
421    /// Copies `other[key_range]` into self, keys and all.
422    pub fn extend_from_keys(&mut self, other: UpdatesView<'_, U>, key_range: std::ops::Range<usize>) {
423        self.keys.values.extend_from_self(other.keys.values, key_range.clone());
424        self.vals.extend_from_self(other.vals, key_range.clone());
425        let val_range = other.vals_bounds(key_range);
426        self.times.extend_from_self(other.times, val_range.clone());
427        let time_range = other.times_bounds(val_range);
428        self.diffs.extend_from_self(other.diffs, time_range);
429    }
430
431    /// Forms a consolidated `UpdatesTyped` trie from unsorted `(key, val, time, diff)` refs.
432    pub fn form_unsorted<'a>(unsorted: impl Iterator<Item = columnar::Ref<'a, Tuple<U>>>) -> Self {
433        let mut data = unsorted.collect::<Vec<_>>();
434        // Unstable is faster (pdqsort, no scratch alloc); equal `(k,v,t)` entries
435        // reorder freely since `form` sums their diffs commutatively.
436        data.sort_unstable();
437        Self::form(data.into_iter())
438    }
439
440    /// Forms a consolidated `UpdatesTyped` trie from sorted `(key, val, time, diff)` refs.
441    pub fn form<'a>(sorted: impl Iterator<Item = columnar::Ref<'a, Tuple<U>>>) -> Self {
442
443        // Step 1: Streaming consolidation — accumulate diffs, drop zeros.
444        let consolidated = Consolidating::new(
445            sorted.map(|(k, v, t, d)| (k, v, t, <U::Diff as Columnar>::into_owned(d)))
446        );
447
448        // Step 2: Build the trie from consolidated, sorted, non-zero data.
449        let mut output = Self::default();
450        let mut updates = consolidated;
451        if let Some((key, val, time, diff)) = updates.next() {
452            let mut prev = (key, val, time);
453            output.keys.values.push(key);
454            output.vals.values.push(val);
455            output.times.values.push(time);
456            output.diffs.values.push(&diff);
457            output.diffs.bounds.push(output.diffs.values.len() as u64);
458
459            // As we proceed, seal up known complete runs.
460            for (key, val, time, diff) in updates {
461
462                // If keys differ, record key and seal vals and times.
463                if key != prev.0 {
464                    output.vals.bounds.push(output.vals.values.len() as u64);
465                    output.times.bounds.push(output.times.values.len() as u64);
466                    output.keys.values.push(key);
467                    output.vals.values.push(val);
468                }
469                // If vals differ, record val and seal times.
470                else if val != prev.1 {
471                    output.times.bounds.push(output.times.values.len() as u64);
472                    output.vals.values.push(val);
473                }
474                else {
475                    // We better not find a duplicate time.
476                    assert!(time != prev.2);
477                }
478
479                // Always record (time, diff).
480                output.times.values.push(time);
481                output.diffs.values.push(&diff);
482                output.diffs.bounds.push(output.diffs.values.len() as u64);
483
484                prev = (key, val, time);
485            }
486
487            // Seal up open lists.
488            output.keys.bounds.push(output.keys.values.len() as u64);
489            output.vals.bounds.push(output.vals.values.len() as u64);
490            output.times.bounds.push(output.times.values.len() as u64);
491        }
492
493        output
494    }
495
496    /// Consolidates into canonical trie form:
497    /// single outer key list, all lists sorted and deduplicated,
498    /// diff lists are singletons (or absent if cancelled).
499    pub fn consolidate(self) -> Self { Self::form_unsorted(self.iter()) }
500    /// Drop entries whose diff list is empty (cancelled), rebuilding the trie.
501    pub fn filter_zero(self) -> Self {
502        if self.diffs.bounds.strided() == Some(1) { self }
503        // TODO: rework to move from trie structure to trie structure.
504        else {
505            let mut keep = Vec::with_capacity(self.times.values.len());
506            for index in 0 .. self.times.values.len() {
507                keep.push({
508                    let (lower, upper) = self.diffs.bounds.bounds(index);
509                    lower < upper
510                });
511            }
512            let (times, keep) = retain_items(self.times.borrow(), &keep[..]);
513            let (vals, keep) = retain_items(self.vals.borrow(), &keep[..]);
514            let (keys, _keep) = retain_items(self.keys.borrow(), &keep[..]);
515            UpdatesTyped {
516                keys,
517                vals,
518                times,
519                diffs: Lists {
520                    bounds: Strides::new(1, self.diffs.values.len() as u64),
521                    values: self.diffs.values,
522                },
523            }
524        }
525        // else { Self::form(self.iter()) }
526    }
527
528    /// The number of leaf-level diff entries (total updates).
529    pub fn len(&self) -> usize { self.diffs.values.len() }
530}
531
532/// Push a single flat update as a stride-1 entry.
533///
534/// Each field is independently typed — columnar refs, `&Owned`, owned values,
535/// or any other type the column container accepts via its `Push` impl.
536impl<KP, VP, TP, DP, U: Update> Push<(KP, VP, TP, DP)> for UpdatesTyped<U>
537where
538    ContainerOf<U::Key>: Push<KP>,
539    ContainerOf<U::Val>: Push<VP>,
540    ContainerOf<U::Time>: Push<TP>,
541    ContainerOf<U::Diff>: Push<DP>,
542{
543    fn push(&mut self, (key, val, time, diff): (KP, VP, TP, DP)) {
544        self.keys.values.push(key);
545        self.keys.bounds.push(self.keys.values.len() as u64);
546        self.vals.values.push(val);
547        self.vals.bounds.push(self.vals.values.len() as u64);
548        self.times.values.push(time);
549        self.times.bounds.push(self.times.values.len() as u64);
550        self.diffs.values.push(diff);
551        self.diffs.bounds.push(self.diffs.values.len() as u64);
552    }
553}
554
555/// PushInto for the `((K, V), T, R)` shape that reduce_trace uses.
556impl<U: Update> timely::container::PushInto<((U::Key, U::Val), U::Time, U::Diff)> for UpdatesTyped<U> {
557    fn push_into(&mut self, ((key, val), time, diff): ((U::Key, U::Val), U::Time, U::Diff)) {
558        self.push((&key, &val, &time, &diff));
559    }
560}
561
562impl<U: Update> UpdatesTyped<U> {
563
564    /// Iterate all `(key, val, time, diff)` entries as refs.
565    pub fn iter(&self) -> impl Iterator<Item = (
566        columnar::Ref<'_, U::Key>,
567        columnar::Ref<'_, U::Val>,
568        columnar::Ref<'_, U::Time>,
569        columnar::Ref<'_, U::Diff>,
570    )> {
571        self.view().iter()
572    }
573}
574
575impl<U: Update> timely::Accountable for UpdatesTyped<U> {
576    #[inline] fn record_count(&self) -> i64 { Len::len(&self.diffs.values) as i64 }
577}
578
579impl<U: Update> timely::dataflow::channels::ContainerBytes for UpdatesTyped<U> {
580    fn from_bytes(_bytes: timely::bytes::arc::Bytes) -> Self { unimplemented!() }
581    fn length_in_bytes(&self) -> usize { unimplemented!() }
582    fn into_bytes<W: std::io::Write>(&self, _writer: &mut W) { unimplemented!() }
583}
584
585/// An incremental trie builder that accepts sorted, consolidated `UpdatesTyped` chunks
586/// and melds them into a single `UpdatesTyped` trie.
587///
588/// The internal `UpdatesTyped` has open (unsealed) bounds at the keys, vals, and times
589/// levels — the last group at each level has its values pushed but no corresponding
590/// bounds entry. `diffs.bounds` is always 1:1 with `times.values`.
591///
592/// `meld` accepts a consolidated `UpdatesTyped` whose first `(key, val, time)` is
593/// strictly greater than the builder's last `(key, val, time)`. The key and val
594/// may equal the builder's current open key/val, as long as the time is greater.
595///
596/// `done` seals all open bounds and returns the completed `UpdatesTyped`.
597pub struct UpdatesBuilder<U: Update> {
598    /// Non-empty, consolidated updates.
599    updates: UpdatesTyped<U>,
600}
601
602impl<U: Update> UpdatesBuilder<U> {
603    /// Construct a new builder from consolidated, sealed updates.
604    ///
605    /// Unseals the last group at keys, vals, and times levels so that
606    /// subsequent `meld` calls can extend the open groups.
607    /// If the updates are not consolidated none of this works.
608    pub fn new_from(mut updates: UpdatesTyped<U>) -> Self {
609        use columnar::Len;
610        if Len::len(&updates.keys.values) > 0 {
611            updates.keys.bounds.pop();
612            updates.vals.bounds.pop();
613            updates.times.bounds.pop();
614        }
615        Self { updates }
616    }
617
618    /// Meld a sorted, consolidated `UpdatesTyped` chunk into this builder.
619    ///
620    /// The chunk's first `(key, val, time)` must be strictly greater than
621    /// the builder's last `(key, val, time)`. Keys and vals may overlap
622    /// (continue the current group), but times must be strictly increasing
623    /// within the same `(key, val)`.
624    pub fn meld(&mut self, chunk: &UpdatesTyped<U>) {
625        use columnar::{Borrow, Index, Len};
626
627        if chunk.len() == 0 { return; }
628
629        // Empty builder: clone the chunk and unseal it.
630        if Len::len(&self.updates.keys.values) == 0 {
631            self.updates = chunk.clone();
632            self.updates.keys.bounds.pop();
633            self.updates.vals.bounds.pop();
634            self.updates.times.bounds.pop();
635            return;
636        }
637
638        // Pre-compute boundary comparisons before mutating.
639        let keys_match = {
640            let skb = self.updates.keys.values.borrow();
641            let ckb = chunk.keys.values.borrow();
642            skb.get(Len::len(&skb) - 1) == ckb.get(0)
643        };
644        let vals_match = keys_match && {
645            let svb = self.updates.vals.values.borrow();
646            let cvb = chunk.vals.values.borrow();
647            svb.get(Len::len(&svb) - 1) == cvb.get(0)
648        };
649
650        let chunk_num_keys = Len::len(&chunk.keys.values);
651        let chunk_num_vals = Len::len(&chunk.vals.values);
652        let chunk_num_times = Len::len(&chunk.times.values);
653
654        // Child ranges for the first element at each level of the chunk.
655        let first_key_vals = child_range(chunk.vals.borrow().bounds, 0);
656        let first_val_times = child_range(chunk.times.borrow().bounds, 0);
657
658        // There is a first position where coordinates disagree.
659        // Strictly beyond that position: seal bounds, extend lists, re-open the last bound.
660        // At that position: meld the first list, extend subsequent lists, re-open.
661        let mut differ = false;
662
663        // --- Keys ---
664        if keys_match {
665            // Skip the duplicate first key; add remaining keys.
666            if chunk_num_keys > 1 {
667                self.updates.keys.values.extend_from_self(chunk.keys.values.borrow(), 1..chunk_num_keys);
668            }
669        } else {
670            // All keys are new.
671            self.updates.keys.values.extend_from_self(chunk.keys.values.borrow(), 0..chunk_num_keys);
672            differ = true;
673        }
674
675        // --- Vals ---
676        if differ {
677            // Keys differed: seal open val group, extend all val lists, unseal last.
678            self.updates.vals.bounds.push(Len::len(&self.updates.vals.values) as u64);
679            self.updates.vals.extend_from_self(chunk.vals.borrow(), 0..chunk_num_keys);
680            self.updates.vals.bounds.pop();
681        } else {
682            // Keys matched: meld vals for the shared key.
683            if vals_match {
684                // Skip the duplicate first val; add remaining vals from the first key's list.
685                if first_key_vals.len() > 1 {
686                    self.updates.vals.values.extend_from_self(
687                        chunk.vals.values.borrow(),
688                        (first_key_vals.start + 1)..first_key_vals.end,
689                    );
690                }
691            } else {
692                // First val differs: add all vals from the first key's list.
693                self.updates.vals.values.extend_from_self(
694                    chunk.vals.values.borrow(),
695                    first_key_vals.clone(),
696                );
697                differ = true;
698            }
699            // Seal the matched key's val group, extend remaining keys' val lists, unseal.
700            if chunk_num_keys > 1 {
701                self.updates.vals.bounds.push(Len::len(&self.updates.vals.values) as u64);
702                self.updates.vals.extend_from_self(chunk.vals.borrow(), 1..chunk_num_keys);
703                self.updates.vals.bounds.pop();
704            }
705        }
706
707        // --- Times ---
708        if differ {
709            // Seal open time group, extend all time lists, unseal last.
710            self.updates.times.bounds.push(Len::len(&self.updates.times.values) as u64);
711            self.updates.times.extend_from_self(chunk.times.borrow(), 0..chunk_num_vals);
712            self.updates.times.bounds.pop();
713        } else {
714            // Keys and vals matched. Times must be strictly greater (precondition),
715            // so we always set differ = true here.
716            debug_assert!({
717                let stb = self.updates.times.values.borrow();
718                let ctb = chunk.times.values.borrow();
719                stb.get(Len::len(&stb) - 1) != ctb.get(0)
720            }, "meld: duplicate time within same (key, val)");
721            // Add times from the first val's time list into the open group.
722            self.updates.times.values.extend_from_self(
723                chunk.times.values.borrow(),
724                first_val_times.clone(),
725            );
726            differ = true;
727            // Seal the matched val's time group, extend remaining vals' time lists, unseal.
728            if chunk_num_vals > 1 {
729                self.updates.times.bounds.push(Len::len(&self.updates.times.values) as u64);
730                self.updates.times.extend_from_self(chunk.times.borrow(), 1..chunk_num_vals);
731                self.updates.times.bounds.pop();
732            }
733        }
734
735        // --- Diffs ---
736        // Diffs are always sealed (1:1 with times). By the precondition that
737        // times are strictly increasing for the same (key, val), differ is
738        // always true by this point — just extend all diff lists.
739        debug_assert!(differ);
740        self.updates.diffs.extend_from_self(chunk.diffs.borrow(), 0..chunk_num_times);
741    }
742
743    /// Seal all open bounds and return the completed `UpdatesTyped`.
744    pub fn done(mut self) -> UpdatesTyped<U> {
745        use columnar::Len;
746        if Len::len(&self.updates.keys.values) > 0 {
747            // Seal the open time group.
748            self.updates.times.bounds.push(Len::len(&self.updates.times.values) as u64);
749            // Seal the open val group.
750            self.updates.vals.bounds.push(Len::len(&self.updates.vals.values) as u64);
751            // Seal the outer key group.
752            self.updates.keys.bounds.push(Len::len(&self.updates.keys.values) as u64);
753        }
754        self.updates
755    }
756}
757
758#[cfg(test)]
759mod tests {
760    use super::*;
761    use columnar::Push;
762
763    type TestUpdate = (u64, u64, u64, i64);
764
765    fn collect(updates: &UpdatesTyped<TestUpdate>) -> Vec<(u64, u64, u64, i64)> {
766        updates.iter().map(|(k, v, t, d)| (*k, *v, *t, *d)).collect()
767    }
768
769    #[test]
770    fn test_push_and_consolidate_basic() {
771        let mut updates = UpdatesTyped::<TestUpdate>::default();
772        updates.push((&1, &10, &100, &1));
773        updates.push((&1, &10, &100, &2));
774        updates.push((&2, &20, &200, &5));
775        assert_eq!(updates.len(), 3);
776        assert_eq!(collect(&updates.consolidate()), vec![(1, 10, 100, 3), (2, 20, 200, 5)]);
777    }
778
779    #[test]
780    fn test_cancellation() {
781        let mut updates = UpdatesTyped::<TestUpdate>::default();
782        updates.push((&1, &10, &100, &3));
783        updates.push((&1, &10, &100, &-3));
784        updates.push((&2, &20, &200, &1));
785        assert_eq!(collect(&updates.consolidate()), vec![(2, 20, 200, 1)]);
786    }
787
788    #[test]
789    fn test_multiple_vals_and_times() {
790        let mut updates = UpdatesTyped::<TestUpdate>::default();
791        updates.push((&1, &10, &100, &1));
792        updates.push((&1, &10, &200, &2));
793        updates.push((&1, &20, &100, &3));
794        updates.push((&1, &20, &100, &4));
795        assert_eq!(collect(&updates.consolidate()), vec![(1, 10, 100, 1), (1, 10, 200, 2), (1, 20, 100, 7)]);
796    }
797
798    #[test]
799    fn test_val_cancellation_propagates() {
800        let mut updates = UpdatesTyped::<TestUpdate>::default();
801        updates.push((&1, &10, &100, &5));
802        updates.push((&1, &10, &100, &-5));
803        updates.push((&1, &20, &100, &1));
804        assert_eq!(collect(&updates.consolidate()), vec![(1, 20, 100, 1)]);
805    }
806
807    #[test]
808    fn test_empty() {
809        let updates = UpdatesTyped::<TestUpdate>::default();
810        assert_eq!(collect(&updates.consolidate()), vec![]);
811    }
812
813    #[test]
814    fn test_total_cancellation() {
815        let mut updates = UpdatesTyped::<TestUpdate>::default();
816        updates.push((&1, &10, &100, &1));
817        updates.push((&1, &10, &100, &-1));
818        assert_eq!(collect(&updates.consolidate()), vec![]);
819    }
820
821    #[test]
822    fn test_unsorted_input() {
823        let mut updates = UpdatesTyped::<TestUpdate>::default();
824        updates.push((&3, &30, &300, &1));
825        updates.push((&1, &10, &100, &2));
826        updates.push((&2, &20, &200, &3));
827        assert_eq!(collect(&updates.consolidate()), vec![(1, 10, 100, 2), (2, 20, 200, 3), (3, 30, 300, 1)]);
828    }
829
830    #[test]
831    fn test_first_key_cancels() {
832        let mut updates = UpdatesTyped::<TestUpdate>::default();
833        updates.push((&1, &10, &100, &5));
834        updates.push((&1, &10, &100, &-5));
835        updates.push((&2, &20, &200, &3));
836        assert_eq!(collect(&updates.consolidate()), vec![(2, 20, 200, 3)]);
837    }
838
839    #[test]
840    fn test_middle_time_cancels() {
841        let mut updates = UpdatesTyped::<TestUpdate>::default();
842        updates.push((&1, &10, &100, &1));
843        updates.push((&1, &10, &200, &2));
844        updates.push((&1, &10, &200, &-2));
845        updates.push((&1, &10, &300, &3));
846        assert_eq!(collect(&updates.consolidate()), vec![(1, 10, 100, 1), (1, 10, 300, 3)]);
847    }
848
849    #[test]
850    fn test_first_val_cancels() {
851        let mut updates = UpdatesTyped::<TestUpdate>::default();
852        updates.push((&1, &10, &100, &1));
853        updates.push((&1, &10, &100, &-1));
854        updates.push((&1, &20, &100, &5));
855        assert_eq!(collect(&updates.consolidate()), vec![(1, 20, 100, 5)]);
856    }
857
858    #[test]
859    fn test_interleaved_cancellations() {
860        let mut updates = UpdatesTyped::<TestUpdate>::default();
861        updates.push((&1, &10, &100, &1));
862        updates.push((&1, &10, &100, &-1));
863        updates.push((&2, &20, &200, &7));
864        updates.push((&3, &30, &300, &4));
865        updates.push((&3, &30, &300, &-4));
866        assert_eq!(collect(&updates.consolidate()), vec![(2, 20, 200, 7)]);
867    }
868}