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}