Skip to main content

mz_compute/render/join/
delta_join.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Delta join execution dataflow construction.
11//!
12//! Consult [DeltaJoinPlan] documentation for details.
13
14#![allow(clippy::op_ref)]
15
16use std::collections::BTreeSet;
17use std::rc::Rc;
18
19use differential_dataflow::consolidation::ConsolidatingContainerBuilder;
20use differential_dataflow::operators::arrange::Arranged;
21use differential_dataflow::trace::cursor::BatchCursor;
22use differential_dataflow::trace::implementations::BatchContainer;
23use differential_dataflow::trace::{Cursor, Navigable, TraceReader};
24use differential_dataflow::{AsCollection, VecCollection};
25use mz_compute_types::dyncfgs::ENABLE_HALF_JOIN2;
26use mz_compute_types::plan::join::JoinClosure;
27use mz_compute_types::plan::join::delta_join::{DeltaJoinPlan, DeltaPathPlan, DeltaStagePlan};
28use mz_compute_types::plan::scalar::LirScalarExpr;
29use mz_dyncfg::ConfigSet;
30use mz_expr::Eval;
31use mz_repr::fixed_length::ExtendDatums;
32use mz_repr::{DatumVec, Diff, Row, RowArena, SharedRow};
33use mz_timely_util::operator::{CollectionExt, StreamExt};
34use timely::container::CapacityContainerBuilder;
35use timely::dataflow::channels::pact::Pipeline;
36use timely::dataflow::operators::OkErr;
37use timely::dataflow::operators::generic::Session;
38use timely::dataflow::operators::vec::Map;
39use timely::progress::Antichain;
40
41use crate::render::RenderTimestamp;
42use crate::render::context::{ArrangementFlavor, CollectionBundle, Context};
43use crate::render::errors::DataflowErrorSer;
44use crate::typedefs::{RowRowAgent, RowRowEnter};
45
46impl<'scope, T: RenderTimestamp> Context<'scope, T> {
47    /// Renders `MirRelationExpr:Join` using dogs^3 delta query dataflows.
48    ///
49    /// The join is followed by the application of `map_filter_project`, whose
50    /// implementation will be pushed in to the join pipeline if at all possible.
51    pub fn render_delta_join(
52        &self,
53        inputs: Vec<CollectionBundle<'scope, T>>,
54        join_plan: DeltaJoinPlan,
55    ) -> CollectionBundle<'scope, T> {
56        // We create a new region to contain the dataflow paths for the delta join.
57        let (oks, errs) = self.scope.clone().region_named("Join(Delta)", |inner| {
58            // Our plan is to iterate through each input relation, and attempt
59            // to find a plan that maximally uses existing keys (better: uses
60            // existing arrangements, to which we have access).
61            let mut join_results = Vec::new();
62
63            // An input bundle may carry collections no delta path consumes: arrangements keyed
64            // differently than any lookup, or a raw collection. Entering a collection into a region
65            // does work proportional to its data (and the raw collection is not reference counted),
66            // so we prune each bundle to what some path reads before bringing it into `inner`.
67            let mut arrangements = vec![BTreeSet::new(); inputs.len()];
68            let mut raw = vec![false; inputs.len()];
69            for path_plan in &join_plan.path_plans {
70                record_path_arrangements(
71                    &mut arrangements,
72                    &mut raw,
73                    path_plan.source_relation,
74                    path_plan.source_key.as_ref(),
75                    &path_plan.stage_plans,
76                );
77            }
78            let inputs = inputs
79                .iter()
80                .enumerate()
81                .map(|(index, cb)| {
82                    prune_bundle(cb, raw[index], &arrangements[index]).enter_region(inner)
83                })
84                .collect::<Vec<_>>();
85
86            // Collects error streams for the ambient scope, seeded with the errors the inputs
87            // arrive with. Every path reads every input, but an input's pre-existing errors belong
88            // in the output once, not once per path. See [`bundle_errs`].
89            let mut inner_errs: Vec<_> = inputs.iter().flat_map(bundle_errs).collect();
90
91            for path_plan in join_plan.path_plans {
92                // Deconstruct the stages of the path plan.
93                let DeltaPathPlan {
94                    source_relation,
95                    initial_closure,
96                    stage_plans,
97                    final_closure,
98                    source_key,
99                } = path_plan;
100
101                // This collection determines changes that result from updates inbound
102                // from `inputs[relation]` and reflects all strictly prior updates and
103                // concurrent updates from relations prior to `relation`.
104                let name = format!("delta path {}", source_relation);
105                let path_results = inner.clone().region_named(&name, |region| {
106                    // The plan is to move through each relation, starting from `relation` and in the order
107                    // indicated in `orders[relation]`. At each moment, we will have the columns from the
108                    // subset of relations encountered so far, and we will have applied as much as we can
109                    // of the filters in `equivalences` and the logic in `map_filter_project`, based on the
110                    // available columns.
111                    //
112                    // As we go, we will track the physical locations of each intended output column, as well
113                    // as the locations of intermediate results from partial application of `map_filter_project`.
114                    //
115                    // Just before we apply the `lookup` function to perform a join, we will first use our
116                    // available information to determine the filtering and logic that we can apply, and
117                    // introduce that in to the `lookup` logic to cause it to happen in that operator.
118
119                    // Prune each bundle to just what this path reads before entering the path's
120                    // region, for the reason noted at the join's region entry above.
121                    let mut path_arrangements = vec![BTreeSet::new(); inputs.len()];
122                    let mut path_raw = vec![false; inputs.len()];
123                    record_path_arrangements(
124                        &mut path_arrangements,
125                        &mut path_raw,
126                        source_relation,
127                        source_key.as_ref(),
128                        &stage_plans,
129                    );
130                    let bundles = inputs
131                        .iter()
132                        .enumerate()
133                        .map(|(index, cb)| {
134                            prune_bundle(cb, path_raw[index], &path_arrangements[index])
135                                .enter_region(region)
136                        })
137                        .collect::<Vec<_>>();
138
139                    // Collects error streams for the region scope. Concats before leaving.
140                    let mut region_errs = Vec::with_capacity(inputs.len());
141
142                    // Form the initial stream of updates that will hydrate the delta path.
143                    let (update_stream, err_stream) = build_update_stream(
144                        &bundles[source_relation],
145                        self.as_of_frontier.clone(),
146                        source_key,
147                        source_relation,
148                        initial_closure,
149                    );
150                    region_errs.push(err_stream);
151
152                    // Promote `time` to a datum element.
153                    //
154                    // The `half_join` operator manipulates as "data" a pair `(data, time)`,
155                    // while tracking the initial time `init_time` separately and without
156                    // modification. The initial value for both times is the initial time.
157                    let mut update_stream = update_stream
158                        .inner
159                        .map(|(v, t, d)| ((v, t.clone()), t, d))
160                        .as_collection();
161
162                    // Repeatedly update `update_stream` to reflect joins with more and more
163                    // other relations, in the specified order.
164                    for stage_plan in stage_plans {
165                        let DeltaStagePlan {
166                            lookup_relation,
167                            stream_key,
168                            stream_thinning,
169                            lookup_key,
170                            closure,
171                        } = stage_plan;
172
173                        // We require different logic based on the relative order of the two inputs.
174                        // If the `source` relation precedes the `lookup` relation, we present all
175                        // updates with less or equal `time`, and otherwise we present only updates
176                        // with strictly less `time`.
177                        //
178                        // We require demuxing over the two flavors of arrangement and over the
179                        // relative order of the inputs. Both are handled inside `build_halfjoin`.
180                        let (oks, errs) = build_halfjoin(
181                            update_stream,
182                            stream_key,
183                            stream_thinning,
184                            &bundles[lookup_relation],
185                            lookup_key,
186                            source_relation < lookup_relation,
187                            closure,
188                            Rc::clone(&self.config_set),
189                        );
190                        update_stream = oks;
191                        region_errs.push(errs);
192                    }
193
194                    // Delay updates as appropriate.
195                    //
196                    // The `half_join` operator maintains a time that we now discard (the `_`),
197                    // and replace with the `time` that is maintained with the data. The former
198                    // exists to pin a consistent total order on updates throughout the process,
199                    // while allowing `time` to vary upwards as a result of actions on time.
200                    let mut update_stream = update_stream
201                        .inner
202                        .map(|((row, time), _, diff)| (row, time, diff))
203                        .as_collection();
204
205                    // We have completed the join building, but may have work remaining.
206                    // For example, we may have expressions not pushed down (e.g. literals)
207                    // and projections that could not be applied (e.g. column repetition).
208                    if let Some(final_closure) = final_closure {
209                        let name = "DeltaJoinFinalization";
210                        type CB<C> = ConsolidatingContainerBuilder<C>;
211                        let (updates, errors) = update_stream
212                            .flat_map_fallible::<CB<_>, CB<_>, _, _, _, _>(name, {
213                                // Reuseable allocation for unpacking.
214                                let mut datums = DatumVec::new();
215                                move |row| {
216                                    let mut row_builder = SharedRow::get();
217                                    let temp_storage = RowArena::new();
218                                    let mut datums_local = datums.borrow_with(&row);
219                                    // TODO(mcsherry): re-use `row` allocation.
220                                    final_closure
221                                        .apply(&mut datums_local, &temp_storage, &mut row_builder)
222                                        .map(|row| row.cloned())
223                                        .map_err(DataflowErrorSer::from)
224                                        .transpose()
225                                }
226                            });
227
228                        update_stream = updates;
229                        region_errs.push(errors);
230                    }
231
232                    inner_errs.push(
233                        differential_dataflow::collection::concatenate(region, region_errs)
234                            .leave_region(inner),
235                    );
236                    update_stream.leave_region(inner)
237                });
238
239                join_results.push(path_results);
240            }
241
242            // Concatenate the results of each delta query as the accumulated results.
243            (
244                differential_dataflow::collection::concatenate(inner, join_results)
245                    .leave_region(self.scope),
246                differential_dataflow::collection::concatenate(inner, inner_errs)
247                    .leave_region(self.scope),
248            )
249        });
250        CollectionBundle::from_collections(oks, errs)
251    }
252}
253
254/// Records what a single delta path reads from each input: each stage's `lookup_key` and, for the
255/// seed relation, either its `source_key` arrangement or, when `source_key` is `None`, its raw
256/// collection (flagged in `raw`).
257fn record_path_arrangements(
258    arrangements: &mut [BTreeSet<Vec<LirScalarExpr>>],
259    raw: &mut [bool],
260    source_relation: usize,
261    source_key: Option<&Vec<LirScalarExpr>>,
262    stage_plans: &[DeltaStagePlan],
263) {
264    match source_key {
265        Some(source_key) => {
266            arrangements[source_relation].insert(source_key.clone());
267        }
268        None => raw[source_relation] = true,
269    }
270    for stage_plan in stage_plans {
271        arrangements[stage_plan.lookup_relation].insert(stage_plan.lookup_key.clone());
272    }
273}
274
275/// Retains only the collections of `bundle` a delta path reads: the arrangements keyed by `keys`,
276/// and the raw collection if `raw`. Every other collection is dropped.
277fn prune_bundle<'scope, T: RenderTimestamp>(
278    bundle: &CollectionBundle<'scope, T>,
279    raw: bool,
280    keys: &BTreeSet<Vec<LirScalarExpr>>,
281) -> CollectionBundle<'scope, T> {
282    CollectionBundle {
283        collection: if raw { bundle.collection.clone() } else { None },
284        arranged: bundle
285            .arranged
286            .iter()
287            .filter(|(key, _)| keys.contains(*key))
288            .map(|(key, flavor)| (key.clone(), flavor.clone()))
289            .collect(),
290    }
291}
292
293/// The error collections of every collection and arrangement `bundle` holds.
294///
295/// A delta join reads every input from every one of its paths, but an input's pre-existing errors
296/// belong in the join's error output once. Propagating them per path would multiply their
297/// multiplicities by the number of paths, and because a join's output is another join's input, those
298/// factors compound multiplicatively through a nested plan until the `Diff` overflows. Error
299/// semantics depend only on presence, so the extra copies buy nothing.
300///
301/// Expects the pruned bundle (see [`prune_bundle`]), so that it yields errors only for the
302/// collections some path actually reads.
303///
304/// NOTE: This bounds an input's errors to one copy per retained form, not to one copy outright.
305/// Each form carries its own error collection, and an arrangement's is a distinct stream built from
306/// the raw one plus that key's key-formation errors, so an input the join reads under two lookup
307/// keys still contributes its errors twice. Reachable whenever a delta path set needs an
308/// error-carrying input arranged by more than one key.
309fn bundle_errs<'scope, T: RenderTimestamp>(
310    bundle: &CollectionBundle<'scope, T>,
311) -> Vec<VecCollection<'scope, T, DataflowErrorSer, Diff>> {
312    let mut collected = Vec::with_capacity(bundle.arranged.len() + 1);
313    if let Some((_oks, errs)) = &bundle.collection {
314        collected.push(errs.clone());
315    }
316    for flavor in bundle.arranged.values() {
317        let errs = match flavor {
318            ArrangementFlavor::Local(_oks, errs) => errs.clone().as_collection(|k, _v| k.clone()),
319            ArrangementFlavor::Trace(_id, _oks, errs) => {
320                errs.clone().as_collection(|k, _v| k.clone())
321            }
322        };
323        collected.push(errs);
324    }
325    collected
326}
327
328/// Constructs a `half_join` against the arrangement held by a collection bundle.
329///
330/// This wrapper demuxes over the two flavors of arrangement (dataflow-local or imported trace)
331/// that the bundle might hold for `lookup_key`, dispatching to the generic [`build_halfjoin_trace`]
332/// for each. `source_precedes_lookup` selects the tie-breaking comparison: `le` if the source
333/// relation precedes the lookup relation in the total order on relations, otherwise `lt`.
334///
335/// The returned error collection holds only the errors this stage produces. The errors `bundle`
336/// already carries are the caller's to propagate, once, rather than once per delta path that looks
337/// the input up. See [`bundle_errs`].
338fn build_halfjoin<'scope, T>(
339    updates: VecCollection<'scope, T, (Row, T), Diff>,
340    prev_key: Vec<LirScalarExpr>,
341    prev_thinning: Vec<usize>,
342    bundle: &CollectionBundle<'scope, T>,
343    lookup_key: Vec<LirScalarExpr>,
344    source_precedes_lookup: bool,
345    closure: JoinClosure,
346    config_set: Rc<ConfigSet>,
347) -> (
348    VecCollection<'scope, T, (Row, T), Diff>,
349    VecCollection<'scope, T, DataflowErrorSer, Diff>,
350)
351where
352    T: RenderTimestamp,
353{
354    match bundle.arrangement(&lookup_key) {
355        Some(ArrangementFlavor::Local(oks, _errs)) => {
356            let (oks, errs2) = if source_precedes_lookup {
357                build_halfjoin_trace::<_, RowRowAgent<_, _>, _>(
358                    updates,
359                    oks,
360                    prev_key,
361                    prev_thinning,
362                    |t1, t2| t1.le(t2),
363                    closure,
364                    config_set,
365                )
366            } else {
367                build_halfjoin_trace::<_, RowRowAgent<_, _>, _>(
368                    updates,
369                    oks,
370                    prev_key,
371                    prev_thinning,
372                    |t1, t2| t1.lt(t2),
373                    closure,
374                    config_set,
375                )
376            };
377            (oks, errs2)
378        }
379        Some(ArrangementFlavor::Trace(_, oks, _errs)) => {
380            let (oks, errs2) = if source_precedes_lookup {
381                build_halfjoin_trace::<_, RowRowEnter<_, _, _>, _>(
382                    updates,
383                    oks,
384                    prev_key,
385                    prev_thinning,
386                    |t1, t2| t1.le(t2),
387                    closure,
388                    config_set,
389                )
390            } else {
391                build_halfjoin_trace::<_, RowRowEnter<_, _, _>, _>(
392                    updates,
393                    oks,
394                    prev_key,
395                    prev_thinning,
396                    |t1, t2| t1.lt(t2),
397                    closure,
398                    config_set,
399                )
400            };
401            (oks, errs2)
402        }
403        None => panic!("Arrangement promised by the planner is absent!"),
404    }
405}
406
407/// Constructs a `half_join` from supplied arguments.
408///
409/// This method exists to factor common logic from four code paths that are generic over the type of trace.
410/// The `comparison` function should either be `le` or `lt` depending on which relation comes first in the
411/// total order on relations (in order to break ties consistently).
412///
413/// The input and output streams are of pairs `(data, time)` where the `time` component can be greater than
414/// the time of the update. This operator may manipulate `time` as part of this pair, but will not manipulate
415/// the time of the update. This is crucial for correctness, as the total order on times of updates is used
416/// to ensure that any two updates are matched at most once.
417fn build_halfjoin_trace<'scope, T, Tr, CF>(
418    updates: VecCollection<'scope, T, (Row, T), Diff>,
419    trace: Arranged<'scope, Tr>,
420    prev_key: Vec<LirScalarExpr>,
421    prev_thinning: Vec<usize>,
422    comparison: CF,
423    closure: JoinClosure,
424    config_set: Rc<ConfigSet>,
425) -> (
426    VecCollection<'scope, T, (Row, T), Diff>,
427    VecCollection<'scope, T, DataflowErrorSer, Diff>,
428)
429where
430    T: RenderTimestamp,
431    Tr: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
432    for<'a> BatchCursor<Tr>: Cursor<
433            Val<'a>: ExtendDatums,
434            KeyContainer: BatchContainer<Owned = Row>,
435            Time = T,
436            Diff = Diff,
437        >,
438    CF: Fn(<BatchCursor<Tr> as Cursor>::TimeGat<'_>, &T) -> bool + 'static,
439{
440    let use_half_join2 = ENABLE_HALF_JOIN2.get(&config_set);
441
442    let name = "DeltaJoinKeyPreparation";
443    type CB<C> = CapacityContainerBuilder<C>;
444    let (updates, errs) = updates.map_fallible::<CB<_>, CB<_>, _, _, _>(name, {
445        // Reuseable allocation for unpacking.
446        let mut datums = DatumVec::new();
447        move |(row, time)| {
448            let temp_storage = RowArena::new();
449            let datums_local = datums.borrow_with(&row);
450            let mut row_builder = SharedRow::get();
451            row_builder.packer().try_extend(
452                prev_key
453                    .iter()
454                    .map(|e| e.eval(&datums_local, &temp_storage)),
455            )?;
456            let key = row_builder.clone();
457            row_builder
458                .packer()
459                .extend(prev_thinning.iter().map(|&c| datums_local[c]));
460            let row_value = row_builder.clone();
461
462            Ok((key, row_value, time))
463        }
464    });
465    let datums = DatumVec::new();
466
467    if use_half_join2 {
468        build_halfjoin2(updates, trace, comparison, closure, datums, errs)
469    } else {
470        build_halfjoin1(updates, trace, comparison, closure, datums, errs)
471    }
472}
473
474/// `half_join2` implementation (less-quadratic, new default).
475fn build_halfjoin2<'scope, T, Tr, CF>(
476    updates: VecCollection<'scope, T, (Row, Row, T), Diff>,
477    trace: Arranged<'scope, Tr>,
478    comparison: CF,
479    closure: JoinClosure,
480    mut datums: DatumVec,
481    errs: VecCollection<'scope, T, DataflowErrorSer, Diff>,
482) -> (
483    VecCollection<'scope, T, (Row, T), Diff>,
484    VecCollection<'scope, T, DataflowErrorSer, Diff>,
485)
486where
487    T: RenderTimestamp,
488    Tr: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
489    for<'a> BatchCursor<Tr>: Cursor<
490            Val<'a>: ExtendDatums,
491            KeyContainer: BatchContainer<Owned = Row>,
492            Time = T,
493            Diff = Diff,
494        >,
495    CF: Fn(<BatchCursor<Tr> as Cursor>::TimeGat<'_>, &T) -> bool + 'static,
496{
497    type CB<C> = CapacityContainerBuilder<C>;
498
499    if closure.could_error() {
500        let (oks, errs2) = differential_dogs3::operators::half_join2::half_join_internal_unsafe(
501            updates,
502            trace,
503            |time, antichain| {
504                antichain.insert(time.step_back());
505            },
506            comparison,
507            // TODO(mcsherry): investigate/establish trade-offs here; time based had problems,
508            // in that we seem to yield too much and do too little work when we do.
509            |_timer, count| count > 1_000_000,
510            // TODO(mcsherry): consider `RefOrMut` in `half_join` interface to allow re-use.
511            move |session: &mut CB<Vec<_>>, key, stream_row, lookup_row, initial, diff1, output| {
512                let mut row_builder = SharedRow::get();
513                let temp_storage = RowArena::new();
514
515                let mut datums_local = datums.borrow();
516                datums_local.extend(key.iter());
517                datums_local.extend(stream_row.iter());
518                lookup_row.extend_datums(&temp_storage, &mut datums_local, None);
519
520                let row = closure.apply(&mut datums_local, &temp_storage, &mut row_builder);
521
522                for (time, diff2) in output.drain(..) {
523                    let row = row.as_ref().map(|row| row.cloned()).map_err(Clone::clone);
524                    let diff = diff1.clone() * diff2.clone();
525                    let data = ((row, time.clone()), initial.clone(), diff);
526                    use timely::container::PushInto;
527                    session.push_into(data);
528                }
529            },
530        )
531        .ok_err(|(data_time, init_time, diff)| {
532            // TODO(mcsherry): consider `ok_err()` for `Collection`.
533            match data_time {
534                (Ok(data), time) => Ok((data.map(|data| (data, time)), init_time, diff)),
535                (Err(err), _time) => Err((DataflowErrorSer::from(err), init_time, diff)),
536            }
537        });
538
539        (
540            oks.as_collection().flat_map(|x| x),
541            errs.concat(errs2.as_collection()),
542        )
543    } else {
544        let oks = differential_dogs3::operators::half_join2::half_join_internal_unsafe(
545            updates,
546            trace,
547            |time, antichain| {
548                antichain.insert(time.step_back());
549            },
550            comparison,
551            // TODO(mcsherry): investigate/establish trade-offs here; time based had problems,
552            // in that we seem to yield too much and do too little work when we do.
553            |_timer, count| count > 1_000_000,
554            // TODO(mcsherry): consider `RefOrMut` in `half_join` interface to allow re-use.
555            move |session: &mut CB<Vec<_>>, key, stream_row, lookup_row, initial, diff1, output| {
556                if output.is_empty() {
557                    return;
558                }
559
560                let mut row_builder = SharedRow::get();
561                let temp_storage = RowArena::new();
562
563                let mut datums_local = datums.borrow();
564                datums_local.extend(key.iter());
565                datums_local.extend(stream_row.iter());
566                lookup_row.extend_datums(&temp_storage, &mut datums_local, None);
567
568                if let Some(row) = closure
569                    .apply(&mut datums_local, &temp_storage, &mut row_builder)
570                    .expect("Closure claimed to never error")
571                {
572                    for (time, diff2) in output.drain(..) {
573                        let diff = diff1.clone() * diff2.clone();
574                        use timely::container::PushInto;
575                        session.push_into(((row.clone(), time.clone()), initial.clone(), diff));
576                    }
577                }
578            },
579        );
580
581        (oks.as_collection(), errs)
582    }
583}
584
585/// Original `half_join` implementation (fallback).
586fn build_halfjoin1<'scope, T, Tr, CF>(
587    updates: VecCollection<'scope, T, (Row, Row, T), Diff>,
588    trace: Arranged<'scope, Tr>,
589    comparison: CF,
590    closure: JoinClosure,
591    mut datums: DatumVec,
592    errs: VecCollection<'scope, T, DataflowErrorSer, Diff>,
593) -> (
594    VecCollection<'scope, T, (Row, T), Diff>,
595    VecCollection<'scope, T, DataflowErrorSer, Diff>,
596)
597where
598    T: RenderTimestamp,
599    Tr: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
600    for<'a> BatchCursor<Tr>: Cursor<
601            Val<'a>: ExtendDatums,
602            KeyContainer: BatchContainer<Owned = Row>,
603            Time = T,
604            Diff = Diff,
605        >,
606    CF: Fn(<BatchCursor<Tr> as Cursor>::TimeGat<'_>, &T) -> bool + 'static,
607{
608    type CB<C> = CapacityContainerBuilder<C>;
609
610    if closure.could_error() {
611        let (oks, errs2) = differential_dogs3::operators::half_join::half_join_internal_unsafe(
612            updates,
613            trace,
614            |time, antichain| {
615                antichain.insert(time.step_back());
616            },
617            comparison,
618            |_timer, count| count > 1_000_000,
619            move |session: &mut Session<'_, '_, T, CB<Vec<_>>, _>,
620                  key,
621                  stream_row: &Row,
622                  lookup_row,
623                  initial,
624                  diff1,
625                  output| {
626                let mut row_builder = SharedRow::get();
627                let temp_storage = RowArena::new();
628
629                let mut datums_local = datums.borrow();
630                datums_local.extend(key.iter());
631                datums_local.extend(stream_row.iter());
632                lookup_row.extend_datums(&temp_storage, &mut datums_local, None);
633
634                let row = closure.apply(&mut datums_local, &temp_storage, &mut row_builder);
635
636                for (time, diff2) in output.drain(..) {
637                    let row = row.as_ref().map(|row| row.cloned()).map_err(Clone::clone);
638                    let diff = diff1.clone() * diff2.clone();
639                    let data = ((row, time.clone()), initial.clone(), diff);
640                    session.give(data);
641                }
642            },
643        )
644        .ok_err(|(data_time, init_time, diff)| match data_time {
645            (Ok(data), time) => Ok((data.map(|data| (data, time)), init_time, diff)),
646            (Err(err), _time) => Err((DataflowErrorSer::from(err), init_time, diff)),
647        });
648
649        (
650            oks.as_collection().flat_map(|x| x),
651            errs.concat(errs2.as_collection()),
652        )
653    } else {
654        let oks = differential_dogs3::operators::half_join::half_join_internal_unsafe(
655            updates,
656            trace,
657            |time, antichain| {
658                antichain.insert(time.step_back());
659            },
660            comparison,
661            |_timer, count| count > 1_000_000,
662            move |session: &mut Session<'_, '_, T, CB<Vec<_>>, _>,
663                  key,
664                  stream_row: &Row,
665                  lookup_row,
666                  initial,
667                  diff1,
668                  output| {
669                if output.is_empty() {
670                    return;
671                }
672
673                let mut row_builder = SharedRow::get();
674                let temp_storage = RowArena::new();
675
676                let mut datums_local = datums.borrow();
677                datums_local.extend(key.iter());
678                datums_local.extend(stream_row.iter());
679                lookup_row.extend_datums(&temp_storage, &mut datums_local, None);
680
681                if let Some(row) = closure
682                    .apply(&mut datums_local, &temp_storage, &mut row_builder)
683                    .expect("Closure claimed to never error")
684                {
685                    for (time, diff2) in output.drain(..) {
686                        let diff = diff1.clone() * diff2.clone();
687                        session.give(((row.clone(), time.clone()), initial.clone(), diff));
688                    }
689                }
690            },
691        );
692
693        (oks.as_collection(), errs)
694    }
695}
696
697/// Builds the initial update stream of a delta path from a collection bundle.
698///
699/// With a `source_key`, demuxes over the two flavors of arrangement the bundle might hold for that
700/// key, dispatching to the generic [`build_update_stream_trace`]. Without a `source_key`, the source
701/// relation is consumed as a raw (unarranged) collection via [`build_update_stream_stream`].
702///
703/// The returned error collection holds only the errors the initial closure produces. The errors
704/// `bundle` already carries are the caller's to propagate, once, rather than once per delta path.
705/// See [`bundle_errs`].
706fn build_update_stream<'scope, T>(
707    bundle: &CollectionBundle<'scope, T>,
708    as_of: Antichain<mz_repr::Timestamp>,
709    source_key: Option<Vec<LirScalarExpr>>,
710    source_relation: usize,
711    initial_closure: JoinClosure,
712) -> (
713    VecCollection<'scope, T, Row, Diff>,
714    VecCollection<'scope, T, DataflowErrorSer, Diff>,
715)
716where
717    T: RenderTimestamp,
718{
719    let Some(source_key) = source_key else {
720        // No source key means a single-time dataflow (e.g. a `SELECT`) whose plan was truncated to
721        // this one path, letting us hydrate the source from its raw collection instead of an
722        // arrangement.
723        let (oks, _errs) = bundle
724            .collection
725            .clone()
726            .expect("The unarranged collection doesn't exist.");
727        return build_update_stream_stream(oks.into_vec(), as_of, source_relation, initial_closure);
728    };
729    match bundle.arrangement(&source_key) {
730        Some(ArrangementFlavor::Local(oks, _errs)) => {
731            build_update_stream_trace::<_, RowRowAgent<_, _>>(
732                oks,
733                as_of,
734                source_relation,
735                initial_closure,
736            )
737        }
738        Some(ArrangementFlavor::Trace(_, oks, _errs)) => {
739            build_update_stream_trace::<_, RowRowEnter<_, _, _>>(
740                oks,
741                as_of,
742                source_relation,
743                initial_closure,
744            )
745        }
746        None => panic!("Arrangement promised by the planner is absent!"),
747    }
748}
749
750/// Builds the beginning of the update stream of a delta path.
751///
752/// At start-up time only the delta path for the first relation sees updates, since any updates fed to the
753/// other delta paths would be discarded anyway due to the tie-breaking logic that avoids double-counting
754/// updates happening at the same time on different relations.
755fn build_update_stream_trace<'scope, T, Tr>(
756    trace: Arranged<'scope, Tr>,
757    as_of: Antichain<mz_repr::Timestamp>,
758    source_relation: usize,
759    initial_closure: JoinClosure,
760) -> (
761    VecCollection<'scope, T, Row, Diff>,
762    VecCollection<'scope, T, DataflowErrorSer, Diff>,
763)
764where
765    T: RenderTimestamp,
766    for<'a, 'b> &'a T: PartialEq<<BatchCursor<Tr> as Cursor>::TimeGat<'b>>,
767    Tr: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
768    for<'a> BatchCursor<Tr>:
769        Cursor<Key<'a>: ExtendDatums, Val<'a>: ExtendDatums, Time = T, Diff = Diff>,
770{
771    let mut inner_as_of = Antichain::new();
772    for event_time in as_of.elements().iter() {
773        inner_as_of.insert(<T>::to_inner(event_time.clone()));
774    }
775
776    let (ok_stream, err_stream) =
777        trace
778            .stream
779            .unary_fallible(Pipeline, "UpdateStream", move |_, _| {
780                let mut datums = DatumVec::new();
781                Box::new(move |input, ok_output, err_output| {
782                    // Buffer to accumulate contributing (time, diff) pairs for each (key, val).
783                    let mut times_diffs = Vec::default();
784                    input.for_each(|time, data| {
785                        let mut row_builder = SharedRow::get();
786                        let mut ok_session = ok_output.session(&time);
787                        let mut err_session = err_output.session(&time);
788
789                        for wrapper in data.iter() {
790                            let batch = &wrapper;
791                            let mut cursor = batch.cursor();
792                            while let Some(key) = cursor.get_key(batch) {
793                                while let Some(val) = cursor.get_val(batch) {
794                                    // Collect contributing (time, diff) pairs before invoking the closure.
795                                    cursor.map_times(batch, |time, diff| {
796                                        if source_relation == 0
797                                            || inner_as_of.elements().iter().all(|e| e != time)
798                                        {
799                                            // TODO: Consolidate as we push, defensively.
800                                            times_diffs.push((
801                                                <BatchCursor<Tr> as Cursor>::owned_time(time),
802                                                <BatchCursor<Tr> as Cursor>::owned_diff(diff),
803                                            ));
804                                        }
805                                    });
806                                    differential_dataflow::consolidation::consolidate(
807                                        &mut times_diffs,
808                                    );
809                                    // The can not-uncommonly be empty, if the inbound updates cancel.
810                                    if !times_diffs.is_empty() {
811                                        let temp_storage = RowArena::new();
812
813                                        let mut datums_local = datums.borrow();
814                                        key.extend_datums(&temp_storage, &mut datums_local, None);
815                                        val.extend_datums(&temp_storage, &mut datums_local, None);
816
817                                        if !initial_closure.is_identity() {
818                                            match initial_closure
819                                                .apply(
820                                                    &mut datums_local,
821                                                    &temp_storage,
822                                                    &mut row_builder,
823                                                )
824                                                .map(|row| row.cloned())
825                                                .transpose()
826                                            {
827                                                Some(Ok(row)) => {
828                                                    for (time, diff) in times_diffs.drain(..) {
829                                                        ok_session.give((row.clone(), time, diff))
830                                                    }
831                                                }
832                                                Some(Err(err)) => {
833                                                    for (time, diff) in times_diffs.drain(..) {
834                                                        err_session.give((err.clone(), time, diff))
835                                                    }
836                                                }
837                                                None => {}
838                                            }
839                                        } else {
840                                            let row = {
841                                                row_builder.packer().extend(&*datums_local);
842                                                row_builder.clone()
843                                            };
844                                            for (time, diff) in times_diffs.drain(..) {
845                                                ok_session.give((row.clone(), time, diff));
846                                            }
847                                        }
848                                    }
849                                    times_diffs.clear();
850
851                                    cursor.step_val(batch);
852                                }
853                                cursor.step_key(batch);
854                            }
855                        }
856                    });
857                })
858            });
859
860    (
861        ok_stream.as_collection(),
862        err_stream.as_collection().map(DataflowErrorSer::from),
863    )
864}
865
866/// Builds the beginning of the update stream of a delta path from a raw collection.
867///
868/// This is the unarranged counterpart of [`build_update_stream_trace`]. Only the delta path for the
869/// first relation can be seeded from a raw collection, since the as-of filtering that the other
870/// paths rely on is only available from an arrangement's times. We assert that here.
871fn build_update_stream_stream<'scope, T>(
872    stream: VecCollection<'scope, T, Row, Diff>,
873    _as_of: Antichain<mz_repr::Timestamp>,
874    source_relation: usize,
875    initial_closure: JoinClosure,
876) -> (
877    VecCollection<'scope, T, Row, Diff>,
878    VecCollection<'scope, T, DataflowErrorSer, Diff>,
879)
880where
881    T: RenderTimestamp,
882{
883    // The other paths discard updates at the as-of, so only the first relation's path can be seeded
884    // from a raw collection that carries no per-update times to filter on.
885    assert_eq!(source_relation, 0);
886
887    type CB<C> = ConsolidatingContainerBuilder<C>;
888    stream.flat_map_fallible::<CB<_>, CB<_>, _, _, _, _>("UpdateStream", {
889        // Reuseable allocation for unpacking.
890        let mut datums = DatumVec::new();
891        move |row| {
892            let mut row_builder = SharedRow::get();
893            let temp_storage = RowArena::new();
894            let mut datums_local = datums.borrow_with(&row);
895            initial_closure
896                .apply(&mut datums_local, &temp_storage, &mut row_builder)
897                .map(|row| row.cloned())
898                .map_err(DataflowErrorSer::from)
899                .transpose()
900        }
901    })
902}