1use std::collections::VecDeque;
19use std::marker::PhantomData;
20
21use crate::columnation::ColumnationStack;
22use columnar::Container as _;
23use columnar::Push as _;
24use columnar::bytes::indexed;
25use columnar::{BorrowedOf, Clear, Columnar, Index, Len};
26use columnation::Columnation;
27use differential_dataflow::difference::Semigroup;
28use differential_dataflow::trace::implementations::merge_batcher::{Extractor, Merger};
29use timely::Container;
30use timely::PartialOrder;
31use timely::container::{ContainerBuilder, PushInto};
32use timely::progress::frontier::{Antichain, AntichainRef};
33
34use crate::columnar::Column;
35use crate::columnar::body::ColumnBody;
36
37#[derive(Default)]
39pub struct Chunker<C> {
40 target: C,
45 ready: VecDeque<C>,
47}
48
49impl<C: Container + Clone + 'static> ContainerBuilder for Chunker<C> {
50 type Container = C;
51
52 fn extract(&mut self) -> Option<&mut Self::Container> {
53 if let Some(ready) = self.ready.pop_front() {
54 self.target = ready;
55 Some(&mut self.target)
56 } else {
57 None
58 }
59 }
60
61 fn finish(&mut self) -> Option<&mut Self::Container> {
62 self.extract()
63 }
64}
65
66impl<'a, D, T, R> PushInto<&'a mut Column<(D, T, R)>> for Chunker<ColumnationStack<(D, T, R)>>
67where
68 D: Columnar + Columnation,
69 for<'b> columnar::Ref<'b, D>: Ord + Copy,
70 T: Columnar + Columnation,
71 for<'b> columnar::Ref<'b, T>: Ord + Copy,
72 R: Columnar + Columnation + Semigroup + for<'b> Semigroup<columnar::Ref<'b, R>>,
73 for<'b> columnar::Ref<'b, R>: Ord,
74{
75 fn push_into(&mut self, container: &'a mut Column<(D, T, R)>) {
76 let borrowed = container.borrow();
79 let mut permutation = Vec::with_capacity(borrowed.len());
80 Extend::extend(&mut permutation, borrowed.into_index_iter());
81 permutation.sort();
82
83 self.target.clear();
84 let mut iter = permutation.drain(..);
86 if let Some((data, time, diff)) = iter.next() {
87 let mut owned_data = D::into_owned(data);
88 let mut owned_time = T::into_owned(time);
89
90 let mut prev_data = data;
91 let mut prev_time = time;
92 let mut prev_diff = <R as Columnar>::into_owned(diff);
93
94 for (data, time, diff) in iter {
95 if (&prev_data, &prev_time) == (&data, &time) {
96 prev_diff.plus_equals(&diff);
97 } else {
98 if !prev_diff.is_zero() {
99 D::copy_from(&mut owned_data, prev_data);
100 T::copy_from(&mut owned_time, prev_time);
101 let tuple = (owned_data, owned_time, prev_diff);
102 self.target.push_into(&tuple);
103 (owned_data, owned_time, prev_diff) = tuple;
104 }
105 prev_data = data;
106 prev_time = time;
107 R::copy_from(&mut prev_diff, diff);
108 }
109 }
110
111 if !prev_diff.is_zero() {
112 D::copy_from(&mut owned_data, prev_data);
113 T::copy_from(&mut owned_time, prev_time);
114 let tuple = (owned_data, owned_time, prev_diff);
115 self.target.push_into(&tuple);
116 }
117 }
118
119 if !self.target.is_empty() {
120 self.ready.push_back(std::mem::take(&mut self.target));
121 }
122 }
123}
124
125pub struct ColumnChunker<U: Columnar> {
133 target: ColumnBody<U>,
135 ready: VecDeque<ColumnBody<U>>,
137}
138
139impl<U: Columnar> Default for ColumnChunker<U> {
144 fn default() -> Self {
145 Self {
146 target: ColumnBody::default(),
147 ready: VecDeque::new(),
148 }
149 }
150}
151
152impl<U: Columnar> ContainerBuilder for ColumnChunker<U>
153where
154 U::Container: Clone + 'static,
155{
156 type Container = ColumnBody<U>;
157
158 fn extract(&mut self) -> Option<&mut Self::Container> {
159 if let Some(ready) = self.ready.pop_front() {
160 self.target = ready;
161 Some(&mut self.target)
162 } else {
163 None
164 }
165 }
166
167 fn finish(&mut self) -> Option<&mut Self::Container> {
168 self.extract()
169 }
170}
171
172impl<'a, D, T, R> PushInto<&'a mut Column<(D, T, R)>> for ColumnChunker<(D, T, R)>
173where
174 D: Columnar,
175 for<'b> columnar::Ref<'b, D>: Copy + Ord,
176 T: Columnar,
177 for<'b> columnar::Ref<'b, T>: Copy + Ord,
178 R: Columnar + Default + Semigroup + for<'b> Semigroup<columnar::Ref<'b, R>>,
179 for<'b> columnar::Ref<'b, R>: Ord,
180 for<'b> <D as Columnar>::Container: columnar::Push<columnar::Ref<'b, D>>,
181 for<'b> <T as Columnar>::Container: columnar::Push<columnar::Ref<'b, T>>,
182 for<'b> <R as Columnar>::Container: columnar::Push<&'b R>,
183{
184 fn push_into(&mut self, container: &'a mut Column<(D, T, R)>) {
185 self.target.clear();
188
189 let borrowed = container.borrow();
191 let mut permutation = Vec::with_capacity(borrowed.len());
192 Extend::extend(&mut permutation, borrowed.into_index_iter());
193 permutation.sort();
194
195 {
201 let (target_d, target_t, target_r) = self.target.typed_mut();
202
203 let mut iter = permutation.drain(..);
204 if let Some((data, time, diff)) = iter.next() {
205 let mut prev_data = data;
206 let mut prev_time = time;
207 let mut prev_diff = <R as Columnar>::into_owned(diff);
208
209 for (data, time, diff) in iter {
210 if (&prev_data, &prev_time) == (&data, &time) {
211 prev_diff.plus_equals(&diff);
212 } else {
213 if !prev_diff.is_zero() {
214 target_d.push(prev_data);
215 target_t.push(prev_time);
216 target_r.push(&prev_diff);
217 }
218 prev_data = data;
219 prev_time = time;
220 R::copy_from(&mut prev_diff, diff);
221 }
222 }
223
224 if !prev_diff.is_zero() {
225 target_d.push(prev_data);
226 target_t.push(prev_time);
227 target_r.push(&prev_diff);
228 }
229 }
230 }
231
232 if !self.target.is_empty() {
233 self.ready.push_back(std::mem::take(&mut self.target));
234 }
235 }
236}
237
238pub(crate) fn gallop(upper: usize, lower: &mut usize, mut cmp: impl FnMut(usize) -> bool) {
253 if *lower < upper && cmp(*lower) {
254 let mut step = 1;
255 while *lower + step < upper && cmp(*lower + step) {
256 *lower += step;
257 step <<= 1;
258 }
259
260 step >>= 1;
261 while step > 0 {
262 if *lower + step < upper && cmp(*lower + step) {
263 *lower += step;
264 }
265 step >>= 1;
266 }
267
268 *lower += 1;
271 }
272}
273
274pub struct ColumnMerger<D, T, R> {
279 _marker: PhantomData<(D, T, R)>,
280}
281
282impl<D, T, R> Default for ColumnMerger<D, T, R> {
283 fn default() -> Self {
284 Self {
285 _marker: PhantomData,
286 }
287 }
288}
289
290pub trait MergeChunk<C: Columnar>: Default {
294 fn typed(&mut self) -> &mut C::Container;
296 fn view(&self) -> BorrowedOf<'_, C>;
298 fn is_empty(&self) -> bool;
300}
301
302impl<C: Columnar> MergeChunk<C> for ColumnBody<C> {
303 fn typed(&mut self) -> &mut C::Container {
304 self.typed_mut()
305 }
306 fn view(&self) -> BorrowedOf<'_, C> {
307 self.borrow()
308 }
309 fn is_empty(&self) -> bool {
310 self.is_empty()
311 }
312}
313
314impl<C: Columnar> MergeChunk<C> for Column<C> {
315 fn typed(&mut self) -> &mut C::Container {
316 if !matches!(self, Column::Typed(_)) {
319 let view = self.borrow();
320 let mut fresh = C::Container::default();
321 fresh.extend_from_self(view, 0..view.len());
322 *self = Column::Typed(fresh);
323 }
324 let Column::Typed(typed) = self else {
325 unreachable!("a serialized chunk was materialized above");
326 };
327 typed
328 }
329 fn view(&self) -> BorrowedOf<'_, C> {
330 self.borrow()
331 }
332 fn is_empty(&self) -> bool {
333 self.is_empty()
334 }
335}
336
337impl<D, T, R> ColumnBody<(D, T, R)>
345where
346 D: Columnar,
347 for<'a> columnar::Ref<'a, D>: Copy + Ord,
348 T: Columnar + Default + Clone + PartialOrder,
349 for<'a> columnar::Ref<'a, T>: Copy + Ord,
350 R: Columnar + Default + Semigroup + for<'a> Semigroup<columnar::Ref<'a, R>>,
351{
352 #[must_use]
355 pub fn merge_from(&mut self, others: &mut [Self], positions: &mut [usize]) -> bool {
356 merge_from(self, others, positions)
357 }
358
359 pub fn extract(
362 &mut self,
363 position: &mut usize,
364 upper: AntichainRef<T>,
365 frontier: &mut Antichain<T>,
366 keep: &mut Self,
367 ship: &mut Self,
368 ) {
369 extract(self, position, upper, frontier, keep, ship)
370 }
371}
372
373impl<D, T, R> Column<(D, T, R)>
375where
376 D: Columnar,
377 for<'a> columnar::Ref<'a, D>: Copy + Ord,
378 T: Columnar + Default + Clone + PartialOrder,
379 for<'a> columnar::Ref<'a, T>: Copy + Ord,
380 R: Columnar + Default + Semigroup + for<'a> Semigroup<columnar::Ref<'a, R>>,
381{
382 #[must_use]
385 pub fn merge_from(&mut self, others: &mut [Self], positions: &mut [usize]) -> bool {
386 merge_from(self, others, positions)
387 }
388
389 pub fn extract(
392 &mut self,
393 position: &mut usize,
394 upper: AntichainRef<T>,
395 frontier: &mut Antichain<T>,
396 keep: &mut Self,
397 ship: &mut Self,
398 ) {
399 extract(self, position, upper, frontier, keep, ship)
400 }
401}
402
403#[must_use]
421pub fn merge_from<D, T, R, Ch>(target: &mut Ch, others: &mut [Ch], positions: &mut [usize]) -> bool
422where
423 D: Columnar,
424 for<'a> columnar::Ref<'a, D>: Copy + Ord,
425 T: Columnar + Default + Clone + PartialOrder,
426 for<'a> columnar::Ref<'a, T>: Copy + Ord,
427 R: Columnar + Default + Semigroup + for<'a> Semigroup<columnar::Ref<'a, R>>,
428 Ch: MergeChunk<(D, T, R)>,
429{
430 match others.len() {
431 0 => false,
432 1 => {
433 let other = &mut others[0];
434 let pos = &mut positions[0];
435 if target.is_empty() && *pos == 0 {
436 std::mem::swap(target, other);
437 return false;
438 }
439 let self_c = target.typed();
440 let src_c = other.view();
441 self_c.extend_from_self(src_c, *pos..other.view().len());
442 *pos = other.view().len();
443 false
444 }
445 2 => {
446 let (left, right) = others.split_at(1);
447 let (left_pos, right_pos) = positions.split_at_mut(1);
448 let left_borrow = left[0].view();
449 let right_borrow = right[0].view();
450
451 let self_c = target.typed();
452
453 let l_d = left_borrow.0;
463 let l_t = left_borrow.1;
464 let l_r = left_borrow.2;
465 let r_d = right_borrow.0;
466 let r_t = right_borrow.1;
467 let r_r = right_borrow.2;
468 let upper_l = l_d.len();
469 let upper_r = r_d.len();
470
471 let (sd, st, sr) = self_c;
478
479 let input_bytes = 8
488 * (indexed::length_in_words(&left_borrow)
489 + indexed::length_in_words(&right_borrow));
490 if input_bytes <= 2 * crate::columnar::SHIP_WORDS * 8 {
491 let inputs = [left_borrow, right_borrow];
492 sd.reserve_for(inputs.iter().map(|b| b.0));
493 st.reserve_for(inputs.iter().map(|b| b.1));
494 sr.reserve_for(inputs.iter().map(|b| b.2));
495 }
496
497 let mut stash = R::default();
498
499 let average_bytes = input_bytes / (upper_l + upper_r).max(1);
508 let check_records =
509 (crate::columnar::SHIP_WORDS * 8 / 10 / average_bytes.max(1)).clamp(1, 1024);
510 let mut next_check = sd.len() + check_records;
511 let mut yielded = false;
512
513 while left_pos[0] < upper_l && right_pos[0] < upper_r {
514 let d1 = l_d.get(left_pos[0]);
515 let t1 = l_t.get(left_pos[0]);
516 let d2 = r_d.get(right_pos[0]);
517 let t2 = r_t.get(right_pos[0]);
518 match (d1, t1).cmp(&(d2, t2)) {
519 std::cmp::Ordering::Less => {
520 sd.push(d1);
528 st.push(t1);
529 sr.push(l_r.get(left_pos[0]));
530 left_pos[0] += 1;
531 if left_pos[0] < upper_l
536 && (l_d.get(left_pos[0]), l_t.get(left_pos[0])) < (d2, t2)
537 {
538 let start = left_pos[0];
539 gallop(
540 upper_l.min(left_pos[0] + next_check.saturating_sub(sd.len())),
541 &mut left_pos[0],
542 |i| (l_d.get(i), l_t.get(i)) < (d2, t2),
543 );
544 sd.extend_from_self(l_d, start..left_pos[0]);
548 st.extend_from_self(l_t, start..left_pos[0]);
549 sr.extend_from_self(l_r, start..left_pos[0]);
550 }
551 }
552 std::cmp::Ordering::Greater => {
553 sd.push(d2);
555 st.push(t2);
556 sr.push(r_r.get(right_pos[0]));
557 right_pos[0] += 1;
558 if right_pos[0] < upper_r
559 && (r_d.get(right_pos[0]), r_t.get(right_pos[0])) < (d1, t1)
560 {
561 let start = right_pos[0];
562 gallop(
563 upper_r.min(right_pos[0] + next_check.saturating_sub(sd.len())),
564 &mut right_pos[0],
565 |i| (r_d.get(i), r_t.get(i)) < (d1, t1),
566 );
567 sd.extend_from_self(r_d, start..right_pos[0]);
568 st.extend_from_self(r_t, start..right_pos[0]);
569 sr.extend_from_self(r_r, start..right_pos[0]);
570 }
571 }
572 std::cmp::Ordering::Equal => {
573 let r1 = l_r.get(left_pos[0]);
574 let r2 = r_r.get(right_pos[0]);
575 R::copy_from(&mut stash, r1);
576 stash.plus_equals(&r2);
577 if !stash.is_zero() {
578 sd.push(d1);
579 st.push(t1);
580 sr.push(&stash);
581 }
582 left_pos[0] += 1;
583 right_pos[0] += 1;
584 }
585 }
586
587 if sd.len() >= next_check {
588 use columnar::Borrow as _;
589 if crate::columnar::at_serialized_capacity(&(
590 sd.borrow(),
591 st.borrow(),
592 sr.borrow(),
593 )) {
594 yielded = true;
595 break;
596 }
597 next_check = sd.len() + check_records;
598 }
599 }
600 yielded
601 }
602 n => unreachable!("merge_from called with {n} inputs; expected 0, 1, or 2"),
605 }
606}
607
608pub fn extract<D, T, R, Ch>(
619 source: &Ch,
620 position: &mut usize,
621 upper: AntichainRef<T>,
622 frontier: &mut Antichain<T>,
623 keep: &mut Ch,
624 ship: &mut Ch,
625) where
626 D: Columnar,
627 for<'a> columnar::Ref<'a, D>: Copy + Ord,
628 T: Columnar + Default + Clone + PartialOrder,
629 for<'a> columnar::Ref<'a, T>: Copy + Ord,
630 R: Columnar + Default + Semigroup + for<'a> Semigroup<columnar::Ref<'a, R>>,
631 Ch: MergeChunk<(D, T, R)>,
632{
633 let keep_c = keep.typed();
634 let ship_c = ship.typed();
635
636 let self_view = source.view();
637 let len = self_view.len();
638
639 use columnar::Borrow as _;
640 let mut owned_t = T::default();
641 while *position < len
642 && !crate::columnar::at_serialized_capacity(&keep_c.borrow())
643 && !crate::columnar::at_serialized_capacity(&ship_c.borrow())
644 {
645 let (_, time, _) = self_view.get(*position);
646 T::copy_from(&mut owned_t, time);
647 if upper.less_equal(&owned_t) {
648 frontier.insert_with(&owned_t, |t| t.clone());
651 keep_c.extend_from_self(self_view, *position..*position + 1);
652 } else {
653 ship_c.extend_from_self(self_view, *position..*position + 1);
654 }
655 *position += 1;
656 }
657}
658
659impl<D, T, R> Merger for ColumnMerger<D, T, R>
668where
669 D: Columnar,
670 for<'a> columnar::Ref<'a, D>: Copy + Ord,
671 T: Columnar + Default + Clone + Ord + PartialOrder,
672 for<'a> columnar::Ref<'a, T>: Copy + Ord,
673 R: Columnar + Default + Semigroup + for<'a> Semigroup<columnar::Ref<'a, R>>,
674{
675 type Chunk = ColumnBody<(D, T, R)>;
676
677 fn merge(
678 &mut self,
679 list1: Vec<Self::Chunk>,
680 list2: Vec<Self::Chunk>,
681 output: &mut Vec<Self::Chunk>,
682 stash: &mut Vec<Self::Chunk>,
683 ) {
684 let mut list1 = list1.into_iter();
685 let mut list2 = list2.into_iter();
686
687 let mut heads = [
688 list1.next().unwrap_or_default(),
689 list2.next().unwrap_or_default(),
690 ];
691 let mut positions = [0usize, 0usize];
692
693 let mut result = empty_chunk(stash);
694
695 loop {
697 let upper_l = heads[0].borrow().len();
698 let upper_r = heads[1].borrow().len();
699 if positions[0] >= upper_l || positions[1] >= upper_r {
700 break;
701 }
702
703 let lhs_passthrough = positions[0] == 0 && upper_l > 0 && {
711 let lhs = heads[0].borrow();
712 let rhs = heads[1].borrow();
713 let last_l = (lhs.0.get(upper_l - 1), lhs.1.get(upper_l - 1));
714 let cur_r = (rhs.0.get(positions[1]), rhs.1.get(positions[1]));
715 last_l < cur_r
716 };
717 if lhs_passthrough {
718 if !result.is_empty() {
719 output.push(std::mem::take(&mut result));
720 result = empty_chunk(stash);
721 }
722 let head = std::mem::replace(&mut heads[0], list1.next().unwrap_or_default());
723 output.push(head);
724 positions[0] = 0;
725 continue;
726 }
727
728 let rhs_passthrough = positions[1] == 0 && upper_r > 0 && {
729 let lhs = heads[0].borrow();
730 let rhs = heads[1].borrow();
731 let last_r = (rhs.0.get(upper_r - 1), rhs.1.get(upper_r - 1));
732 let cur_l = (lhs.0.get(positions[0]), lhs.1.get(positions[0]));
733 last_r < cur_l
734 };
735 if rhs_passthrough {
736 if !result.is_empty() {
737 output.push(std::mem::take(&mut result));
738 result = empty_chunk(stash);
739 }
740 let head = std::mem::replace(&mut heads[1], list2.next().unwrap_or_default());
741 output.push(head);
742 positions[1] = 0;
743 continue;
744 }
745
746 let yielded = result.merge_from(&mut heads, &mut positions);
750
751 if positions[0] >= heads[0].borrow().len() {
752 let old = std::mem::replace(&mut heads[0], list1.next().unwrap_or_default());
753 recycle_chunk(old, stash);
754 positions[0] = 0;
755 }
756 if positions[1] >= heads[1].borrow().len() {
757 let old = std::mem::replace(&mut heads[1], list2.next().unwrap_or_default());
758 recycle_chunk(old, stash);
759 positions[1] = 0;
760 }
761 if yielded || result.at_capacity() {
762 output.push(std::mem::take(&mut result));
763 result = empty_chunk(stash);
764 }
765 }
766
767 drain_side(
770 &mut heads[0],
771 &mut positions[0],
772 &mut list1,
773 &mut result,
774 output,
775 stash,
776 );
777 drain_side(
778 &mut heads[1],
779 &mut positions[1],
780 &mut list2,
781 &mut result,
782 output,
783 stash,
784 );
785 if !result.is_empty() {
786 output.push(result);
787 }
788 }
789
790 fn len(chunk: &Self::Chunk) -> usize {
791 chunk.len()
792 }
793
794 fn allocation(chunk: &Self::Chunk) -> (usize, usize, usize) {
795 let bytes = chunk.length_in_bytes();
801 (bytes, bytes, 1)
802 }
803}
804
805impl<D, T, R> Extractor for ColumnMerger<D, T, R>
806where
807 D: Columnar,
808 for<'a> columnar::Ref<'a, D>: Copy + Ord,
809 T: Columnar + Default + Clone + Ord + PartialOrder,
810 for<'a> columnar::Ref<'a, T>: Copy + Ord,
811 R: Columnar + Default + Semigroup + for<'a> Semigroup<columnar::Ref<'a, R>>,
812{
813 type Time = T;
814
815 fn extract(
816 &mut self,
817 merged: Vec<Self::Chunk>,
818 upper: AntichainRef<Self::Time>,
819 frontier: &mut Antichain<Self::Time>,
820 ship: &mut Vec<Self::Chunk>,
821 kept: &mut Vec<Self::Chunk>,
822 stash: &mut Vec<Self::Chunk>,
823 ) {
824 let mut keep = empty_chunk(stash);
825 let mut ready = empty_chunk(stash);
826
827 for mut buffer in merged {
828 let mut position = 0;
829 let len = buffer.borrow().len();
830 while position < len {
831 buffer.extract(&mut position, upper, frontier, &mut keep, &mut ready);
832 if keep.at_capacity() {
833 kept.push(std::mem::take(&mut keep));
834 keep = empty_chunk(stash);
835 }
836 if ready.at_capacity() {
837 ship.push(std::mem::take(&mut ready));
838 ready = empty_chunk(stash);
839 }
840 }
841 recycle_chunk(buffer, stash);
842 }
843 if !keep.is_empty() {
844 kept.push(keep);
845 }
846 if !ready.is_empty() {
847 ship.push(ready);
848 }
849 }
850}
851
852#[inline]
855pub(crate) fn empty_chunk<C: Columnar>(stash: &mut Vec<ColumnBody<C>>) -> ColumnBody<C> {
856 stash.pop().unwrap_or_default()
857}
858
859#[inline]
867pub(crate) fn recycle_chunk<C: Columnar>(mut chunk: ColumnBody<C>, stash: &mut Vec<ColumnBody<C>>) {
868 if let ColumnBody::Typed(c) = &mut chunk {
869 c.clear();
870 stash.push(chunk);
871 }
872}
873
874fn drain_side<D, T, R>(
880 head: &mut ColumnBody<(D, T, R)>,
881 pos: &mut usize,
882 list: &mut std::vec::IntoIter<ColumnBody<(D, T, R)>>,
883 result: &mut ColumnBody<(D, T, R)>,
884 output: &mut Vec<ColumnBody<(D, T, R)>>,
885 stash: &mut Vec<ColumnBody<(D, T, R)>>,
886) where
887 D: Columnar,
888 for<'a> columnar::Ref<'a, D>: Copy + Ord,
889 T: Columnar + Default + Clone + PartialOrder,
890 for<'a> columnar::Ref<'a, T>: Copy + Ord,
891 R: Columnar + Default + Semigroup + for<'a> Semigroup<columnar::Ref<'a, R>>,
892{
893 if *pos < head.borrow().len() {
894 let _ = result.merge_from(std::slice::from_mut(head), std::slice::from_mut(pos));
897 }
898 if !result.is_empty() {
899 output.push(std::mem::take(result));
900 *result = empty_chunk(stash);
901 }
902 Extend::extend(output, list);
903}
904
905#[cfg(test)]
906mod tests {
907 use timely::dataflow::channels::ContainerBytes;
908
909 use super::*;
910
911 fn serialize<C: Columnar>(column: &Column<C>) -> Column<C> {
916 let mut bytes: Vec<u8> = Vec::new();
917 column.into_bytes(&mut bytes);
918 assert_eq!(bytes.len() % 8, 0);
919 let words = bytes
920 .chunks_exact(8)
921 .map(|w| u64::from_ne_bytes(w.try_into().expect("chunk is 8 bytes")))
922 .collect();
923 Column::Align(words)
924 }
925
926 fn run_chunker<D, T, R>(inputs: &[(D, T, R)]) -> Vec<(D, T, R)>
929 where
930 D: Columnar + Clone,
931 for<'a> columnar::Ref<'a, D>: Copy + Ord,
932 T: Columnar + Clone,
933 for<'a> columnar::Ref<'a, T>: Copy + Ord,
934 R: Columnar + Clone + Default + Semigroup + for<'a> Semigroup<columnar::Ref<'a, R>>,
935 for<'a> columnar::Ref<'a, R>: Ord,
936 <(D, T, R) as Columnar>::Container: Clone,
937 for<'a> <(D, T, R) as Columnar>::Container: columnar::Push<&'a (D, T, R)>,
938 <(D, T, R) as Columnar>::Container: columnar::Push<(D, T, R)>,
939 for<'a> <D as Columnar>::Container: columnar::Push<columnar::Ref<'a, D>>,
940 for<'a> <T as Columnar>::Container: columnar::Push<columnar::Ref<'a, T>>,
941 for<'a> <R as Columnar>::Container: columnar::Push<&'a R>,
942 {
943 let mut input: Column<(D, T, R)> = Default::default();
944 for tuple in inputs.iter().cloned() {
945 input.push_into(tuple);
946 }
947
948 let mut chunker: ColumnChunker<(D, T, R)> = Default::default();
949 chunker.push_into(&mut input);
950
951 let mut out = Vec::new();
952 while let Some(chunk) = chunker.extract() {
953 for (d, t, r) in chunk.borrow().into_index_iter() {
954 out.push((D::into_owned(d), T::into_owned(t), R::into_owned(r)));
955 }
956 }
957 out
958 }
959
960 #[mz_ore::test]
961 fn empty_input_yields_no_chunk() {
962 let mut chunker: ColumnChunker<(u64, u64, i64)> = Default::default();
963 let mut input: Column<(u64, u64, i64)> = Default::default();
964 chunker.push_into(&mut input);
965 assert!(chunker.extract().is_none());
966 assert!(chunker.finish().is_none());
967 }
968
969 #[mz_ore::test]
970 fn unsorted_input_is_sorted() {
971 let out = run_chunker(&[(3u64, 0u64, 1i64), (1u64, 0u64, 1i64), (2u64, 0u64, 1i64)]);
972 assert_eq!(out, vec![(1, 0, 1), (2, 0, 1), (3, 0, 1)]);
973 }
974
975 #[mz_ore::test]
976 fn serialized_input_is_consolidated() {
977 let mut input: Column<(u64, u64, i64)> = Default::default();
980 for tuple in [
981 (2u64, 0u64, 1i64),
982 (1, 0, 1),
983 (2, 0, 2),
984 (3, 0, 1),
985 (3, 0, -1),
986 ] {
987 input.push_into(tuple);
988 }
989 let mut input = serialize(&input);
990
991 let mut chunker: ColumnChunker<(u64, u64, i64)> = Default::default();
992 chunker.push_into(&mut input);
993
994 let mut out = Vec::new();
995 while let Some(chunk) = chunker.extract() {
996 for (d, t, r) in chunk.borrow().into_index_iter() {
997 out.push((*d, *t, *r));
998 }
999 }
1000 assert_eq!(out, vec![(1, 0, 1), (2, 0, 3)]);
1001 }
1002
1003 #[mz_ore::test]
1004 fn duplicate_keys_consolidate() {
1005 let out = run_chunker(&[(1u64, 0u64, 1i64), (1u64, 0u64, 2i64), (1u64, 0u64, -1i64)]);
1006 assert_eq!(out, vec![(1, 0, 2)]);
1007 }
1008
1009 #[mz_ore::test]
1010 fn diffs_summing_to_zero_are_dropped() {
1011 let out = run_chunker(&[(1u64, 0u64, 1i64), (1u64, 0u64, -1i64)]);
1012 assert!(out.is_empty());
1013 }
1014
1015 #[mz_ore::test]
1016 fn mixed_consolidation() {
1017 let out = run_chunker(&[
1021 (1u64, 0u64, 1i64),
1022 (2u64, 0u64, 1i64),
1023 (1u64, 0u64, 2i64),
1024 (1u64, 1u64, 5i64),
1025 (1u64, 0u64, -3i64),
1026 ]);
1027 assert_eq!(out, vec![(1, 1, 5), (2, 0, 1)]);
1028 }
1029
1030 #[mz_ore::test]
1031 fn key_val_tuple_data() {
1032 let out = run_chunker(&[
1034 ((1u64, 10u64), 0u64, 1i64),
1035 ((1u64, 10u64), 0u64, 1i64),
1036 ((1u64, 11u64), 0u64, 1i64),
1037 ((2u64, 10u64), 0u64, 1i64),
1038 ]);
1039 assert_eq!(
1040 out,
1041 vec![((1, 10), 0, 2), ((1, 11), 0, 1), ((2, 10), 0, 1),]
1042 );
1043 }
1044
1045 #[mz_ore::test]
1046 fn buffer_reuse_across_calls() {
1047 let mut input1: Column<(u64, u64, i64)> = Default::default();
1050 input1.push_into((1u64, 0u64, 1i64));
1051 input1.push_into((2u64, 0u64, 1i64));
1052
1053 let mut input2: Column<(u64, u64, i64)> = Default::default();
1054 input2.push_into((3u64, 0u64, 1i64));
1055 input2.push_into((1u64, 0u64, 1i64));
1056
1057 let mut chunker: ColumnChunker<(u64, u64, i64)> = Default::default();
1058 chunker.push_into(&mut input1);
1059
1060 {
1063 let _ = chunker.extract().expect("first chunk");
1064 }
1065
1066 chunker.push_into(&mut input2);
1067
1068 let chunk = chunker.extract().expect("second chunk");
1069 let collected: Vec<_> = chunk
1070 .borrow()
1071 .into_index_iter()
1072 .map(|(d, t, r)| (u64::into_owned(d), u64::into_owned(t), i64::into_owned(r)))
1073 .collect();
1074 assert_eq!(collected, vec![(1, 0, 1), (3, 0, 1)]);
1075 }
1076
1077 fn col(rows: &[((u64, u64), u64, i64)]) -> ColumnBody<((u64, u64), u64, i64)> {
1079 let mut c: ColumnBody<((u64, u64), u64, i64)> = Default::default();
1080 for &t in rows {
1081 c.push_into(t);
1082 }
1083 c
1084 }
1085
1086 fn collect_chunks(
1087 chunks: &[ColumnBody<((u64, u64), u64, i64)>],
1088 ) -> Vec<((u64, u64), u64, i64)> {
1089 chunks
1090 .iter()
1091 .flat_map(|c| {
1092 c.borrow().into_index_iter().map(|((k, v), t, r)| {
1093 (
1094 (u64::into_owned(k), u64::into_owned(v)),
1095 u64::into_owned(t),
1096 i64::into_owned(r),
1097 )
1098 })
1099 })
1100 .collect()
1101 }
1102
1103 #[mz_ore::test]
1108 fn merger_disjoint_chains_passthrough() {
1109 let chain1 = vec![
1110 col(&[((0, 0), 0, 1), ((1, 0), 0, 1)]),
1111 col(&[((2, 0), 0, 1), ((3, 0), 0, 1)]),
1112 ];
1113 let chain2 = vec![
1114 col(&[((10, 0), 0, 1), ((11, 0), 0, 1)]),
1115 col(&[((12, 0), 0, 1), ((13, 0), 0, 1)]),
1116 ];
1117
1118 let mut merger: ColumnMerger<(u64, u64), u64, i64> = Default::default();
1119 let mut output = Vec::new();
1120 let mut stash = Vec::new();
1121 Merger::merge(&mut merger, chain1, chain2, &mut output, &mut stash);
1122
1123 let collected = collect_chunks(&output);
1124 let expected: Vec<_> = (0..4u64)
1125 .map(|d| ((d, 0u64), 0u64, 1i64))
1126 .chain((10..14u64).map(|d| ((d, 0u64), 0u64, 1i64)))
1127 .collect();
1128 assert_eq!(collected, expected);
1129 }
1130
1131 #[mz_ore::test]
1136 fn merger_interleaved_chains() {
1137 let chain1 = vec![
1140 col(&[((0, 0), 0, 1), ((2, 0), 0, 1)]),
1141 col(&[((4, 0), 0, 1), ((6, 0), 0, 1)]),
1142 ];
1143 let chain2 = vec![
1144 col(&[((1, 0), 0, 1), ((3, 0), 0, 1)]),
1145 col(&[((5, 0), 0, 1), ((7, 0), 0, 1)]),
1146 ];
1147
1148 let mut merger: ColumnMerger<(u64, u64), u64, i64> = Default::default();
1149 let mut output = Vec::new();
1150 let mut stash = Vec::new();
1151 Merger::merge(&mut merger, chain1, chain2, &mut output, &mut stash);
1152
1153 let collected = collect_chunks(&output);
1154 let expected: Vec<_> = (0..8u64).map(|d| ((d, 0u64), 0u64, 1i64)).collect();
1155 assert_eq!(collected, expected);
1156 }
1157
1158 #[mz_ore::test]
1162 fn merger_passthrough_respects_equal_boundary() {
1163 let chain1 = vec![col(&[((0, 0), 0, 1), ((5, 0), 0, 1)])];
1168 let chain2 = vec![col(&[((5, 0), 0, 1), ((10, 0), 0, 1)])];
1169
1170 let mut merger: ColumnMerger<(u64, u64), u64, i64> = Default::default();
1171 let mut output = Vec::new();
1172 let mut stash = Vec::new();
1173 Merger::merge(&mut merger, chain1, chain2, &mut output, &mut stash);
1174
1175 let collected = collect_chunks(&output);
1176 assert_eq!(
1177 collected,
1178 vec![((0, 0), 0, 1), ((5, 0), 0, 2), ((10, 0), 0, 1)]
1179 );
1180 }
1181}
1182
1183#[cfg(test)]
1184mod proptests {
1185 use super::*;
1195 use mz_ore::cast::CastFrom;
1196 use proptest::prelude::*;
1197 use timely::progress::frontier::Antichain;
1198
1199 type Tuple = ((u64, u64), u64, i64);
1200
1201 fn consolidate(mut v: Vec<Tuple>) -> Vec<Tuple> {
1204 v.sort();
1205 let mut out: Vec<Tuple> = Vec::new();
1206 for (d, t, r) in v {
1207 if let Some(last) = out.last_mut() {
1208 if last.0 == d && last.1 == t {
1209 last.2 += r;
1210 continue;
1211 }
1212 }
1213 out.push((d, t, r));
1214 }
1215 out.retain(|x| x.2 != 0);
1216 out
1217 }
1218
1219 fn arb_consolidated() -> impl Strategy<Value = Vec<Tuple>> {
1222 prop::collection::vec(((0u64..5, 0u64..5), 0u64..3, -3i64..=3i64), 0..30)
1223 .prop_map(consolidate)
1224 }
1225
1226 fn build_column(v: &[Tuple]) -> ColumnBody<Tuple> {
1227 let mut col: ColumnBody<Tuple> = Default::default();
1228 for tup in v {
1229 col.push_into(*tup);
1230 }
1231 col
1232 }
1233
1234 fn in_form(col: ColumnBody<Tuple>, words: bool) -> ColumnBody<Tuple> {
1236 if !words {
1237 return col;
1238 }
1239 let mut bytes = Vec::new();
1240 col.write_into(&mut bytes).expect("vec writes");
1241 ColumnBody::Words(bytemuck::allocation::pod_collect_to_vec(&bytes))
1242 }
1243
1244 fn collect_column(col: &ColumnBody<Tuple>) -> Vec<Tuple> {
1245 col.borrow()
1246 .into_index_iter()
1247 .map(|((k, v), t, r)| {
1248 (
1249 (u64::into_owned(k), u64::into_owned(v)),
1250 u64::into_owned(t),
1251 i64::into_owned(r),
1252 )
1253 })
1254 .collect()
1255 }
1256
1257 fn drive_merge(left: ColumnBody<Tuple>, right: ColumnBody<Tuple>) -> ColumnBody<Tuple> {
1261 let mut self_col: ColumnBody<Tuple> = Default::default();
1262 let mut others = [left, right];
1263 let mut positions = [0usize, 0];
1264 let _ = self_col.merge_from(&mut others, &mut positions);
1265
1266 let [left_done, right_done] = others;
1267 let [left_pos, right_pos] = positions;
1268
1269 if left_pos < left_done.borrow().len() {
1270 let mut tail = [left_done];
1271 let mut p = [left_pos];
1272 let _ = self_col.merge_from(&mut tail, &mut p);
1273 } else if right_pos < right_done.borrow().len() {
1274 let mut tail = [right_done];
1275 let mut p = [right_pos];
1276 let _ = self_col.merge_from(&mut tail, &mut p);
1277 }
1278
1279 self_col
1280 }
1281
1282 proptest! {
1283 #[mz_ore::test]
1286 #[cfg_attr(miri, ignore)]
1287 fn merge_from_equals_consolidated_union(
1288 a in arb_consolidated(),
1289 b in arb_consolidated(),
1290 a_words in any::<bool>(),
1291 b_words in any::<bool>(),
1292 ) {
1293 let merged = drive_merge(
1294 in_form(build_column(&a), a_words),
1295 in_form(build_column(&b), b_words),
1296 );
1297
1298 let mut union = a.clone();
1299 Extend::extend(&mut union, b.iter().copied());
1300 let expected = consolidate(union);
1301
1302 prop_assert_eq!(collect_column(&merged), expected);
1303 }
1304
1305 #[mz_ore::test]
1308 #[cfg_attr(miri, ignore)]
1309 fn merge_from_one_input_drains_tail(
1310 data in arb_consolidated(),
1311 pos_frac in 0u32..=100,
1312 self_words in any::<bool>(),
1313 other_words in any::<bool>(),
1314 ) {
1315 let len = data.len();
1317 let start_pos = if len == 0 { 0 } else {
1318 (usize::cast_from(pos_frac) * len) / 101
1319 };
1320
1321 let mut self_col: ColumnBody<Tuple> = Default::default();
1324 let sentinel: Tuple = ((u64::MAX, u64::MAX), 0, 1);
1325 self_col.push_into(sentinel);
1326 let mut self_col = in_form(self_col, self_words);
1328
1329 let mut others = [in_form(build_column(&data), other_words)];
1330 let mut positions = [start_pos];
1331 let _ = self_col.merge_from(&mut others, &mut positions);
1332
1333 let mut expected = vec![sentinel];
1334 Extend::extend(&mut expected, data[start_pos..].iter().copied());
1335
1336 prop_assert_eq!(collect_column(&self_col), expected);
1337 prop_assert_eq!(positions[0], len);
1338 }
1339
1340 #[mz_ore::test]
1343 #[cfg_attr(miri, ignore)]
1344 fn merge_from_empty_self_swap(data in arb_consolidated(), words in any::<bool>()) {
1345 let mut self_col: ColumnBody<Tuple> = Default::default();
1346 let mut others = [in_form(build_column(&data), words)];
1347 let mut positions = [0usize];
1348 let _ = self_col.merge_from(&mut others, &mut positions);
1349
1350 prop_assert_eq!(collect_column(&self_col), data);
1351 }
1352
1353 #[mz_ore::test]
1359 #[cfg_attr(miri, ignore)]
1360 fn extract_partitions_by_frontier(
1361 data in arb_consolidated(),
1362 upper_time in 0u64..=4,
1363 words in any::<bool>(),
1364 ) {
1365 let mut self_col = in_form(build_column(&data), words);
1366 let upper = Antichain::from_elem(upper_time);
1367 let mut frontier: Antichain<u64> = Antichain::new();
1368 let mut keep: ColumnBody<Tuple> = Default::default();
1369 let mut ship: ColumnBody<Tuple> = Default::default();
1370 let mut position = 0;
1371
1372 self_col.extract(
1373 &mut position,
1374 upper.borrow(),
1375 &mut frontier,
1376 &mut keep,
1377 &mut ship,
1378 );
1379
1380 prop_assert_eq!(position, data.len());
1382
1383 let kept = collect_column(&keep);
1384 let shipped = collect_column(&ship);
1385
1386 for (_, t, _) in &kept {
1388 prop_assert!(
1389 upper.borrow().less_equal(t),
1390 "kept time {} should satisfy upper.less_equal", t,
1391 );
1392 }
1393 for (_, t, _) in &shipped {
1394 prop_assert!(
1395 !upper.borrow().less_equal(t),
1396 "shipped time {} should NOT satisfy upper.less_equal", t,
1397 );
1398 }
1399
1400 let mut union = kept.clone();
1402 Extend::extend(&mut union, shipped.iter().copied());
1403 union.sort();
1404 let mut expected_sorted = data.clone();
1405 expected_sorted.sort();
1406 prop_assert_eq!(union, expected_sorted);
1407
1408 for (_, t, _) in &kept {
1410 prop_assert!(
1411 frontier.less_equal(t),
1412 "frontier should dominate kept time {}", t,
1413 );
1414 }
1415 }
1416
1417 #[mz_ore::test]
1419 #[cfg_attr(miri, ignore)]
1420 fn extract_empty_input(upper_time in 0u64..=4) {
1421 let mut self_col: ColumnBody<Tuple> = Default::default();
1422 let upper = Antichain::from_elem(upper_time);
1423 let mut frontier: Antichain<u64> = Antichain::new();
1424 let mut keep: ColumnBody<Tuple> = Default::default();
1425 let mut ship: ColumnBody<Tuple> = Default::default();
1426 let mut position = 0;
1427
1428 self_col.extract(
1429 &mut position,
1430 upper.borrow(),
1431 &mut frontier,
1432 &mut keep,
1433 &mut ship,
1434 );
1435
1436 prop_assert_eq!(position, 0);
1437 prop_assert!(collect_column(&keep).is_empty());
1438 prop_assert!(collect_column(&ship).is_empty());
1439 prop_assert!(frontier.elements().is_empty());
1440 }
1441 }
1442}