1use std::time::{Duration, Instant};
15
16use columnar::{Columnar, Index};
17use differential_dataflow::consolidation::ConsolidatingContainerBuilder;
18use differential_dataflow::lattice::Lattice;
19use differential_dataflow::operators::arrange::arrangement::Arranged;
20use differential_dataflow::trace::cursor::{BatchCursor, BatchKey, BatchVal};
21use differential_dataflow::trace::{Cursor, Navigable, TraceReader};
22use differential_dataflow::{AsCollection, Data, VecCollection};
23use mz_compute_types::dyncfgs::{ENABLE_MZ_JOIN_CORE, LINEAR_JOIN_YIELDING};
24use mz_compute_types::plan::join::JoinClosure;
25use mz_compute_types::plan::join::linear_join::{LinearJoinPlan, LinearStagePlan};
26use mz_compute_types::plan::scalar::LirScalarExpr;
27use mz_dyncfg::ConfigSet;
28use mz_expr::Eval;
29use mz_repr::fixed_length::ExtendDatums;
30use mz_repr::{DatumVec, Diff, Row, RowArena, SharedRow};
31use mz_timely_util::columnar::Column;
32use mz_timely_util::columnar::batcher;
33use mz_timely_util::columnar::builder::ColumnBuilder;
34use mz_timely_util::columnar::consolidate::ConsolidatingColumnBuilder;
35use mz_timely_util::columnar::{
36 Col2ValBatcher, Col2ValColBatcher, Col2ValPagedBatcher, columnar_exchange,
37};
38use mz_timely_util::operator::{CollectionExt, StreamExt};
39use timely::ContainerBuilder;
40use timely::container::{CapacityContainerBuilder, PushInto};
41use timely::dataflow::channels::pact::{ExchangeCore, Pipeline};
42use timely::dataflow::operators::OkErr;
43use timely::dataflow::operators::generic::Operator;
44use timely::dataflow::{Scope, Stream};
45
46use crate::extensions::arrange::{ArrangementBatcher, MzArrangeCore};
47use crate::render::RenderTimestamp;
48use crate::render::columnar::{CollectionEdge, columnar_to_vec, vec_to_columnar};
49use crate::render::context::{ArrangementFlavor, CollectionBundle, Context};
50use crate::render::errors::DataflowErrorSer;
51use crate::render::join::mz_join_core::mz_join_core;
52use crate::typedefs::{RowRowAgent, RowRowEnter};
53use mz_row_spine::{RowRowBuilder, RowRowColPagedBuilder, RowRowSpine};
54
55#[derive(Clone, Copy)]
59enum LinearJoinImpl {
60 Materialize,
61 DifferentialDataflow,
62}
63
64#[derive(Clone, Copy)]
71pub struct LinearJoinSpec {
72 implementation: LinearJoinImpl,
73 yielding: YieldSpec,
74}
75
76impl Default for LinearJoinSpec {
77 fn default() -> Self {
78 Self {
79 implementation: LinearJoinImpl::Materialize,
80 yielding: Default::default(),
81 }
82 }
83}
84
85impl LinearJoinSpec {
86 pub fn from_config(config: &ConfigSet) -> Self {
88 let implementation = if ENABLE_MZ_JOIN_CORE.get(config) {
89 LinearJoinImpl::Materialize
90 } else {
91 LinearJoinImpl::DifferentialDataflow
92 };
93
94 let yielding_raw = LINEAR_JOIN_YIELDING.get(config);
95 let yielding = YieldSpec::try_from_str(&yielding_raw).unwrap_or_else(|| {
96 tracing::error!("invalid LINEAR_JOIN_YIELDING config: {yielding_raw}");
97 YieldSpec::default()
98 });
99
100 Self {
101 implementation,
102 yielding,
103 }
104 }
105
106 fn render<'s, T, Tr1, Tr2, L, I, CB>(
113 &self,
114 arranged1: Arranged<'s, Tr1>,
115 arranged2: Arranged<'s, Tr2>,
116 result: L,
117 ) -> Stream<'s, T, CB::Container>
118 where
119 T: Lattice + timely::progress::Timestamp,
120 CB: ContainerBuilder + PushInto<(I::Item, T, Diff)> + 'static,
121 Tr1: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
122 Tr2: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
123 BatchCursor<Tr1>: Cursor<Time = T, Diff = Diff>,
124 for<'a> BatchCursor<Tr2>: Cursor<Key<'a> = BatchKey<'a, Tr1>, Time = T, Diff = Diff>,
125 L: FnMut(BatchKey<'_, Tr1>, BatchVal<'_, Tr1>, BatchVal<'_, Tr2>) -> I + 'static,
126 I: IntoIterator<Item: Data> + 'static,
127 {
128 use LinearJoinImpl::*;
129
130 match (
131 self.implementation,
132 self.yielding.after_work,
133 self.yielding.after_time,
134 ) {
135 (DifferentialDataflow, _, _) => {
136 encode_updates::<_, _, CB>(arranged1.join_core(arranged2, result), "JoinCoreEncode")
137 }
138 (Materialize, Some(work_limit), Some(time_limit)) => {
139 let yield_fn =
140 move |start: Instant, work| work >= work_limit || start.elapsed() >= time_limit;
141 mz_join_core::<_, _, _, _, _, _, CB>(arranged1, arranged2, result, yield_fn)
142 }
143 (Materialize, Some(work_limit), None) => {
144 let yield_fn = move |_start, work| work >= work_limit;
145 mz_join_core::<_, _, _, _, _, _, CB>(arranged1, arranged2, result, yield_fn)
146 }
147 (Materialize, None, Some(time_limit)) => {
148 let yield_fn = move |start: Instant, _work| start.elapsed() >= time_limit;
149 mz_join_core::<_, _, _, _, _, _, CB>(arranged1, arranged2, result, yield_fn)
150 }
151 (Materialize, None, None) => {
152 let yield_fn = |_start, _work| false;
153 mz_join_core::<_, _, _, _, _, _, CB>(arranged1, arranged2, result, yield_fn)
154 }
155 }
156 }
157}
158
159#[derive(Clone, Copy)]
161struct YieldSpec {
162 after_work: Option<usize>,
164 after_time: Option<Duration>,
166}
167
168impl Default for YieldSpec {
169 fn default() -> Self {
170 Self {
171 after_work: Some(1_000_000),
172 after_time: Some(Duration::from_millis(100)),
173 }
174 }
175}
176
177impl YieldSpec {
178 fn try_from_str(s: &str) -> Option<Self> {
179 let mut after_work = None;
180 let mut after_time = None;
181
182 let options = s.split(',').map(|o| o.trim());
183 for option in options {
184 let mut iter = option.split(':').map(|p| p.trim());
185 match std::array::from_fn(|_| iter.next()) {
186 [Some("work"), Some(amount), None] => {
187 let amount = amount.parse().ok()?;
188 after_work = Some(amount);
189 }
190 [Some("time"), Some(millis), None] => {
191 let millis = millis.parse().ok()?;
192 let duration = Duration::from_millis(millis);
193 after_time = Some(duration);
194 }
195 _ => return None,
196 }
197 }
198
199 Some(Self {
200 after_work,
201 after_time,
202 })
203 }
204}
205
206enum JoinedFlavor<'scope, T: RenderTimestamp> {
208 Edge(CollectionEdge<'scope, T>),
212 Collection(VecCollection<'scope, T, Row, Diff>),
220 Local(Arranged<'scope, RowRowAgent<T, Diff>>),
222 Trace(Arranged<'scope, RowRowEnter<mz_repr::Timestamp, Diff, T>>),
224}
225
226impl<'scope, T> Context<'scope, T>
227where
228 T: Lattice + RenderTimestamp,
229{
230 pub(crate) fn render_join(
231 &self,
232 inputs: Vec<CollectionBundle<'scope, T>>,
233 linear_plan: LinearJoinPlan,
234 ) -> CollectionBundle<'scope, T> {
235 self.scope.clone().region_named("Join(Linear)", |inner| {
236 self.render_join_inner(inputs, linear_plan, inner)
237 })
238 }
239
240 fn render_join_inner(
241 &self,
242 inputs: Vec<CollectionBundle<'scope, T>>,
243 linear_plan: LinearJoinPlan,
244 inner: Scope<'_, T>,
245 ) -> CollectionBundle<'scope, T> {
246 let mut errors = Vec::new();
248
249 let arrangement = linear_plan
254 .stage_plans
255 .get(0)
256 .and_then(|stage| inputs[linear_plan.source_relation].arrangement(&stage.stream_key));
257 let mut joined = match (arrangement, linear_plan.initial_closure) {
259 (Some(ArrangementFlavor::Local(oks, errs)), None) => {
260 errors.push(errs.as_collection(|k, _v| k.clone()).enter_region(inner));
261 JoinedFlavor::Local(oks.enter_region(inner))
262 }
263 (Some(ArrangementFlavor::Trace(_gid, oks, errs)), None) => {
264 errors.push(errs.as_collection(|k, _v| k.clone()).enter_region(inner));
265 JoinedFlavor::Trace(oks.enter_region(inner))
266 }
267 (_, initial_closure) => {
268 let (joined, errs) = match linear_plan.source_key.as_deref() {
271 None => inputs[linear_plan.source_relation]
272 .collection
273 .clone()
274 .expect("The unarranged collection doesn't exist."),
275 Some(key) => {
276 inputs[linear_plan.source_relation].as_specific_collection(Some(key))
277 }
278 };
279 errors.push(errs.enter_region(inner));
280 let joined = joined.enter_region(inner);
281
282 if let Some(closure) = initial_closure {
285 let name = "LinearJoinInitialization";
291 type CB<C> = ConsolidatingContainerBuilder<C>;
292 let (j, errs) = columnar_to_vec(joined)
293 .flat_map_fallible::<CB<_>, CB<_>, _, _, _, _>(name, {
294 let mut datums = DatumVec::new();
296 move |row| {
297 let mut row_builder = SharedRow::get();
298 let temp_storage = RowArena::new();
299 let mut datums_local = datums.borrow_with(&row);
300 closure
302 .apply(&mut datums_local, &temp_storage, &mut row_builder)
303 .map(|row| row.cloned())
304 .map_err(DataflowErrorSer::from)
305 .transpose()
306 }
307 });
308 errors.push(errs);
309 JoinedFlavor::Collection(j)
310 } else {
311 JoinedFlavor::Edge(joined)
312 }
313 }
314 };
315
316 let stage_count = linear_plan.stage_plans.len();
322 let terminal_stage_writes_edge = linear_plan.final_closure.is_none();
323 for (index, stage_plan) in linear_plan.stage_plans.into_iter().enumerate() {
324 let terminal = index + 1 == stage_count && terminal_stage_writes_edge;
325 joined = self.differential_join(
328 joined,
329 inputs[stage_plan.lookup_relation].enter_region(inner),
330 stage_plan,
331 terminal,
332 &mut errors,
333 );
334 }
335
336 let ok_edge = if let Some(closure) = linear_plan.final_closure {
342 let input = match joined {
345 JoinedFlavor::Edge(edge) => columnar_to_vec(edge),
346 JoinedFlavor::Collection(collection) => collection,
347 _ => panic!("Unexpectedly arranged join output"),
348 };
349 let name = "LinearJoinFinalization";
350 type OkCB<T> = ConsolidatingColumnBuilder<Row, T, Diff>;
351 type ErrCB<C> = ConsolidatingContainerBuilder<C>;
352 let (updates, errs) = input.flat_map_fallible::<OkCB<T>, ErrCB<_>, _, _, _, _>(name, {
353 let mut datums = DatumVec::new();
355 move |row| {
356 let mut row_builder = SharedRow::get();
357 let temp_storage = RowArena::new();
358 let mut datums_local = datums.borrow_with(&row);
359 closure
361 .apply(&mut datums_local, &temp_storage, &mut row_builder)
362 .map(|row| row.cloned())
363 .map_err(DataflowErrorSer::from)
364 .transpose()
365 }
366 });
367 errors.push(errs);
368 updates
369 } else {
370 match joined {
378 JoinedFlavor::Edge(edge) => edge,
379 JoinedFlavor::Collection(collection) => vec_to_columnar(collection),
380 _ => panic!("Unexpectedly arranged join output"),
381 }
382 };
383
384 let bundle = CollectionBundle::from_edge(
386 ok_edge,
387 differential_dataflow::collection::concatenate(inner, errors),
388 );
389 bundle.leave_region(self.scope)
390 }
391
392 fn differential_join<'s>(
398 &self,
399 mut joined: JoinedFlavor<'s, T>,
400 lookup_relation: CollectionBundle<'s, T>,
401 LinearStagePlan {
402 stream_key,
403 stream_thinning,
404 lookup_key,
405 closure,
406 lookup_relation: _,
407 }: LinearStagePlan,
408 terminal: bool,
409 errors: &mut Vec<VecCollection<'s, T, DataflowErrorSer, Diff>>,
410 ) -> JoinedFlavor<'s, T> {
411 match joined {
416 JoinedFlavor::Edge(edge) => {
417 let (arranged, errs) = arrange_join_input(
418 edge,
419 stream_key,
420 stream_thinning,
421 ArrangementBatcher::from_config(&self.config_set),
422 );
423 errors.push(errs);
424 joined = JoinedFlavor::Local(arranged);
425 }
426 JoinedFlavor::Collection(collection) => {
427 let (arranged, errs) = arrange_join_collection(
428 collection,
429 stream_key,
430 stream_thinning,
431 ArrangementBatcher::from_config(&self.config_set),
432 );
433 errors.push(errs);
434 joined = JoinedFlavor::Local(arranged);
435 }
436 JoinedFlavor::Local(_) | JoinedFlavor::Trace(_) => {}
437 }
438
439 let arrangement = lookup_relation
441 .arrangement(&lookup_key[..])
442 .expect("Arrangement absent despite explicit construction");
443
444 match joined {
445 JoinedFlavor::Edge(_) | JoinedFlavor::Collection(_) => {
446 unreachable!("streamed join input arranged at top of method");
447 }
448 JoinedFlavor::Local(local) => match arrangement {
449 ArrangementFlavor::Local(oks, errs1) => {
450 let (oks, errs2) = self
451 .differential_join_inner::<RowRowAgent<_, _>, RowRowAgent<_, _>>(
452 local, oks, closure, terminal,
453 );
454
455 errors.push(errs1.as_collection(|k, _v| k.clone()));
456 errors.extend(errs2);
457 oks
458 }
459 ArrangementFlavor::Trace(_gid, oks, errs1) => {
460 let (oks, errs2) = self
461 .differential_join_inner::<RowRowAgent<_, _>, RowRowEnter<_, _, _>>(
462 local, oks, closure, terminal,
463 );
464
465 errors.push(errs1.as_collection(|k, _v| k.clone()));
466 errors.extend(errs2);
467 oks
468 }
469 },
470 JoinedFlavor::Trace(trace) => match arrangement {
471 ArrangementFlavor::Local(oks, errs1) => {
472 let (oks, errs2) = self
473 .differential_join_inner::<RowRowEnter<_, _, _>, RowRowAgent<_, _>>(
474 trace, oks, closure, terminal,
475 );
476
477 errors.push(errs1.as_collection(|k, _v| k.clone()));
478 errors.extend(errs2);
479 oks
480 }
481 ArrangementFlavor::Trace(_gid, oks, errs1) => {
482 let (oks, errs2) = self
483 .differential_join_inner::<RowRowEnter<_, _, _>, RowRowEnter<_, _, _>>(
484 trace, oks, closure, terminal,
485 );
486
487 errors.push(errs1.as_collection(|k, _v| k.clone()));
488 errors.extend(errs2);
489 oks
490 }
491 },
492 }
493 }
494
495 fn differential_join_inner<'s, Tr1, Tr2>(
507 &self,
508 prev_keyed: Arranged<'s, Tr1>,
509 next_input: Arranged<'s, Tr2>,
510 closure: JoinClosure,
511 terminal: bool,
512 ) -> (
513 JoinedFlavor<'s, T>,
514 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
515 )
516 where
517 Tr1: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
518 Tr2: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
519 for<'a> BatchCursor<Tr1>:
520 Cursor<Key<'a>: ExtendDatums, Val<'a>: ExtendDatums, Time = T, Diff = Diff>,
521 for<'a> BatchCursor<Tr2>:
522 Cursor<Key<'a> = BatchKey<'a, Tr1>, Val<'a>: ExtendDatums, Time = T, Diff = Diff>,
523 {
524 let mut datums = DatumVec::new();
526
527 type VecCB<D, T> = CapacityContainerBuilder<Vec<(D, T, Diff)>>;
530
531 if closure.could_error() {
532 let (oks, err) = self
533 .linear_join_spec
534 .render::<T, _, _, _, _, VecCB<Result<Row, DataflowErrorSer>, T>>(
535 prev_keyed,
536 next_input,
537 move |key, old, new| {
538 apply_join_closure(&closure, &mut datums, key, old, new)
539 .map_err(DataflowErrorSer::from)
540 .transpose()
541 },
542 )
543 .ok_err(|(x, t, d)| {
544 match x {
546 Ok(x) => Ok((x, t, d)),
547 Err(x) => Err((x, t, d)),
548 }
549 });
550
551 let oks = oks.as_collection();
552 let oks = if terminal {
553 JoinedFlavor::Edge(vec_to_columnar(oks))
556 } else {
557 JoinedFlavor::Collection(oks)
558 };
559 (oks, Some(err.as_collection()))
560 } else if terminal {
561 let oks = self
562 .linear_join_spec
563 .render::<T, _, _, _, _, ConsolidatingColumnBuilder<Row, T, Diff>>(
564 prev_keyed,
565 next_input,
566 move |key, old, new| {
567 apply_join_closure(&closure, &mut datums, key, old, new)
568 .expect("Closure claimed to never error")
569 },
570 );
571
572 (JoinedFlavor::Edge(oks.as_collection()), None)
573 } else {
574 let oks = self
575 .linear_join_spec
576 .render::<T, _, _, _, _, VecCB<Row, T>>(
577 prev_keyed,
578 next_input,
579 move |key, old, new| {
580 apply_join_closure(&closure, &mut datums, key, old, new)
581 .expect("Closure claimed to never error")
582 },
583 );
584
585 (JoinedFlavor::Collection(oks.as_collection()), None)
586 }
587 }
588}
589
590fn apply_join_closure<K, V1, V2>(
596 closure: &JoinClosure,
597 datums: &mut DatumVec,
598 key: K,
599 old: V1,
600 new: V2,
601) -> Result<Option<Row>, mz_expr::EvalError>
602where
603 K: ExtendDatums,
604 V1: ExtendDatums,
605 V2: ExtendDatums,
606{
607 let mut row_builder = SharedRow::get();
608 let temp_storage = RowArena::new();
609
610 let mut datums_local = datums.borrow();
611 key.extend_datums(&temp_storage, &mut datums_local, None);
612 old.extend_datums(&temp_storage, &mut datums_local, None);
613 new.extend_datums(&temp_storage, &mut datums_local, None);
614
615 closure
616 .apply(&mut datums_local, &temp_storage, &mut row_builder)
617 .map(|row| row.cloned())
618}
619
620fn encode_updates<'s, T, D, CB>(
625 collection: VecCollection<'s, T, D, Diff>,
626 name: &str,
627) -> Stream<'s, T, CB::Container>
628where
629 T: timely::progress::Timestamp,
630 D: Data,
631 CB: ContainerBuilder + PushInto<(D, T, Diff)> + 'static,
632{
633 collection
634 .inner
635 .unary::<CB, _, _, _>(Pipeline, name, |_, _| {
636 move |input, output| {
637 input.for_each(|time, data| {
638 output
639 .session_with_builder(&time)
640 .give_iterator(data.drain(..));
641 });
642 }
643 })
644}
645
646fn key_join_input_vec<'s, T>(
655 stream: Stream<'s, T, Vec<(Row, T, Diff)>>,
656 stream_key: Vec<LirScalarExpr>,
657 stream_thinning: Vec<usize>,
658) -> (
659 Stream<'s, T, Column<((Row, Row), T, Diff)>>,
660 Stream<'s, T, Vec<(DataflowErrorSer, T, Diff)>>,
661)
662where
663 T: RenderTimestamp,
664{
665 stream.unary_fallible::<ColumnBuilder<((Row, Row), T, Diff)>, _, _, _>(
666 Pipeline,
667 "LinearJoinAccumulatorKeyPreparation",
668 |_, _| {
669 Box::new(move |input, ok, errs| {
670 let mut temp_storage = RowArena::new();
671 let mut key_buf = Row::default();
672 let mut val_buf = Row::default();
673 let mut datums = DatumVec::new();
674 input.for_each(|time, data| {
675 let mut ok_session = ok.session_with_builder(&time);
676 let mut err_session = errs.session(&time);
677 for (row, time, diff) in data.iter() {
678 temp_storage.clear();
679 let datums_local = datums.borrow_with(row);
680 let datums = stream_key
681 .iter()
682 .map(|e| e.eval(&datums_local, &temp_storage));
683 match key_buf.packer().try_extend(datums) {
684 Ok(()) => {
685 val_buf
686 .packer()
687 .extend(stream_thinning.iter().map(|e| datums_local[*e]));
688 ok_session.give(((&key_buf, &val_buf), time, diff));
689 }
690 Err(e) => {
691 err_session.give((e.into(), time.clone(), *diff));
692 }
693 }
694 }
695 });
696 })
697 },
698 )
699}
700
701fn arrange_keyed_join_input<'s, T>(
703 keyed: Stream<'s, T, Column<((Row, Row), T, Diff)>>,
704 errs: Stream<'s, T, Vec<(DataflowErrorSer, T, Diff)>>,
705 batcher: ArrangementBatcher,
706) -> (
707 Arranged<'s, RowRowAgent<T, Diff>>,
708 VecCollection<'s, T, DataflowErrorSer, Diff>,
709)
710where
711 T: Lattice + RenderTimestamp,
712{
713 let exchange =
714 ExchangeCore::<ColumnBuilder<_>, _>::new_core(columnar_exchange::<Row, Row, T, Diff>);
715 let arranged = match batcher {
716 ArrangementBatcher::ColumnarPaged => keyed.mz_arrange_core::<
717 _,
718 batcher::ColumnChunker<_>,
719 Col2ValPagedBatcher<_, _, _, _>,
720 RowRowColPagedBuilder<_, _>,
721 RowRowSpine<_, _>,
722 >(exchange, "JoinStage"),
723 ArrangementBatcher::Columnar => keyed.mz_arrange_core::<
724 _,
725 batcher::ColumnChunker<_>,
726 Col2ValColBatcher<_, _, _, _>,
727 RowRowColPagedBuilder<_, _>,
728 RowRowSpine<_, _>,
729 >(exchange, "JoinStage"),
730 ArrangementBatcher::Columnation => keyed.mz_arrange_core::<
731 _,
732 batcher::Chunker<_>,
733 Col2ValBatcher<_, _, _, _>,
734 RowRowBuilder<_, _>,
735 RowRowSpine<_, _>,
736 >(exchange, "JoinStage"),
737 };
738 (arranged, errs.as_collection())
739}
740
741fn arrange_join_input<'s, T>(
747 edge: CollectionEdge<'s, T>,
748 stream_key: Vec<LirScalarExpr>,
749 stream_thinning: Vec<usize>,
750 batcher: ArrangementBatcher,
751) -> (
752 Arranged<'s, RowRowAgent<T, Diff>>,
753 VecCollection<'s, T, DataflowErrorSer, Diff>,
754)
755where
756 T: Lattice + RenderTimestamp,
757{
758 let (keyed, errs) = edge
759 .inner
760 .unary_fallible::<ColumnBuilder<((Row, Row), T, Diff)>, _, _, _>(
761 Pipeline,
762 "LinearJoinKeyPreparation",
763 |_, _| {
764 Box::new(move |input, ok, errs| {
765 let mut temp_storage = RowArena::new();
766 let mut key_buf = Row::default();
767 let mut val_buf = Row::default();
768 let mut datums = DatumVec::new();
769 input.for_each(|time, data| {
770 let mut ok_session = ok.session_with_builder(&time);
771 let mut err_session = errs.session(&time);
772 for (row, time, diff) in data.borrow().into_index_iter() {
773 temp_storage.clear();
774 let datums_local = datums.borrow_with(row);
775 let datums = stream_key
776 .iter()
777 .map(|e| e.eval(&datums_local, &temp_storage));
778 match key_buf.packer().try_extend(datums) {
779 Ok(()) => {
780 val_buf
781 .packer()
782 .extend(stream_thinning.iter().map(|e| datums_local[*e]));
783 ok_session.give(((&key_buf, &val_buf), time, diff));
784 }
785 Err(e) => {
786 err_session.give((
787 e.into(),
788 Columnar::into_owned(time),
789 Columnar::into_owned(diff),
790 ));
791 }
792 }
793 }
794 });
795 })
796 },
797 );
798 arrange_keyed_join_input(keyed, errs, batcher)
799}
800
801fn arrange_join_collection<'s, T>(
806 collection: VecCollection<'s, T, Row, Diff>,
807 stream_key: Vec<LirScalarExpr>,
808 stream_thinning: Vec<usize>,
809 batcher: ArrangementBatcher,
810) -> (
811 Arranged<'s, RowRowAgent<T, Diff>>,
812 VecCollection<'s, T, DataflowErrorSer, Diff>,
813)
814where
815 T: Lattice + RenderTimestamp,
816{
817 let (keyed, errs) = key_join_input_vec(collection.inner, stream_key, stream_thinning);
818 arrange_keyed_join_input(keyed, errs, batcher)
819}
820
821#[cfg(test)]
822mod tests {
823 use differential_dataflow::input::Input;
824 use mz_expr::EvalError;
825 use mz_repr::{Datum, ReprScalarType, Timestamp};
826 use timely::dataflow::operators::Capture;
827 use timely::dataflow::operators::capture::{Event, Extract};
828
829 use super::*;
830 use crate::render::columnar::vec_to_columnar;
831
832 type KeyedUpdate = ((Row, Row), Timestamp, Diff);
833 type ErrUpdate = (DataflowErrorSer, Timestamp, Diff);
834 type Captured<D> = std::sync::mpsc::Receiver<Event<Timestamp, Vec<D>>>;
835
836 fn extract_sorted(captured: Captured<KeyedUpdate>) -> Vec<KeyedUpdate> {
837 let mut updates: Vec<_> = captured
838 .extract()
839 .into_iter()
840 .flat_map(|(_, data)| data)
841 .collect();
842 updates.sort();
843 updates
844 }
845
846 fn extract_err(captured: Captured<ErrUpdate>) -> Vec<(String, Timestamp, Diff)> {
848 let mut updates: Vec<_> = captured
849 .extract()
850 .into_iter()
851 .flat_map(|(_, data)| data)
852 .map(|(e, t, d)| (format!("{e:?}"), t, d))
853 .collect();
854 updates.sort();
855 updates
856 }
857
858 fn test_input() -> Vec<(Row, u64, Diff)> {
862 vec![
863 (
864 Row::pack_slice(&[Datum::Int32(1), Datum::String("a")]),
865 0,
866 Diff::ONE,
867 ),
868 (
869 Row::pack_slice(&[Datum::Int32(2), Datum::String("b")]),
870 1,
871 Diff::ONE,
872 ),
873 (
874 Row::pack_slice(&[Datum::Int32(1), Datum::String("c")]),
875 2,
876 Diff::ONE,
877 ),
878 (
879 Row::pack_slice(&[Datum::Int32(3), Datum::Null]),
880 2,
881 Diff::ONE,
882 ),
883 (
884 Row::pack_slice(&[Datum::Int32(2), Datum::String("b")]),
885 2,
886 -Diff::ONE,
887 ),
888 (
889 Row::pack_slice(&[Datum::Int32(4), Datum::String("d")]),
890 1,
891 -Diff::ONE,
892 ),
893 ]
894 }
895
896 fn run_columnar(
899 input: Vec<(Row, u64, Diff)>,
900 key: Vec<LirScalarExpr>,
901 ) -> (Vec<KeyedUpdate>, Vec<(String, Timestamp, Diff)>) {
902 let (ok, err) = timely::execute_directly(move |worker| {
903 worker.dataflow::<Timestamp, _, _>(|scope| {
904 let (mut handle, collection) = scope.new_collection();
905 let (arranged, errs) = arrange_join_input(
906 vec_to_columnar(collection),
907 key,
908 vec![1],
909 ArrangementBatcher::Columnation,
910 );
911 let keyed = arranged.as_collection(|k, v| (k.to_row(), v.to_row()));
912 let ok = keyed.inner.capture();
913 let err = errs.inner.capture();
914 for (row, time, diff) in input {
915 handle.update_at(row, Timestamp::from(time), diff);
916 }
917 handle.advance_to(Timestamp::from(3_u64));
918 handle.flush();
919 (ok, err)
920 })
921 });
922 (extract_sorted(ok), extract_err(err))
923 }
924
925 #[mz_ore::test]
928 fn arrange_join_input_keys_correctly() {
929 let (ok, err) = run_columnar(test_input(), vec![LirScalarExpr::column(0)]);
930 assert!(!ok.is_empty());
931 assert!(err.is_empty());
932 assert!(ok.iter().any(|(_, _, d)| *d < Diff::ZERO));
935 for ((key, value), _t, _d) in &ok {
938 assert_eq!(key.iter().count(), 1);
939 assert_eq!(value.iter().count(), 1);
940 }
941 }
942
943 #[mz_ore::test]
947 fn arrange_join_input_error_path() {
948 let key = vec![LirScalarExpr::literal(
949 Err(EvalError::DivisionByZero),
950 ReprScalarType::Int32,
951 )];
952 let (ok, err) = run_columnar(test_input(), key);
953 assert!(ok.is_empty());
954 assert!(!err.is_empty());
955 }
956
957 #[mz_ore::test]
964 fn arrange_join_collection_matches_edge() {
965 let key = vec![LirScalarExpr::column(0)];
966 let input = test_input();
967 let (edge_ok, acc_ok) = timely::execute_directly(move |worker| {
968 worker.dataflow::<Timestamp, _, _>(|scope| {
969 let (mut handle, collection) = scope.new_collection();
970 let (edge_arr, _edge_errs) = arrange_join_input(
971 vec_to_columnar(collection.clone()),
972 key.clone(),
973 vec![1],
974 ArrangementBatcher::Columnation,
975 );
976 let (acc_arr, _acc_errs) = arrange_join_collection(
977 collection,
978 key.clone(),
979 vec![1],
980 ArrangementBatcher::Columnation,
981 );
982 let edge_ok = edge_arr
983 .as_collection(|k, v| (k.to_row(), v.to_row()))
984 .inner
985 .capture();
986 let acc_ok = acc_arr
987 .as_collection(|k, v| (k.to_row(), v.to_row()))
988 .inner
989 .capture();
990 for (row, time, diff) in input {
991 handle.update_at(row, Timestamp::from(time), diff);
992 }
993 handle.advance_to(Timestamp::from(3_u64));
994 handle.flush();
995 (edge_ok, acc_ok)
996 })
997 });
998 let edge_ok = extract_sorted(edge_ok);
999 let acc_ok = extract_sorted(acc_ok);
1000 assert!(!edge_ok.is_empty());
1001 assert_eq!(edge_ok, acc_ok);
1002 }
1003}