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