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