1use std::hash::{BuildHasher, Hash, Hasher};
19
20use columnation::Columnation;
21use differential_dataflow::batcher::merge::{Extractor, MergeBatcher, MergeLadder};
22use differential_dataflow::batcher::{Batcher, Extract};
23use differential_dataflow::consolidation::ConsolidatingContainerBuilder;
24use differential_dataflow::difference::{Multiply, Semigroup};
25use differential_dataflow::lattice::Lattice;
26use differential_dataflow::logging::Logger;
27use differential_dataflow::{AsCollection, Collection, Hashable, VecCollection};
28use timely::container::{DrainContainer, PushInto};
29use timely::dataflow::channels::pact::{Exchange, ParallelizationContract, Pipeline};
30use timely::dataflow::operators::Capability;
31use timely::dataflow::operators::generic::builder_rc::{
32 OperatorBuilder as OperatorBuilderRc, OperatorBuilder,
33};
34use timely::dataflow::operators::generic::operator::{self, Operator};
35use timely::dataflow::operators::generic::{
36 InputHandleCore, OperatorInfo, OutputBuilder, OutputBuilderSession,
37};
38use timely::dataflow::{Scope, Stream, StreamVec};
39use timely::progress::frontier::AntichainRef;
40use timely::progress::operate::FrontierInterest;
41use timely::progress::{Antichain, Timestamp};
42use timely::{Container, ContainerBuilder, PartialOrder};
43
44use crate::columnation::ColumnationStack;
45
46pub trait StreamExt<'scope, T, C1>
48where
49 T: Timestamp,
50 C1: Container + DrainContainer + Clone + 'static,
51{
52 fn unary_fallible<DCB, ECB, B, P>(
63 self,
64 pact: P,
65 name: &str,
66 constructor: B,
67 ) -> (
68 Stream<'scope, T, DCB::Container>,
69 Stream<'scope, T, ECB::Container>,
70 )
71 where
72 DCB: ContainerBuilder,
73 ECB: ContainerBuilder,
74 B: FnOnce(
75 Capability<T>,
76 OperatorInfo,
77 ) -> Box<
78 dyn FnMut(
79 &mut InputHandleCore<T, C1, P::Puller>,
80 &mut OutputBuilderSession<'_, T, DCB>,
81 &mut OutputBuilderSession<'_, T, ECB>,
82 ) + 'static,
83 >,
84 P: ParallelizationContract<T, C1>;
85
86 fn flat_map_fallible<DCB, ECB, D2, E, I, L>(
91 self,
92 name: &str,
93 logic: L,
94 ) -> (
95 Stream<'scope, T, DCB::Container>,
96 Stream<'scope, T, ECB::Container>,
97 )
98 where
99 DCB: ContainerBuilder + PushInto<D2>,
100 ECB: ContainerBuilder + PushInto<E>,
101 I: IntoIterator<Item = Result<D2, E>>,
102 L: for<'a> FnMut(C1::Item<'a>) -> I + 'static;
103
104 fn partition_by<CB, L>(
112 self,
113 name: &str,
114 predicate: L,
115 ) -> (
116 Stream<'scope, T, CB::Container>,
117 Stream<'scope, T, CB::Container>,
118 )
119 where
120 CB: ContainerBuilder + for<'a> PushInto<C1::Item<'a>>,
121 L: for<'a> FnMut(&C1::Item<'a>) -> bool + 'static;
122
123 fn expire_stream_at(self, name: &str, expiration: T) -> Stream<'scope, T, C1>;
125}
126
127pub trait CollectionExt<'scope, T, D1, R>: Sized
129where
130 T: Timestamp,
131 R: Semigroup,
132{
133 fn empty(scope: Scope<'scope, T>) -> VecCollection<'scope, T, D1, R>;
135
136 fn map_fallible<DCB, ECB, D2, E, L>(
145 self,
146 name: &str,
147 mut logic: L,
148 ) -> (
149 VecCollection<'scope, T, D2, R>,
150 VecCollection<'scope, T, E, R>,
151 )
152 where
153 DCB: ContainerBuilder<Container = Vec<(D2, T, R)>> + PushInto<(D2, T, R)>,
154 ECB: ContainerBuilder<Container = Vec<(E, T, R)>> + PushInto<(E, T, R)>,
155 D2: Clone + 'static,
156 E: Clone + 'static,
157 L: FnMut(D1) -> Result<D2, E> + 'static,
158 {
159 self.flat_map_fallible::<DCB, ECB, _, _, _, _>(name, move |record| Some(logic(record)))
160 }
161
162 fn flat_map_fallible<DCB, ECB, D2, E, I, L>(
167 self,
168 name: &str,
169 logic: L,
170 ) -> (
171 Collection<'scope, T, DCB::Container>,
172 Collection<'scope, T, ECB::Container>,
173 )
174 where
175 DCB: ContainerBuilder + PushInto<(D2, T, R)>,
176 ECB: ContainerBuilder + PushInto<(E, T, R)>,
177 D2: Clone + 'static,
178 E: Clone + 'static,
179 I: IntoIterator<Item = Result<D2, E>>,
180 L: FnMut(D1) -> I + 'static;
181
182 fn expire_collection_at(self, name: &str, expiration: T) -> VecCollection<'scope, T, D1, R>;
184
185 fn explode_one<D2, R2, L>(
190 self,
191 logic: L,
192 ) -> VecCollection<'scope, T, D2, <R2 as Multiply<R>>::Output>
193 where
194 D2: differential_dataflow::Data,
195 R2: Semigroup + Multiply<R>,
196 <R2 as Multiply<R>>::Output: Clone + 'static + Semigroup,
197 L: FnMut(D1) -> (D2, R2) + 'static,
198 T: Lattice;
199
200 fn ensure_monotonic<E, IE>(
205 self,
206 into_err: IE,
207 ) -> (
208 VecCollection<'scope, T, D1, R>,
209 VecCollection<'scope, T, E, R>,
210 )
211 where
212 E: Clone + 'static,
213 IE: Fn(D1, R) -> (E, R) + 'static,
214 R: num_traits::sign::Signed;
215
216 fn consolidate_named_if<Ba>(self, must_consolidate: bool, name: &str) -> Self
219 where
220 D1: differential_dataflow::ExchangeData + Hash + Columnation,
221 R: Semigroup + differential_dataflow::ExchangeData + Columnation,
222 T: Lattice + Columnation,
223 Ba: Batcher<
224 Vec<((D1, ()), T, R)>,
225 Time = T,
226 Output = Vec<ColumnationStack<((D1, ()), T, R)>>,
227 > + 'static,
228 Ba: BatcherNew;
229
230 fn consolidate_named<Ba>(self, name: &str) -> Self
232 where
233 D1: differential_dataflow::ExchangeData + Hash + Columnation,
234 R: Semigroup + differential_dataflow::ExchangeData + Columnation,
235 T: Lattice + Columnation,
236 Ba: Batcher<
237 Vec<((D1, ()), T, R)>,
238 Time = T,
239 Output = Vec<ColumnationStack<((D1, ()), T, R)>>,
240 > + 'static,
241 Ba: BatcherNew;
242}
243
244impl<'scope, T, C1> StreamExt<'scope, T, C1> for Stream<'scope, T, C1>
245where
246 T: Timestamp,
247 C1: Container + DrainContainer + Clone + 'static,
248{
249 fn unary_fallible<DCB, ECB, B, P>(
250 self,
251 pact: P,
252 name: &str,
253 constructor: B,
254 ) -> (
255 Stream<'scope, T, DCB::Container>,
256 Stream<'scope, T, ECB::Container>,
257 )
258 where
259 DCB: ContainerBuilder,
260 ECB: ContainerBuilder,
261 B: FnOnce(
262 Capability<T>,
263 OperatorInfo,
264 ) -> Box<
265 dyn FnMut(
266 &mut InputHandleCore<T, C1, P::Puller>,
267 &mut OutputBuilderSession<'_, T, DCB>,
268 &mut OutputBuilderSession<'_, T, ECB>,
269 ) + 'static,
270 >,
271 P: ParallelizationContract<T, C1>,
272 {
273 let mut builder = OperatorBuilderRc::new(name.into(), self.scope());
274
275 let operator_info = builder.operator_info();
276
277 let mut input = builder.new_input(self.clone(), pact);
278 builder.set_notify_for(0, FrontierInterest::Never);
279 let (ok_output, ok_stream) = builder.new_output();
280 let mut ok_output = OutputBuilder::from(ok_output);
281 let (err_output, err_stream) = builder.new_output();
282 let mut err_output = OutputBuilder::from(err_output);
283
284 builder.build(move |mut capabilities| {
285 let capability = capabilities.pop().unwrap();
287 let mut logic = constructor(capability, operator_info);
288 move |_frontiers| {
289 let mut ok_output_handle = ok_output.activate();
290 let mut err_output_handle = err_output.activate();
291 logic(&mut input, &mut ok_output_handle, &mut err_output_handle);
292 }
293 });
294
295 (ok_stream, err_stream)
296 }
297
298 #[allow(clippy::redundant_closure)]
304 fn flat_map_fallible<DCB, ECB, D2, E, I, L>(
305 self,
306 name: &str,
307 mut logic: L,
308 ) -> (
309 Stream<'scope, T, DCB::Container>,
310 Stream<'scope, T, ECB::Container>,
311 )
312 where
313 DCB: ContainerBuilder + PushInto<D2>,
314 ECB: ContainerBuilder + PushInto<E>,
315 I: IntoIterator<Item = Result<D2, E>>,
316 L: for<'a> FnMut(C1::Item<'a>) -> I + 'static,
317 {
318 self.unary_fallible::<DCB, ECB, _, _>(Pipeline, name, move |_, _| {
319 Box::new(move |input, ok_output, err_output| {
320 input.for_each_stamp(|time, data| {
321 let mut ok_session = ok_output.session_with_builder(&time);
322 let mut err_session = err_output.session_with_builder(&time);
323 for r in data
324 .flat_map(DrainContainer::drain)
325 .flat_map(|d1| logic(d1))
326 {
327 match r {
328 Ok(d2) => ok_session.give(d2),
329 Err(e) => err_session.give(e),
330 }
331 }
332 })
333 })
334 })
335 }
336
337 fn partition_by<CB, L>(
338 self,
339 name: &str,
340 mut predicate: L,
341 ) -> (
342 Stream<'scope, T, CB::Container>,
343 Stream<'scope, T, CB::Container>,
344 )
345 where
346 CB: ContainerBuilder + for<'a> PushInto<C1::Item<'a>>,
347 L: for<'a> FnMut(&C1::Item<'a>) -> bool + 'static,
348 {
349 self.unary_fallible::<CB, CB, _, _>(Pipeline, name, move |_, _| {
350 Box::new(move |input, matching_output, rest_output| {
351 input.for_each_stamp(|time, data| {
352 let mut matching = matching_output.session_with_builder(&time);
353 let mut rest = rest_output.session_with_builder(&time);
354 for item in data.flat_map(DrainContainer::drain) {
355 if predicate(&item) {
356 matching.give(item);
357 } else {
358 rest.give(item);
359 }
360 }
361 })
362 })
363 })
364 }
365
366 fn expire_stream_at(self, name: &str, expiration: T) -> Stream<'scope, T, C1> {
367 let name = format!("expire_stream_at({name})");
368 self.unary_frontier(Pipeline, &name.clone(), move |cap, _| {
369 let cap = Some(cap.delayed(&expiration));
373 let mut warned = false;
374 move |(input, frontier), output| {
375 let _ = ∩
376 let frontier = frontier.frontier();
377 if !frontier.less_than(&expiration) && !warned {
378 tracing::warn!(
386 name = name,
387 frontier = ?frontier,
388 expiration = ?expiration,
389 "frontier not less than expiration"
390 );
391 warned = true;
392 }
393 input.for_each(|time, data| {
394 let mut session = output.session(&time);
395 session.give_container(data);
396 });
397 }
398 })
399 }
400}
401
402impl<'scope, T, D1, R> CollectionExt<'scope, T, D1, R> for VecCollection<'scope, T, D1, R>
403where
404 T: Timestamp + Clone + 'static,
405 D1: Clone + 'static,
406 R: Semigroup + 'static,
407{
408 fn empty(scope: Scope<'scope, T>) -> VecCollection<'scope, T, D1, R> {
409 operator::empty(scope).as_collection()
410 }
411
412 fn flat_map_fallible<DCB, ECB, D2, E, I, L>(
413 self,
414 name: &str,
415 mut logic: L,
416 ) -> (
417 Collection<'scope, T, DCB::Container>,
418 Collection<'scope, T, ECB::Container>,
419 )
420 where
421 DCB: ContainerBuilder + PushInto<(D2, T, R)>,
422 ECB: ContainerBuilder + PushInto<(E, T, R)>,
423 D2: Clone + 'static,
424 E: Clone + 'static,
425 I: IntoIterator<Item = Result<D2, E>>,
426 L: FnMut(D1) -> I + 'static,
427 {
428 let (ok_stream, err_stream) =
429 self.inner
430 .flat_map_fallible::<DCB, ECB, _, _, _, _>(name, move |(d1, t, r)| {
431 logic(d1).into_iter().map(move |res| match res {
432 Ok(d2) => Ok((d2, t.clone(), r.clone())),
433 Err(e) => Err((e, t.clone(), r.clone())),
434 })
435 });
436 (ok_stream.as_collection(), err_stream.as_collection())
437 }
438
439 fn expire_collection_at(self, name: &str, expiration: T) -> VecCollection<'scope, T, D1, R> {
440 self.inner
441 .expire_stream_at(name, expiration)
442 .as_collection()
443 }
444
445 fn explode_one<D2, R2, L>(
446 self,
447 mut logic: L,
448 ) -> VecCollection<'scope, T, D2, <R2 as Multiply<R>>::Output>
449 where
450 D2: differential_dataflow::Data,
451 R2: Semigroup + Multiply<R>,
452 <R2 as Multiply<R>>::Output: Clone + 'static + Semigroup,
453 L: FnMut(D1) -> (D2, R2) + 'static,
454 T: Lattice,
455 {
456 self.inner
457 .clone()
458 .unary::<ConsolidatingContainerBuilder<_>, _, _, _>(
459 Pipeline,
460 "ExplodeOne",
461 move |_, _| {
462 move |input, output| {
463 input.for_each(|time, data| {
464 output
465 .session_with_builder(&time)
466 .give_iterator(data.drain(..).map(|(x, t, d)| {
467 let (x, d2) = logic(x);
468 (x, t, d2.multiply(&d))
469 }));
470 });
471 }
472 },
473 )
474 .as_collection()
475 }
476
477 fn ensure_monotonic<E, IE>(
478 self,
479 into_err: IE,
480 ) -> (
481 VecCollection<'scope, T, D1, R>,
482 VecCollection<'scope, T, E, R>,
483 )
484 where
485 E: Clone + 'static,
486 IE: Fn(D1, R) -> (E, R) + 'static,
487 R: num_traits::sign::Signed,
488 {
489 let (oks, errs) = self
490 .inner
491 .unary_fallible(Pipeline, "EnsureMonotonic", move |_, _| {
492 Box::new(move |input, ok_output, err_output| {
493 input.for_each(|time, data| {
494 let mut ok_session = ok_output.session(&time);
495 let mut err_session = err_output.session(&time);
496 for (x, t, d) in data.drain(..) {
497 if d.is_positive() {
498 ok_session.give((x, t, d))
499 } else {
500 let (e, d2) = into_err(x, d);
501 err_session.give((e, t, d2))
502 }
503 }
504 })
505 })
506 });
507 (oks.as_collection(), errs.as_collection())
508 }
509
510 fn consolidate_named_if<Ba>(self, must_consolidate: bool, name: &str) -> Self
511 where
512 D1: differential_dataflow::ExchangeData + Hash + Columnation,
513 R: Semigroup + differential_dataflow::ExchangeData + Columnation,
514 T: Lattice + Ord + Columnation,
515 Ba: Batcher<
516 Vec<((D1, ()), T, R)>,
517 Time = T,
518 Output = Vec<ColumnationStack<((D1, ()), T, R)>>,
519 > + 'static,
520 Ba: BatcherNew,
521 {
522 if must_consolidate {
523 let random_state = crate::hash::fixed_state();
536 let exchange = Exchange::new(move |update: &((D1, _), T, R)| {
537 let data = &(update.0).0;
538 let mut h = random_state.build_hasher();
539 data.hash(&mut h);
540 h.finish()
541 });
542 consolidate_pact::<Ba, _, _>(
543 self.map(|k| (k, ())).inner,
544 exchange,
545 name,
546 Ba::new_batcher,
547 )
548 .unary(Pipeline, "unpack consolidated", |_, _| {
549 |input, output| {
550 input.for_each(|time, data| {
551 let mut session = output.session(&time);
552 for ((k, ()), t, d) in data.iter().flatten().flat_map(|chunk| chunk.iter())
553 {
554 session.give((k.clone(), t.clone(), d.clone()))
555 }
556 })
557 }
558 })
559 .as_collection()
560 } else {
561 self
562 }
563 }
564
565 fn consolidate_named<Ba>(self, name: &str) -> Self
566 where
567 D1: differential_dataflow::ExchangeData + Hash + Columnation,
568 R: Semigroup + differential_dataflow::ExchangeData + Columnation,
569 T: Lattice + Ord + Columnation,
570 Ba: Batcher<
571 Vec<((D1, ()), T, R)>,
572 Time = T,
573 Output = Vec<ColumnationStack<((D1, ()), T, R)>>,
574 > + 'static,
575 Ba: BatcherNew,
576 {
577 let exchange = Exchange::new(move |update: &((D1, ()), T, R)| (update.0).0.hashed());
578
579 consolidate_pact::<Ba, _, _>(self.map(|k| (k, ())).inner, exchange, name, Ba::new_batcher)
580 .unary(Pipeline, &format!("Unpack {name}"), |_, _| {
581 |input, output| {
582 input.for_each(|time, data| {
583 let mut session = output.session(&time);
584 for ((k, ()), t, d) in data.iter().flatten().flat_map(|chunk| chunk.iter())
585 {
586 session.give((k.clone(), t.clone(), d.clone()))
587 }
588 })
589 }
590 })
591 .as_collection()
592 }
593}
594
595pub fn consolidate_pact<'scope, Ba, C, P>(
602 stream: Stream<'scope, Ba::Time, C>,
603 pact: P,
604 name: &str,
605 batcher: impl FnOnce(Option<Logger>, usize) -> Ba + 'static,
606) -> StreamVec<'scope, Ba::Time, Ba::Output>
607where
608 Ba: Batcher<C> + 'static,
609 Ba::Time: Timestamp,
610 C: Container + Clone + 'static,
611 Ba::Output: Default + Clone + 'static,
612 P: ParallelizationContract<Ba::Time, C>,
613{
614 let logger = stream
615 .scope()
616 .worker()
617 .logger_for("differential/arrange")
618 .map(Into::into);
619 stream.unary_frontier(pact, name, move |_cap, info| {
620 let mut batcher = batcher(logger, info.global_id);
621 let mut capabilities = Antichain::<Capability<Ba::Time>>::new();
623 let mut prev_frontier = Antichain::from_elem(Ba::Time::minimum());
624 let mut batcher_frontier = Antichain::<Ba::Time>::new();
627
628 move |(input, frontier), output| {
629 input.for_each(|cap, data| {
630 for capability in cap.retain_stamp(0).iter() {
633 capabilities.insert(capability.clone());
634 }
635 batcher.insert(data);
636 });
637
638 if prev_frontier.borrow() != frontier.frontier() {
639 if capabilities
640 .elements()
641 .iter()
642 .any(|c| !frontier.less_equal(c.time()))
643 {
644 let mut upper = Antichain::new(); for (index, capability) in capabilities.elements().iter().enumerate() {
648 if !frontier.less_equal(capability.time()) {
649 upper.clear();
653 for time in frontier.frontier().iter() {
654 upper.insert(time.clone());
655 }
656 for other_capability in &capabilities.elements()[(index + 1)..] {
657 upper.insert(other_capability.time().clone());
658 }
659
660 let (chain, retained) = batcher.extract(upper.borrow());
662 batcher_frontier.clear();
663 batcher_frontier.extend(retained.iter().cloned());
664
665 let mut session = output.session(&capabilities.elements()[index]);
667 session.give(chain.unwrap_or_default());
668 }
669 }
670
671 let mut new_capabilities = Antichain::new();
677 for time in batcher_frontier.iter() {
678 if let Some(capability) = capabilities
679 .elements()
680 .iter()
681 .find(|c| c.time().less_equal(time))
682 {
683 new_capabilities.insert(capability.delayed(time));
684 } else {
685 panic!("failed to find capability");
686 }
687 }
688
689 capabilities = new_capabilities;
690 }
691
692 prev_frontier.clear();
693 prev_frontier.extend(frontier.frontier().iter().cloned());
694 }
695 }
696 })
697}
698
699pub struct ConsolidatingBatcher<Chu, M: Extractor> {
709 chunker: Chu,
711 ladder: MergeLadder<M>,
713 extractor: M,
717 stash: Vec<M::Chunk>,
719 frontier: Antichain<M::Time>,
721}
722
723impl<Chu: Default, M: Extractor<Time: Timestamp>> ConsolidatingBatcher<Chu, M> {
724 pub fn new(logger: Option<Logger>, operator_id: usize) -> Self {
726 Self {
727 chunker: Chu::default(),
728 ladder: MergeLadder::new(logger, operator_id),
729 extractor: M::default(),
730 stash: Vec::new(),
731 frontier: Antichain::new(),
732 }
733 }
734}
735
736impl<C, Chu, M> Batcher<C> for ConsolidatingBatcher<Chu, M>
737where
738 M: Extractor<Time: Timestamp>,
739 Chu: ContainerBuilder<Container = M::Chunk> + for<'a> PushInto<&'a mut C>,
740{
741 fn insert(&mut self, container: &mut C) {
742 self.chunker.push_into(container);
743 while let Some(chunk) = self.chunker.extract().map(std::mem::take) {
744 self.ladder.insert_chain(vec![chunk]);
745 }
746 }
747}
748
749impl<Chu, M> Extract for ConsolidatingBatcher<Chu, M>
750where
751 M: Extractor<Time: Timestamp>,
752 Chu: ContainerBuilder<Container = M::Chunk>,
753{
754 type Time = M::Time;
755 type Output = Vec<M::Chunk>;
756
757 fn extract<'a>(
758 &'a mut self,
759 upper: AntichainRef<'_, M::Time>,
760 ) -> (Option<Self::Output>, AntichainRef<'a, M::Time>) {
761 while let Some(chunk) = self.chunker.finish().map(std::mem::take) {
764 self.ladder.insert_chain(vec![chunk]);
765 }
766
767 let merged = self.ladder.merge_all();
768
769 let mut kept = Vec::new();
770 let mut readied = Vec::new();
771 self.frontier.clear();
772 self.extractor.extract(
773 merged,
774 upper,
775 &mut self.frontier,
776 &mut readied,
777 &mut kept,
778 &mut self.stash,
779 );
780
781 self.ladder.insert_chain(kept);
783 self.stash.clear();
784
785 (
786 (!readied.is_empty()).then_some(readied),
787 self.frontier.borrow(),
788 )
789 }
790}
791
792impl<Chu, M: Extractor> ConsolidatingBatcher<Chu, M> {
793 pub fn push_chunk(&mut self, chunk: M::Chunk) {
798 self.ladder.insert_chain(vec![chunk]);
799 }
800}
801
802pub trait BatcherNew {
808 fn new_batcher(logger: Option<Logger>, operator_id: usize) -> Self;
810}
811
812impl<Chu: Default, M: Extractor, S> BatcherNew for MergeBatcher<Chu, M, S> {
813 fn new_batcher(logger: Option<Logger>, operator_id: usize) -> Self {
814 Self::new(logger, operator_id)
815 }
816}
817
818impl<Chu: Default, M: Extractor<Time: Timestamp>> BatcherNew for ConsolidatingBatcher<Chu, M> {
819 fn new_batcher(logger: Option<Logger>, operator_id: usize) -> Self {
820 Self::new(logger, operator_id)
821 }
822}
823
824pub trait ConcatenateFlatten<'scope, T: Timestamp, C: Container + DrainContainer> {
826 fn concatenate_flatten<I, CB>(&self, sources: I) -> Stream<'scope, T, CB::Container>
847 where
848 I: IntoIterator<Item = Stream<'scope, T, C>>,
849 CB: ContainerBuilder + for<'a> PushInto<C::Item<'a>>;
850}
851
852impl<'scope, T, C> ConcatenateFlatten<'scope, T, C> for Stream<'scope, T, C>
853where
854 T: Timestamp,
855 C: Container + DrainContainer + Clone + 'static,
856{
857 fn concatenate_flatten<I, CB>(&self, sources: I) -> Stream<'scope, T, CB::Container>
858 where
859 I: IntoIterator<Item = Stream<'scope, T, C>>,
860 CB: ContainerBuilder + for<'a> PushInto<C::Item<'a>>,
861 {
862 self.scope()
863 .concatenate_flatten::<_, CB>(Some(Clone::clone(self)).into_iter().chain(sources))
864 }
865}
866
867impl<'scope, T, C> ConcatenateFlatten<'scope, T, C> for Scope<'scope, T>
868where
869 T: Timestamp,
870 C: Container + DrainContainer,
871{
872 fn concatenate_flatten<I, CB>(&self, sources: I) -> Stream<'scope, T, CB::Container>
873 where
874 I: IntoIterator<Item = Stream<'scope, T, C>>,
875 CB: ContainerBuilder + for<'a> PushInto<C::Item<'a>>,
876 {
877 let mut builder = OperatorBuilder::new("ConcatenateFlatten".to_string(), self.clone());
878
879 let mut handles = sources
881 .into_iter()
882 .map(|s| builder.new_input(s, Pipeline))
883 .collect::<Vec<_>>();
884 for i in 0..handles.len() {
885 builder.set_notify_for(i, FrontierInterest::Never);
886 }
887
888 let (output, result) = builder.new_output::<CB::Container>();
890 let mut output = OutputBuilder::<_, CB>::from(output);
891
892 builder.build(move |_capability| {
893 move |_frontier| {
894 let mut output = output.activate();
895 for handle in handles.iter_mut() {
896 handle.for_each_stamp(|time, data| {
897 output
898 .session_with_builder(&time)
899 .give_iterator(data.flat_map(DrainContainer::drain));
900 })
901 }
902 }
903 });
904
905 result
906 }
907}
908
909pub trait ClearContainer {
911 fn clear(&mut self);
913}
914
915impl<T> ClearContainer for Vec<T> {
916 fn clear(&mut self) {
917 Vec::clear(self)
918 }
919}
920
921#[cfg(test)]
922mod tests {
923 use timely::container::CapacityContainerBuilder;
924 use timely::dataflow::operators::Capture;
925 use timely::dataflow::operators::capture::Extract;
926 use timely::dataflow::operators::core::to_stream::ToStreamBuilder;
927 use timely::dataflow::operators::vec::ToStream;
928
929 use crate::columnar::Column;
930 use crate::columnar::builder::ColumnBuilder;
931
932 use super::*;
933
934 const UPDATES: [(u64, u64, i64); 4] = [(0, 0, 1), (1, 3, 1), (2, 5, -1), (3, 7, 1)];
937 const SPLIT: u64 = 5;
938 const MATCHING: [(u64, u64, i64); 2] = [(2, 5, -1), (3, 7, 1)];
939 const REST: [(u64, u64, i64); 2] = [(0, 0, 1), (1, 3, 1)];
940
941 #[mz_ore::test]
942 fn partition_by_routes_vec_records_by_their_own_time() {
943 let (matching, rest) = timely::execute_directly(|worker| {
944 worker.dataflow::<u64, _, _>(|scope| {
945 let (matching, rest) = UPDATES
946 .to_vec()
947 .to_stream(scope)
948 .partition_by::<CapacityContainerBuilder<Vec<_>>, _>("Test", |(_, time, _)| {
949 *time >= SPLIT
950 });
951 (matching.capture(), rest.capture())
952 })
953 });
954 let flatten = |captured: Vec<(u64, Vec<(u64, u64, i64)>)>| {
955 captured
956 .into_iter()
957 .flat_map(|(capability, updates)| {
958 assert_eq!(capability, 0, "outputs keep the input capability");
959 updates
960 })
961 .collect::<Vec<_>>()
962 };
963 assert_eq!(flatten(matching.extract()), MATCHING);
964 assert_eq!(flatten(rest.extract()), REST);
965 }
966
967 #[mz_ore::test]
968 fn partition_by_routes_columnar_records_by_their_own_time() {
969 let (matching, rest) = timely::execute_directly(|worker| {
970 worker.dataflow::<u64, _, _>(|scope| {
971 let (matching, rest) = UPDATES
972 .to_vec()
973 .to_stream_with_builder::<_, ColumnBuilder<(u64, u64, i64)>>(scope)
974 .partition_by::<ColumnBuilder<(u64, u64, i64)>, _>("Test", |(_, time, _)| {
975 **time >= SPLIT
976 });
977 (matching.capture(), rest.capture())
978 })
979 });
980 let flatten = |captured: Vec<(u64, Column<(u64, u64, i64)>)>| {
981 captured
982 .into_iter()
983 .flat_map(|(capability, mut column)| {
984 assert_eq!(capability, 0, "outputs keep the input capability");
985 column
986 .drain()
987 .map(|(data, time, diff)| (*data, *time, *diff))
988 .collect::<Vec<_>>()
989 })
990 .collect::<Vec<_>>()
991 };
992 assert_eq!(flatten(matching.extract()), MATCHING);
993 assert_eq!(flatten(rest.extract()), REST);
994 }
995}