1use std::time::{Duration, Instant};
15
16use columnar::{Columnar, Index};
17use differential_dataflow::lattice::Lattice;
18use differential_dataflow::operators::arrange::arrangement::Arranged;
19use differential_dataflow::trace::cursor::{BatchCursor, BatchKey, BatchVal};
20use differential_dataflow::trace::{Cursor, Navigable, TraceReader};
21use differential_dataflow::{AsCollection, Data, VecCollection};
22use mz_compute_types::dyncfgs::{ENABLE_MZ_JOIN_CORE, LINEAR_JOIN_YIELDING};
23use mz_compute_types::plan::join::JoinClosure;
24use mz_compute_types::plan::join::linear_join::{LinearJoinPlan, LinearStagePlan};
25use mz_compute_types::plan::scalar::LirScalarExpr;
26use mz_dyncfg::ConfigSet;
27use mz_expr::Eval;
28use mz_repr::fixed_length::ExtendDatums;
29use mz_repr::{DatumVec, DatumVecBorrow, Diff, Row, RowArena, SharedRow};
30use mz_timely_util::columnar::Column;
31use mz_timely_util::columnar::batcher;
32use mz_timely_util::columnar::builder::ColumnBuilder;
33use mz_timely_util::columnar::chunk::{AccountedChunkBatcher, ChunkChunker, UnchunkBuilder};
34use mz_timely_util::columnar::consolidate::ConsolidatingColumnBuilder;
35use mz_timely_util::columnar::{Col2ValBatcher, Col2ValColBatcher, columnar_exchange};
36use mz_timely_util::operator::StreamExt;
37use timely::ContainerBuilder;
38use timely::container::{CapacityContainerBuilder, PushInto};
39use timely::dataflow::channels::pact::{ExchangeCore, Pipeline};
40use timely::dataflow::operators::generic::Operator;
41use timely::dataflow::{Scope, Stream};
42
43use crate::extensions::arrange::{ArrangementBatcher, MzArrangeCore};
44use crate::render::RenderTimestamp;
45use crate::render::columnar::{ColCollection, flat_map_datums};
46use crate::render::context::{ArrangementFlavor, CollectionBundle, Context};
47use crate::render::errors::DataflowErrorSer;
48use crate::render::join::mz_join_core::mz_join_core;
49use crate::typedefs::{RowRowAgent, RowRowEnter};
50use mz_row_spine::{RowRowBuilder, RowRowColPagedBuilder, RowRowSpine};
51
52#[derive(Clone, Copy)]
56enum LinearJoinImpl {
57 Materialize,
58 DifferentialDataflow,
59}
60
61#[derive(Clone, Copy)]
68pub struct LinearJoinSpec {
69 implementation: LinearJoinImpl,
70 yielding: YieldSpec,
71}
72
73impl Default for LinearJoinSpec {
74 fn default() -> Self {
75 Self {
76 implementation: LinearJoinImpl::Materialize,
77 yielding: Default::default(),
78 }
79 }
80}
81
82impl LinearJoinSpec {
83 pub fn from_config(config: &ConfigSet) -> Self {
85 let implementation = if ENABLE_MZ_JOIN_CORE.get(config) {
86 LinearJoinImpl::Materialize
87 } else {
88 LinearJoinImpl::DifferentialDataflow
89 };
90
91 let yielding_raw = LINEAR_JOIN_YIELDING.get(config);
92 let yielding = YieldSpec::try_from_str(&yielding_raw).unwrap_or_else(|| {
93 tracing::error!("invalid LINEAR_JOIN_YIELDING config: {yielding_raw}");
94 YieldSpec::default()
95 });
96
97 Self {
98 implementation,
99 yielding,
100 }
101 }
102
103 fn render<'s, T, Tr1, Tr2, L, I, CB>(
110 &self,
111 arranged1: Arranged<'s, Tr1>,
112 arranged2: Arranged<'s, Tr2>,
113 result: L,
114 ) -> Stream<'s, T, CB::Container>
115 where
116 T: Lattice + timely::progress::Timestamp,
117 CB: ContainerBuilder + PushInto<(I::Item, T, Diff)> + 'static,
118 Tr1: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
119 Tr2: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
120 BatchCursor<Tr1>: Cursor<Time = T, Diff = Diff>,
121 for<'a> BatchCursor<Tr2>: Cursor<Key<'a> = BatchKey<'a, Tr1>, Time = T, Diff = Diff>,
122 L: FnMut(BatchKey<'_, Tr1>, BatchVal<'_, Tr1>, BatchVal<'_, Tr2>) -> I + 'static,
123 I: IntoIterator<Item: Data> + 'static,
124 {
125 use LinearJoinImpl::*;
126
127 match (
128 self.implementation,
129 self.yielding.after_work,
130 self.yielding.after_time,
131 ) {
132 (DifferentialDataflow, _, _) => {
133 encode_updates::<_, _, CB>(arranged1.join_core(arranged2, result), "JoinCoreEncode")
134 }
135 (Materialize, Some(work_limit), Some(time_limit)) => {
136 let yield_fn =
137 move |start: Instant, work| work >= work_limit || start.elapsed() >= time_limit;
138 mz_join_core::<_, _, _, _, _, _, CB>(arranged1, arranged2, result, yield_fn)
139 }
140 (Materialize, Some(work_limit), None) => {
141 let yield_fn = move |_start, work| work >= work_limit;
142 mz_join_core::<_, _, _, _, _, _, CB>(arranged1, arranged2, result, yield_fn)
143 }
144 (Materialize, None, Some(time_limit)) => {
145 let yield_fn = move |start: Instant, _work| start.elapsed() >= time_limit;
146 mz_join_core::<_, _, _, _, _, _, CB>(arranged1, arranged2, result, yield_fn)
147 }
148 (Materialize, None, None) => {
149 let yield_fn = |_start, _work| false;
150 mz_join_core::<_, _, _, _, _, _, CB>(arranged1, arranged2, result, yield_fn)
151 }
152 }
153 }
154}
155
156#[derive(Clone, Copy)]
158struct YieldSpec {
159 after_work: Option<usize>,
161 after_time: Option<Duration>,
163}
164
165impl Default for YieldSpec {
166 fn default() -> Self {
167 Self {
168 after_work: Some(1_000_000),
169 after_time: Some(Duration::from_millis(100)),
170 }
171 }
172}
173
174impl YieldSpec {
175 fn try_from_str(s: &str) -> Option<Self> {
176 let mut after_work = None;
177 let mut after_time = None;
178
179 let options = s.split(',').map(|o| o.trim());
180 for option in options {
181 let mut iter = option.split(':').map(|p| p.trim());
182 match std::array::from_fn(|_| iter.next()) {
183 [Some("work"), Some(amount), None] => {
184 let amount = amount.parse().ok()?;
185 after_work = Some(amount);
186 }
187 [Some("time"), Some(millis), None] => {
188 let millis = millis.parse().ok()?;
189 let duration = Duration::from_millis(millis);
190 after_time = Some(duration);
191 }
192 _ => return None,
193 }
194 }
195
196 Some(Self {
197 after_work,
198 after_time,
199 })
200 }
201}
202
203fn apply_closure<'a>(
208 closure: &'a JoinClosure,
209 datums: &mut DatumVecBorrow<'a>,
210 temp_storage: &'a RowArena,
211) -> Result<Option<Row>, DataflowErrorSer> {
212 let mut row_builder = SharedRow::get();
213 closure
216 .apply(datums, temp_storage, &mut row_builder)
217 .map(|row| row.cloned())
218 .map_err(DataflowErrorSer::from)
219}
220
221fn apply_closure_to_edge<'s, T>(
227 edge: ColCollection<'s, T>,
228 name: &str,
229 closure: JoinClosure,
230) -> (
231 ColCollection<'s, T>,
232 VecCollection<'s, T, DataflowErrorSer, Diff>,
233)
234where
235 T: RenderTimestamp,
236{
237 let (oks, errs) = flat_map_datums::<_, ConsolidatingColumnBuilder<Row, T, Diff>, _>(
238 edge,
239 name,
240 usize::MAX,
241 {
242 let mut datum_vec = DatumVec::new();
243 move |row_datums, time, diff, ok_session, err_session| {
244 let temp_storage = RowArena::new();
248 let mut datums = datum_vec.borrow();
249 datums.extend(row_datums.iter());
250 match apply_closure(&closure, &mut datums, &temp_storage) {
251 Ok(Some(row)) => {
252 ok_session.give((row, time, diff));
253 1
254 }
255 Ok(None) => 0,
256 Err(e) => {
257 err_session.give((e, time, diff));
258 1
259 }
260 }
261 }
262 },
263 );
264 (oks.as_collection(), errs.as_collection())
265}
266
267enum JoinedFlavor<'scope, T: RenderTimestamp> {
269 Collection(ColCollection<'scope, T>),
273 Local(Arranged<'scope, RowRowAgent<T, Diff>>),
275 Trace(Arranged<'scope, RowRowEnter<mz_repr::Timestamp, Diff, T>>),
277}
278
279impl<'scope, T> Context<'scope, T>
280where
281 T: Lattice + RenderTimestamp,
282{
283 pub(crate) fn render_join(
284 &self,
285 inputs: Vec<CollectionBundle<'scope, T>>,
286 linear_plan: LinearJoinPlan,
287 ) -> CollectionBundle<'scope, T> {
288 self.scope.clone().region_named("Join(Linear)", |inner| {
289 self.render_join_inner(inputs, linear_plan, inner)
290 })
291 }
292
293 fn render_join_inner(
294 &self,
295 inputs: Vec<CollectionBundle<'scope, T>>,
296 linear_plan: LinearJoinPlan,
297 inner: Scope<'_, T>,
298 ) -> CollectionBundle<'scope, T> {
299 let mut errors = Vec::new();
301
302 let arrangement = linear_plan
307 .stage_plans
308 .get(0)
309 .and_then(|stage| inputs[linear_plan.source_relation].arrangement(&stage.stream_key));
310 let mut joined = match (arrangement, linear_plan.initial_closure) {
312 (Some(ArrangementFlavor::Local(oks, errs)), None) => {
313 errors.push(errs.as_collection(|k, _v| k.clone()).enter_region(inner));
314 JoinedFlavor::Local(oks.enter_region(inner))
315 }
316 (Some(ArrangementFlavor::Trace(_gid, oks, errs)), None) => {
317 errors.push(errs.as_collection(|k, _v| k.clone()).enter_region(inner));
318 JoinedFlavor::Trace(oks.enter_region(inner))
319 }
320 (_, initial_closure) => {
321 let (joined, errs) = match linear_plan.source_key.as_deref() {
324 None => inputs[linear_plan.source_relation]
325 .collection
326 .clone()
327 .expect("The unarranged collection doesn't exist."),
328 Some(key) => {
329 inputs[linear_plan.source_relation].as_specific_collection(Some(key))
330 }
331 };
332 errors.push(errs.enter_region(inner));
333 let joined = joined.enter_region(inner);
334
335 if let Some(closure) = initial_closure {
338 let (j, errs) =
343 apply_closure_to_edge(joined, "LinearJoinInitialization", closure);
344 errors.push(errs);
345 JoinedFlavor::Collection(j)
346 } else {
347 JoinedFlavor::Collection(joined)
348 }
349 }
350 };
351
352 let stage_count = linear_plan.stage_plans.len();
358 let terminal_stage_writes_edge = linear_plan.final_closure.is_none();
359 for (index, stage_plan) in linear_plan.stage_plans.into_iter().enumerate() {
360 let terminal = index + 1 == stage_count && terminal_stage_writes_edge;
361 joined = self.differential_join(
364 joined,
365 inputs[stage_plan.lookup_relation].enter_region(inner),
366 stage_plan,
367 terminal,
368 &mut errors,
369 );
370 }
371
372 let ok_edge = match (joined, linear_plan.final_closure) {
378 (JoinedFlavor::Collection(edge), None) => edge,
379 (JoinedFlavor::Collection(edge), Some(closure)) => {
380 let (updates, errs) =
381 apply_closure_to_edge(edge, "LinearJoinFinalization", closure);
382 errors.push(errs);
383 updates
384 }
385 _ => panic!("Unexpectedly arranged join output"),
386 };
387
388 let bundle = CollectionBundle::from_edge(
390 ok_edge,
391 differential_dataflow::collection::concatenate(inner, errors),
392 );
393 bundle.leave_region(self.scope)
394 }
395
396 fn differential_join<'s>(
402 &self,
403 mut joined: JoinedFlavor<'s, T>,
404 lookup_relation: CollectionBundle<'s, T>,
405 LinearStagePlan {
406 stream_key,
407 stream_thinning,
408 lookup_key,
409 closure,
410 lookup_relation: _,
411 }: LinearStagePlan,
412 terminal: bool,
413 errors: &mut Vec<VecCollection<'s, T, DataflowErrorSer, Diff>>,
414 ) -> JoinedFlavor<'s, T> {
415 match joined {
417 JoinedFlavor::Collection(edge) => {
418 let (arranged, errs) = arrange_join_input(
419 edge,
420 stream_key,
421 stream_thinning,
422 ArrangementBatcher::from_config(&self.config_set),
423 );
424 errors.push(errs);
425 joined = JoinedFlavor::Local(arranged);
426 }
427 JoinedFlavor::Local(_) | JoinedFlavor::Trace(_) => {}
428 }
429
430 let arrangement = lookup_relation
432 .arrangement(&lookup_key[..])
433 .expect("Arrangement absent despite explicit construction");
434
435 match joined {
436 JoinedFlavor::Collection(_) => {
437 unreachable!("streamed join input arranged at top of method");
438 }
439 JoinedFlavor::Local(local) => match arrangement {
440 ArrangementFlavor::Local(oks, errs1) => {
441 let (oks, errs2) = self
442 .differential_join_inner::<RowRowAgent<_, _>, RowRowAgent<_, _>>(
443 local, oks, closure, terminal,
444 );
445
446 errors.push(errs1.as_collection(|k, _v| k.clone()));
447 errors.extend(errs2);
448 oks
449 }
450 ArrangementFlavor::Trace(_gid, oks, errs1) => {
451 let (oks, errs2) = self
452 .differential_join_inner::<RowRowAgent<_, _>, RowRowEnter<_, _, _>>(
453 local, oks, closure, terminal,
454 );
455
456 errors.push(errs1.as_collection(|k, _v| k.clone()));
457 errors.extend(errs2);
458 oks
459 }
460 },
461 JoinedFlavor::Trace(trace) => match arrangement {
462 ArrangementFlavor::Local(oks, errs1) => {
463 let (oks, errs2) = self
464 .differential_join_inner::<RowRowEnter<_, _, _>, RowRowAgent<_, _>>(
465 trace, oks, closure, terminal,
466 );
467
468 errors.push(errs1.as_collection(|k, _v| k.clone()));
469 errors.extend(errs2);
470 oks
471 }
472 ArrangementFlavor::Trace(_gid, oks, errs1) => {
473 let (oks, errs2) = self
474 .differential_join_inner::<RowRowEnter<_, _, _>, RowRowEnter<_, _, _>>(
475 trace, oks, closure, terminal,
476 );
477
478 errors.push(errs1.as_collection(|k, _v| k.clone()));
479 errors.extend(errs2);
480 oks
481 }
482 },
483 }
484 }
485
486 fn differential_join_inner<'s, Tr1, Tr2>(
496 &self,
497 prev_keyed: Arranged<'s, Tr1>,
498 next_input: Arranged<'s, Tr2>,
499 closure: JoinClosure,
500 terminal: bool,
501 ) -> (
502 JoinedFlavor<'s, T>,
503 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
504 )
505 where
506 Tr1: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
507 Tr2: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
508 for<'a> BatchCursor<Tr1>:
509 Cursor<Key<'a>: ExtendDatums, Val<'a>: ExtendDatums, Time = T, Diff = Diff>,
510 for<'a> BatchCursor<Tr2>:
511 Cursor<Key<'a> = BatchKey<'a, Tr1>, Val<'a>: ExtendDatums, Time = T, Diff = Diff>,
512 {
513 let mut datums = DatumVec::new();
515
516 type VecCB<D, T> = CapacityContainerBuilder<Vec<(D, T, Diff)>>;
518
519 if closure.could_error() {
520 let results = self
521 .linear_join_spec
522 .render::<T, _, _, _, _, VecCB<Result<Row, DataflowErrorSer>, T>>(
523 prev_keyed,
524 next_input,
525 move |key, old, new| {
526 apply_join_closure(&closure, &mut datums, key, old, new)
527 .map_err(DataflowErrorSer::from)
528 .transpose()
529 },
530 );
531 let (oks, errs) = demux_join_results(results);
532 (JoinedFlavor::Collection(oks), Some(errs))
533 } else if terminal {
534 let oks = self
535 .linear_join_spec
536 .render::<T, _, _, _, _, ConsolidatingColumnBuilder<Row, T, Diff>>(
537 prev_keyed,
538 next_input,
539 move |key, old, new| {
540 apply_join_closure(&closure, &mut datums, key, old, new)
541 .expect("Closure claimed to never error")
542 },
543 );
544
545 (JoinedFlavor::Collection(oks.as_collection()), None)
546 } else {
547 let oks = self
550 .linear_join_spec
551 .render::<T, _, _, _, _, ColumnBuilder<(Row, T, Diff)>>(
552 prev_keyed,
553 next_input,
554 move |key, old, new| {
555 apply_join_closure(&closure, &mut datums, key, old, new)
556 .expect("Closure claimed to never error")
557 },
558 );
559
560 (JoinedFlavor::Collection(oks.as_collection()), None)
561 }
562 }
563}
564
565fn apply_join_closure<K, V1, V2>(
571 closure: &JoinClosure,
572 datums: &mut DatumVec,
573 key: K,
574 old: V1,
575 new: V2,
576) -> Result<Option<Row>, mz_expr::EvalError>
577where
578 K: ExtendDatums,
579 V1: ExtendDatums,
580 V2: ExtendDatums,
581{
582 let mut row_builder = SharedRow::get();
583 let temp_storage = RowArena::new();
584
585 let mut datums_local = datums.borrow();
586 key.extend_datums(&temp_storage, &mut datums_local, None);
587 old.extend_datums(&temp_storage, &mut datums_local, None);
588 new.extend_datums(&temp_storage, &mut datums_local, None);
589
590 closure
591 .apply(&mut datums_local, &temp_storage, &mut row_builder)
592 .map(|row| row.cloned())
593}
594
595fn demux_join_results<'s, T>(
601 results: Stream<'s, T, Vec<(Result<Row, DataflowErrorSer>, T, Diff)>>,
602) -> (
603 ColCollection<'s, T>,
604 VecCollection<'s, T, DataflowErrorSer, Diff>,
605)
606where
607 T: RenderTimestamp,
608{
609 let (oks, errs) = results.unary_fallible::<ColumnBuilder<(Row, T, Diff)>, _, _, _>(
610 Pipeline,
611 "LinearJoinStageDemux",
612 |_, _| {
613 Box::new(move |input, ok, err| {
614 input.for_each(|time, data| {
615 let mut ok_session = ok.session_with_builder(&time);
616 let mut err_session = err.session(&time);
617 for (result, time, diff) in data.drain(..) {
618 match result {
619 Ok(row) => ok_session.give((&row, &time, &diff)),
620 Err(e) => err_session.give((e, time, diff)),
621 }
622 }
623 });
624 })
625 },
626 );
627 (oks.as_collection(), errs.as_collection())
628}
629
630fn encode_updates<'s, T, D, CB>(
635 collection: VecCollection<'s, T, D, Diff>,
636 name: &str,
637) -> Stream<'s, T, CB::Container>
638where
639 T: timely::progress::Timestamp,
640 D: Data,
641 CB: ContainerBuilder + PushInto<(D, T, Diff)> + 'static,
642{
643 collection
644 .inner
645 .unary::<CB, _, _, _>(Pipeline, name, |_, _| {
646 move |input, output| {
647 input.for_each(|time, data| {
648 output
649 .session_with_builder(&time)
650 .give_iterator(data.drain(..));
651 });
652 }
653 })
654}
655
656fn arrange_keyed_join_input<'s, T>(
658 keyed: Stream<'s, T, Column<((Row, Row), T, Diff)>>,
659 errs: Stream<'s, T, Vec<(DataflowErrorSer, T, Diff)>>,
660 batcher: ArrangementBatcher,
661) -> (
662 Arranged<'s, RowRowAgent<T, Diff>>,
663 VecCollection<'s, T, DataflowErrorSer, Diff>,
664)
665where
666 T: Lattice + RenderTimestamp,
667{
668 let exchange =
669 ExchangeCore::<ColumnBuilder<_>, _>::new_core(columnar_exchange::<Row, Row, T, Diff>);
670 let arranged = match batcher {
671 ArrangementBatcher::Chunked => keyed.mz_arrange_core::<
672 _,
673 ChunkChunker<(Row, Row), T, Diff>,
674 AccountedChunkBatcher<(Row, Row), T, Diff>,
675 UnchunkBuilder<RowRowColPagedBuilder<T, Diff>, (Row, Row), T, Diff>,
676 RowRowSpine<_, _>,
677 >(exchange, "JoinStage"),
678 ArrangementBatcher::Columnar => keyed.mz_arrange_core::<
679 _,
680 batcher::ColumnChunker<_>,
681 Col2ValColBatcher<_, _, _, _>,
682 RowRowColPagedBuilder<_, _>,
683 RowRowSpine<_, _>,
684 >(exchange, "JoinStage"),
685 ArrangementBatcher::Columnation => keyed.mz_arrange_core::<
686 _,
687 batcher::Chunker<_>,
688 Col2ValBatcher<_, _, _, _>,
689 RowRowBuilder<_, _>,
690 RowRowSpine<_, _>,
691 >(exchange, "JoinStage"),
692 };
693 (arranged, errs.as_collection())
694}
695
696fn arrange_join_input<'s, T>(
702 edge: ColCollection<'s, T>,
703 stream_key: Vec<LirScalarExpr>,
704 stream_thinning: Vec<usize>,
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 (keyed, errs) = edge
714 .inner
715 .unary_fallible::<ColumnBuilder<((Row, Row), T, Diff)>, _, _, _>(
716 Pipeline,
717 "LinearJoinKeyPreparation",
718 |_, _| {
719 Box::new(move |input, ok, errs| {
720 let mut temp_storage = RowArena::new();
721 let mut key_buf = Row::default();
722 let mut val_buf = Row::default();
723 let mut datums = DatumVec::new();
724 input.for_each(|time, data| {
725 let mut ok_session = ok.session_with_builder(&time);
726 let mut err_session = errs.session(&time);
727 for (row, time, diff) in data.borrow().into_index_iter() {
728 temp_storage.clear();
729 let datums_local = datums.borrow_with(row);
730 let datums = stream_key
731 .iter()
732 .map(|e| e.eval(&datums_local, &temp_storage));
733 match key_buf.packer().try_extend(datums) {
734 Ok(()) => {
735 val_buf
736 .packer()
737 .extend(stream_thinning.iter().map(|e| datums_local[*e]));
738 ok_session.give(((&key_buf, &val_buf), time, diff));
739 }
740 Err(e) => {
741 err_session.give((
742 e.into(),
743 Columnar::into_owned(time),
744 Columnar::into_owned(diff),
745 ));
746 }
747 }
748 }
749 });
750 })
751 },
752 );
753 arrange_keyed_join_input(keyed, errs, batcher)
754}
755
756#[cfg(test)]
757mod tests {
758 use differential_dataflow::input::Input;
759 use mz_expr::EvalError;
760 use mz_repr::{Datum, ReprScalarType, Timestamp};
761 use timely::dataflow::operators::Capture;
762 use timely::dataflow::operators::capture::{Event, Extract};
763
764 use super::*;
765 use crate::render::columnar::vec_to_columnar;
766
767 type KeyedUpdate = ((Row, Row), Timestamp, Diff);
768 type ErrUpdate = (DataflowErrorSer, Timestamp, Diff);
769 type Captured<D> = std::sync::mpsc::Receiver<Event<Timestamp, Vec<D>>>;
770
771 fn extract_sorted(captured: Captured<KeyedUpdate>) -> Vec<KeyedUpdate> {
772 let mut updates: Vec<_> = captured
773 .extract()
774 .into_iter()
775 .flat_map(|(_, data)| data)
776 .collect();
777 updates.sort();
778 updates
779 }
780
781 fn extract_err(captured: Captured<ErrUpdate>) -> Vec<(String, Timestamp, Diff)> {
783 let mut updates: Vec<_> = captured
784 .extract()
785 .into_iter()
786 .flat_map(|(_, data)| data)
787 .map(|(e, t, d)| (format!("{e:?}"), t, d))
788 .collect();
789 updates.sort();
790 updates
791 }
792
793 fn test_input() -> Vec<(Row, u64, Diff)> {
797 vec![
798 (
799 Row::pack_slice(&[Datum::Int32(1), Datum::String("a")]),
800 0,
801 Diff::ONE,
802 ),
803 (
804 Row::pack_slice(&[Datum::Int32(2), Datum::String("b")]),
805 1,
806 Diff::ONE,
807 ),
808 (
809 Row::pack_slice(&[Datum::Int32(1), Datum::String("c")]),
810 2,
811 Diff::ONE,
812 ),
813 (
814 Row::pack_slice(&[Datum::Int32(3), Datum::Null]),
815 2,
816 Diff::ONE,
817 ),
818 (
819 Row::pack_slice(&[Datum::Int32(2), Datum::String("b")]),
820 2,
821 -Diff::ONE,
822 ),
823 (
824 Row::pack_slice(&[Datum::Int32(4), Datum::String("d")]),
825 1,
826 -Diff::ONE,
827 ),
828 ]
829 }
830
831 fn run_columnar(
834 input: Vec<(Row, u64, Diff)>,
835 key: Vec<LirScalarExpr>,
836 ) -> (Vec<KeyedUpdate>, Vec<(String, Timestamp, Diff)>) {
837 let (ok, err) = timely::execute_directly(move |worker| {
838 worker.dataflow::<Timestamp, _, _>(|scope| {
839 let (mut handle, collection) = scope.new_collection();
840 let (arranged, errs) = arrange_join_input(
841 vec_to_columnar(collection),
842 key,
843 vec![1],
844 ArrangementBatcher::Columnation,
845 );
846 let keyed = arranged.as_collection(|k, v| (k.to_row(), v.to_row()));
847 let ok = keyed.inner.capture();
848 let err = errs.inner.capture();
849 for (row, time, diff) in input {
850 handle.update_at(row, Timestamp::from(time), diff);
851 }
852 handle.advance_to(Timestamp::from(3_u64));
853 handle.flush();
854 (ok, err)
855 })
856 });
857 (extract_sorted(ok), extract_err(err))
858 }
859
860 #[mz_ore::test]
863 fn arrange_join_input_keys_correctly() {
864 let (ok, err) = run_columnar(test_input(), vec![LirScalarExpr::column(0)]);
865 assert!(!ok.is_empty());
866 assert!(err.is_empty());
867 assert!(ok.iter().any(|(_, _, d)| *d < Diff::ZERO));
870 for ((key, value), _t, _d) in &ok {
873 assert_eq!(key.iter().count(), 1);
874 assert_eq!(value.iter().count(), 1);
875 }
876 }
877
878 #[mz_ore::test]
882 fn arrange_join_input_error_path() {
883 let key = vec![LirScalarExpr::literal(
884 Err(EvalError::DivisionByZero),
885 ReprScalarType::Int32,
886 )];
887 let (ok, err) = run_columnar(test_input(), key);
888 assert!(ok.is_empty());
889 assert!(!err.is_empty());
890 }
891}