Skip to main content

mz_compute/
render.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//! Renders a plan into a timely/differential dataflow computation.
11//!
12//! ## Error handling
13//!
14//! Timely and differential have no idioms for computations that can error. The
15//! philosophy is, reasonably, to define the semantics of the computation such
16//! that errors are unnecessary: e.g., by using wrap-around semantics for
17//! integer overflow.
18//!
19//! Unfortunately, SQL semantics are not nearly so elegant, and require errors
20//! in myriad cases. The classic example is a division by zero, but invalid
21//! input for casts, overflowing integer operations, and dozens of other
22//! functions need the ability to produce errors ar runtime.
23//!
24//! At the moment, only *scalar* expression evaluation can fail, so only
25//! operators that evaluate scalar expressions can fail. At the time of writing,
26//! that includes map, filter, reduce, and join operators. Constants are a bit
27//! of a special case: they can be either a constant vector of rows *or* a
28//! constant, singular error.
29//!
30//! The approach taken is to build two parallel trees of computation: one for
31//! the rows that have been successfully evaluated (the "oks tree"), and one for
32//! the errors that have been generated (the "errs tree"). For example:
33//!
34//! ```text
35//!    oks1  errs1       oks2  errs2
36//!      |     |           |     |
37//!      |     |           |     |
38//!   project  |           |     |
39//!      |     |           |     |
40//!      |     |           |     |
41//!     map    |           |     |
42//!      |\    |           |     |
43//!      | \   |           |     |
44//!      |  \  |           |     |
45//!      |   \ |           |     |
46//!      |    \|           |     |
47//!   project  +           +     +
48//!      |     |          /     /
49//!      |     |         /     /
50//!    join ------------+     /
51//!      |     |             /
52//!      |     | +----------+
53//!      |     |/
54//!     oks   errs
55//! ```
56//!
57//! The project operation cannot fail, so errors from errs1 are propagated
58//! directly. Map operators are fallible and so can inject additional errors
59//! into the stream. Join operators combine the errors from each of their
60//! inputs.
61//!
62//! The semantics of the error stream are minimal. From the perspective of SQL,
63//! a dataflow is considered to be in an error state if there is at least one
64//! element in the final errs collection. The error value returned to the user
65//! is selected arbitrarily; SQL only makes provisions to return one error to
66//! the user at a time. There are plans to make the err collection accessible to
67//! end users, so they can see all errors at once.
68//!
69//! To make errors transient, simply ensure that the operator can retract any
70//! produced errors when corrected data arrives. To make errors permanent, write
71//! the operator such that it never retracts the errors it produced. Future work
72//! will likely want to introduce some sort of sort order for errors, so that
73//! permanent errors are returned to the user ahead of transient errors—probably
74//! by introducing a new error type a la:
75//!
76//! ```no_run
77//! # struct EvalError;
78//! # struct SourceError;
79//! enum DataflowError {
80//!     Transient(EvalError),
81//!     Permanent(SourceError),
82//! }
83//! ```
84//!
85//! If the error stream is empty, the oks stream must be correct. If the error
86//! stream is non-empty, then there are no semantics for the oks stream. This is
87//! sufficient to support SQL in its current form, but is likely to be
88//! unsatisfactory long term. We suspect that we can continue to imbue the oks
89//! stream with semantics if we are very careful in describing what data should
90//! and should not be produced upon encountering an error. Roughly speaking, the
91//! oks stream could represent the correct result of the computation where all
92//! rows that caused an error have been pruned from the stream. There are
93//! strange and confusing questions here around foreign keys, though: what if
94//! the optimizer proves that a particular key must exist in a collection, but
95//! the key gets pruned away because its row participated in a scalar expression
96//! evaluation that errored?
97//!
98//! In the meantime, it is probably wise for operators to keep the oks stream
99//! roughly "as correct as possible" even when errors are present in the errs
100//! stream. This reduces the amount of recomputation that must be performed
101//! if/when the errors are retracted.
102
103use std::any::Any;
104use std::cell::RefCell;
105use std::collections::{BTreeMap, BTreeSet};
106use std::convert::Infallible;
107use std::future::Future;
108use std::pin::Pin;
109use std::rc::{Rc, Weak};
110use std::sync::Arc;
111use std::task::Poll;
112
113use differential_dataflow::dynamic::pointstamp::PointStamp;
114use differential_dataflow::lattice::Lattice;
115use differential_dataflow::operators::arrange::Arranged;
116use differential_dataflow::operators::arrange::ShutdownButton;
117use differential_dataflow::operators::iterate::Variable;
118use differential_dataflow::trace::cursor::{BatchCursor, BatchDiff, BatchKey, BatchVal};
119use differential_dataflow::trace::{BatchReader, Cursor, Navigable, TraceReader};
120use differential_dataflow::{AsCollection, Data, VecCollection};
121use futures::FutureExt;
122use futures::channel::oneshot;
123use itertools::Itertools;
124use mz_compute_types::dataflows::{DataflowDescription, IndexDesc};
125use mz_compute_types::dyncfgs::{
126    COMPUTE_APPLY_COLUMN_DEMANDS, COMPUTE_LOGICAL_BACKPRESSURE_INFLIGHT_SLACK,
127    COMPUTE_LOGICAL_BACKPRESSURE_MAX_RETAINED_CAPABILITIES, ENABLE_COMPUTE_LOGICAL_BACKPRESSURE,
128    ENABLE_COMPUTE_TEMPORAL_BUCKETING, ENABLE_ERROR_DISTINCT, SUBSCRIBE_SNAPSHOT_OPTIMIZATION,
129    TEMPORAL_BUCKETING_SUMMARY,
130};
131use mz_compute_types::plan::render_plan::{
132    self, BindStage, LetBind, LetFreePlan, RecBind, RenderPlan,
133};
134use mz_compute_types::plan::scalar::LirScalarExpr;
135use mz_compute_types::plan::{ArrangementStrategy, LirId};
136use mz_expr::{EvalError, Id, LocalId, permutation_for_arrangement};
137use mz_persist_client::operators::shard_source::{ErrorHandler, SnapshotMode};
138use mz_repr::explain::DummyHumanizer;
139use mz_repr::fixed_length::ExtendDatums;
140use mz_repr::{Datum, DatumVec, Diff, GlobalId, ReprRelationType, Row, RowArena, SharedRow};
141use mz_storage_operators::persist_source;
142use mz_storage_types::controller::CollectionMetadata;
143use mz_timely_util::columnation::ColumnationChunker;
144use mz_timely_util::operator::{CollectionExt, StreamExt};
145use mz_timely_util::probe::{Handle as MzProbeHandle, ProbeNotify};
146use mz_timely_util::scope_label::ScopeExt;
147use timely::PartialOrder;
148use timely::container::CapacityContainerBuilder;
149use timely::dataflow::channels::pact::Pipeline;
150use timely::dataflow::operators::vec::ToStream;
151use timely::dataflow::operators::vec::{BranchWhen, Filter};
152use timely::dataflow::operators::{Capability, Operator, Probe, probe};
153use timely::dataflow::{Scope, Stream, StreamVec};
154use timely::order::{Product, TotalOrder};
155use timely::progress::timestamp::Refines;
156use timely::progress::{Antichain, Timestamp};
157use timely::scheduling::ActivateOnDrop;
158use timely::worker::Worker as TimelyWorker;
159
160use crate::arrangement::manager::TraceBundle;
161use crate::compute_state::ComputeState;
162use crate::extensions::arrange::{KeyCollection, MzArrange};
163use crate::extensions::reduce::MzReduce;
164use crate::extensions::temporal_bucket::TemporalBucketing;
165use crate::logging::compute::{
166    ComputeEvent, DataflowGlobal, LirMapping, LirMetadata, LogDataflowErrors, OperatorHydration,
167};
168use crate::render::columnar::CollectionEdge;
169use crate::render::context::{ArrangementFlavor, Context};
170use crate::render::errors::DataflowErrorSer;
171use crate::typedefs::{ErrBatcher, ErrBuilder, ErrSpine, KeyBatcher, MzTimestamp};
172use mz_row_spine::{DatumSeq, RowRowBatcher, RowRowBuilder};
173
174pub(crate) mod columnar;
175pub mod context;
176pub(crate) mod errors;
177mod flat_map;
178mod join;
179mod reduce;
180pub mod sinks;
181mod threshold;
182mod top_k;
183
184pub use context::CollectionBundle;
185pub use join::LinearJoinSpec;
186
187/// Guard that presses a differential [`ShutdownButton`] when dropped.
188///
189/// Dropping this guard releases the imported trace's capabilities.
190struct PressOnDrop<T>(ShutdownButton<T>);
191
192impl<T> Drop for PressOnDrop<T> {
193    fn drop(&mut self) {
194        self.0.press();
195    }
196}
197
198/// Assemble the "compute"  side of a dataflow, i.e. all but the sources.
199///
200/// This method imports sources from provided assets, and then builds the remaining
201/// dataflow using "compute-local" assets like shared arrangements, and producing
202/// both arrangements and sinks.
203pub fn build_compute_dataflow(
204    timely_worker: &mut TimelyWorker,
205    compute_state: &mut ComputeState,
206    dataflow: DataflowDescription<RenderPlan, CollectionMetadata>,
207    start_signal: StartSignal,
208    until: Antichain<mz_repr::Timestamp>,
209    dataflow_expiration: Antichain<mz_repr::Timestamp>,
210) {
211    // Mutually recursive view definitions require special handling.
212    let recursive = dataflow
213        .objects_to_build
214        .iter()
215        .any(|object| object.plan.is_recursive());
216
217    // Determine indexes to export, and their dependencies.
218    let indexes = dataflow
219        .index_exports
220        .iter()
221        .map(|(idx_id, (idx, _typ))| (*idx_id, dataflow.depends_on(idx.on_id), idx.as_lir()))
222        .collect::<Vec<_>>();
223
224    // Determine sinks to export, and their dependencies.
225    let sinks = dataflow
226        .sink_exports
227        .iter()
228        .map(|(sink_id, sink)| (*sink_id, dataflow.depends_on(sink.from), sink.clone()))
229        .collect::<Vec<_>>();
230
231    let worker_logging = timely_worker.logger_for("timely").map(Into::into);
232    let apply_demands = COMPUTE_APPLY_COLUMN_DEMANDS.get(&compute_state.worker_config);
233    let subscribe_snapshot_optimization =
234        SUBSCRIBE_SNAPSHOT_OPTIMIZATION.get(&compute_state.worker_config);
235
236    let name = format!("Dataflow: {}", dataflow.debug_name);
237    let input_name = format!("InputRegion: {}", dataflow.debug_name);
238    let build_name = format!("BuildRegion: {}", dataflow.debug_name);
239
240    timely_worker.dataflow_core(&name, worker_logging, Box::new(()), |_, scope| {
241        let scope = scope.with_label();
242
243        // The scope.clone() occurs to allow import in the region.
244        // We build a region here to establish a pattern of a scope inside the dataflow,
245        // so that other similar uses (e.g. with iterative scopes) do not require weird
246        // alternate type signatures.
247        let mut imported_sources = Vec::new();
248        let mut tokens: BTreeMap<_, Rc<dyn Any>> = BTreeMap::new();
249        let output_probe = MzProbeHandle::default();
250
251        scope.clone().region_named(&input_name, |region| {
252            // Import declared sources into the rendering context.
253            for (source_id, import) in dataflow.source_imports.iter() {
254                region.region_named(&format!("Source({:?})", source_id), |inner| {
255                    let mut read_schema = None;
256                    let mut mfp = import.desc.arguments.operators.clone().map(|mut ops| {
257                        // If enabled, we read from Persist with a `RelationDesc` that
258                        // omits uneeded columns.
259                        if apply_demands {
260                            let demands = ops.demand();
261                            let new_desc = import
262                                .desc
263                                .storage_metadata
264                                .relation_desc
265                                .apply_demand(&demands);
266                            let new_arity = demands.len();
267                            let remap: BTreeMap<_, _> = demands
268                                .into_iter()
269                                .enumerate()
270                                .map(|(new, old)| (old, new))
271                                .collect();
272                            ops.permute_fn(|old_idx| remap[&old_idx], new_arity);
273                            read_schema = Some(new_desc);
274                        }
275
276                        mz_expr::MfpPlan::create_from(ops)
277                            .expect("Linear operators should always be valid")
278                    });
279
280                    let snapshot_mode = if import.with_snapshot || !subscribe_snapshot_optimization
281                    {
282                        SnapshotMode::Include
283                    } else {
284                        compute_state.metrics.inc_subscribe_snapshot_optimization();
285                        SnapshotMode::Exclude
286                    };
287                    let suppress_early_progress_as_of = dataflow.as_of.clone();
288
289                    // Note: For correctness, we require that sources only emit times advanced by
290                    // `dataflow.as_of`. `persist_source` is documented to provide this guarantee.
291                    let (mut ok_stream, err_stream, token) =
292                        persist_source::persist_source::<DataflowErrorSer>(
293                            inner,
294                            *source_id,
295                            Arc::clone(&compute_state.persist_clients),
296                            &compute_state.txns_ctx,
297                            import.desc.storage_metadata.clone(),
298                            read_schema,
299                            dataflow.as_of.clone(),
300                            snapshot_mode,
301                            until.clone(),
302                            mfp.as_mut(),
303                            compute_state.dataflow_max_inflight_bytes(),
304                            start_signal.clone().into_send_future(),
305                            ErrorHandler::Halt("compute_import"),
306                        );
307
308                    // If `mfp` is non-identity, we need to apply what remains.
309                    // For the moment, assert that it is either trivial or `None`.
310                    assert!(mfp.map(|x| x.is_identity()).unwrap_or(true));
311
312                    // To avoid a memory spike during arrangement hydration (database-issues#6368), need to
313                    // ensure that the first frontier we report into the dataflow is beyond the
314                    // `as_of`.
315                    if let Some(as_of) = suppress_early_progress_as_of {
316                        ok_stream = suppress_early_progress(ok_stream, as_of);
317                    }
318
319                    if ENABLE_COMPUTE_LOGICAL_BACKPRESSURE.get(&compute_state.worker_config) {
320                        // Apply logical backpressure to the source.
321                        let limit = COMPUTE_LOGICAL_BACKPRESSURE_MAX_RETAINED_CAPABILITIES
322                            .get(&compute_state.worker_config);
323                        let slack = COMPUTE_LOGICAL_BACKPRESSURE_INFLIGHT_SLACK
324                            .get(&compute_state.worker_config)
325                            .as_millis()
326                            .try_into()
327                            .expect("must fit");
328
329                        let stream = ok_stream.limit_progress(
330                            output_probe.clone(),
331                            slack,
332                            limit,
333                            import.upper.clone(),
334                            name.clone(),
335                        );
336                        ok_stream = stream;
337                    }
338
339                    // Attach a probe reporting the input frontier.
340                    let input_probe =
341                        compute_state.input_probe_for(*source_id, dataflow.export_ids());
342                    ok_stream = ok_stream.probe_with(&input_probe);
343
344                    let (oks, errs) = (
345                        ok_stream
346                            .as_collection()
347                            .leave_region(region)
348                            .leave_region(scope),
349                        err_stream
350                            .as_collection()
351                            .leave_region(region)
352                            .leave_region(scope),
353                    );
354
355                    imported_sources.push((mz_expr::Id::Global(*source_id), (oks, errs)));
356
357                    // Associate returned tokens with the source identifier.
358                    tokens.insert(*source_id, Rc::new(token));
359                });
360            }
361        });
362
363        // If there exists a recursive expression, we'll need to use a non-region scope,
364        // in order to support additional timestamp coordinates for iteration.
365        if recursive {
366            scope.clone().iterative::<PointStamp<u64>, _, _>(|region| {
367                let mut context = Context::for_dataflow_in(
368                    &dataflow,
369                    region.clone(),
370                    compute_state,
371                    until,
372                    dataflow_expiration,
373                );
374
375                for (id, (oks, errs)) in imported_sources.into_iter() {
376                    let bundle = crate::render::CollectionBundle::from_collections(
377                        oks.enter(region),
378                        errs.enter(region),
379                    );
380                    // Associate collection bundle with the source identifier.
381                    context.insert_id(id, bundle);
382                }
383
384                // Import declared indexes into the rendering context.
385                for (idx_id, idx) in &dataflow.index_imports {
386                    let input_probe = compute_state.input_probe_for(*idx_id, dataflow.export_ids());
387                    let snapshot_mode = if idx.with_snapshot || !subscribe_snapshot_optimization {
388                        SnapshotMode::Include
389                    } else {
390                        compute_state.metrics.inc_subscribe_snapshot_optimization();
391                        SnapshotMode::Exclude
392                    };
393                    context.import_index(
394                        scope,
395                        compute_state,
396                        &mut tokens,
397                        input_probe,
398                        *idx_id,
399                        &idx.desc.as_lir(),
400                        &idx.typ,
401                        snapshot_mode,
402                        start_signal.clone(),
403                    );
404                }
405
406                // Build declared objects.
407                for object in dataflow.objects_to_build {
408                    let bundle = context.scope.clone().region_named(
409                        &format!("BuildingObject({:?})", object.id),
410                        |region| {
411                            let depends = object.plan.depends();
412                            let in_let = object.plan.is_recursive();
413                            context
414                                .enter_region(region, Some(&depends))
415                                .render_recursive_plan(
416                                    object.id,
417                                    0,
418                                    object.plan,
419                                    // recursive plans _must_ have bodies in a let
420                                    BindingInfo::Body { in_let },
421                                )
422                                .leave_region(context.scope)
423                        },
424                    );
425                    let global_id = object.id;
426
427                    context.log_dataflow_global_id(
428                        *bundle
429                            .scope()
430                            .addr()
431                            .first()
432                            .expect("Dataflow root id must exist"),
433                        global_id,
434                    );
435                    context.insert_id(Id::Global(object.id), bundle);
436                }
437
438                // Export declared indexes.
439                for (idx_id, dependencies, idx) in indexes {
440                    context.export_index_iterative(
441                        scope,
442                        compute_state,
443                        &tokens,
444                        dependencies,
445                        idx_id,
446                        &idx,
447                        &output_probe,
448                    );
449                }
450
451                // Export declared sinks.
452                for (sink_id, dependencies, sink) in sinks {
453                    context.export_sink(
454                        compute_state,
455                        &tokens,
456                        dependencies,
457                        sink_id,
458                        &sink,
459                        start_signal.clone(),
460                        &output_probe,
461                        scope,
462                    );
463                }
464            });
465        } else {
466            scope.clone().region_named(&build_name, |region| {
467                let mut context = Context::for_dataflow_in(
468                    &dataflow,
469                    region.clone(),
470                    compute_state,
471                    until,
472                    dataflow_expiration,
473                );
474
475                for (id, (oks, errs)) in imported_sources.into_iter() {
476                    let bundle = crate::render::CollectionBundle::from_collections(
477                        oks.enter_region(region),
478                        errs.enter_region(region),
479                    );
480                    // Associate collection bundle with the source identifier.
481                    context.insert_id(id, bundle);
482                }
483
484                // Import declared indexes into the rendering context.
485                for (idx_id, idx) in &dataflow.index_imports {
486                    let input_probe = compute_state.input_probe_for(*idx_id, dataflow.export_ids());
487                    let snapshot_mode = if idx.with_snapshot || !subscribe_snapshot_optimization {
488                        SnapshotMode::Include
489                    } else {
490                        compute_state.metrics.inc_subscribe_snapshot_optimization();
491                        SnapshotMode::Exclude
492                    };
493                    context.import_index(
494                        scope,
495                        compute_state,
496                        &mut tokens,
497                        input_probe,
498                        *idx_id,
499                        &idx.desc.as_lir(),
500                        &idx.typ,
501                        snapshot_mode,
502                        start_signal.clone(),
503                    );
504                }
505
506                // Build declared objects.
507                for object in dataflow.objects_to_build {
508                    let bundle = context.scope.clone().region_named(
509                        &format!("BuildingObject({:?})", object.id),
510                        |region| {
511                            let depends = object.plan.depends();
512                            context
513                                .enter_region(region, Some(&depends))
514                                .render_plan(object.id, object.plan)
515                                .leave_region(context.scope)
516                        },
517                    );
518                    let global_id = object.id;
519                    context.log_dataflow_global_id(
520                        *bundle
521                            .scope()
522                            .addr()
523                            .first()
524                            .expect("Dataflow root id must exist"),
525                        global_id,
526                    );
527                    context.insert_id(Id::Global(object.id), bundle);
528                }
529
530                // Export declared indexes.
531                for (idx_id, dependencies, idx) in indexes {
532                    context.export_index(
533                        compute_state,
534                        &tokens,
535                        dependencies,
536                        idx_id,
537                        &idx,
538                        &output_probe,
539                    );
540                }
541
542                // Export declared sinks.
543                for (sink_id, dependencies, sink) in sinks {
544                    context.export_sink(
545                        compute_state,
546                        &tokens,
547                        dependencies,
548                        sink_id,
549                        &sink,
550                        start_signal.clone(),
551                        &output_probe,
552                        scope,
553                    );
554                }
555            });
556        }
557    });
558}
559
560// This implementation block allows child timestamps to vary from parent timestamps,
561// but requires the parent timestamp to be `repr::Timestamp`.
562impl<'g, T> Context<'g, T>
563where
564    T: Refines<mz_repr::Timestamp> + RenderTimestamp,
565{
566    /// Import the collection from the arrangement, discarding batches from the snapshot.
567    /// (This does not guarantee that no records from the snapshot are included; the assumption is
568    /// that we'll filter those out later if necessary.)
569    fn import_filtered_index_collection<
570        'outer,
571        Tr: TraceReader<Time = mz_repr::Timestamp, Batch: Navigable> + Clone,
572        V: Data,
573    >(
574        &self,
575        arranged: Arranged<'outer, Tr>,
576        start_signal: StartSignal,
577        mut logic: impl FnMut(BatchKey<'_, Tr>, BatchVal<'_, Tr>) -> V + 'static,
578    ) -> VecCollection<'g, T, V, BatchDiff<Tr>>
579    where
580        // This is implied by the fact that the outer timestamp = mz_repr::Timestamp, but it's essential
581        // for our batch-level filtering to be safe, so we document it here regardless.
582        mz_repr::Timestamp: TotalOrder,
583        BatchCursor<Tr>: Cursor<Time = mz_repr::Timestamp>,
584    {
585        let oks = arranged.stream.with_start_signal(start_signal).filter({
586            let as_of = self.as_of_frontier.clone();
587            move |b| !<Antichain<mz_repr::Timestamp> as PartialOrder>::less_equal(b.upper(), &as_of)
588        });
589        Arranged::<'outer, Tr>::flat_map_batches(oks, move |a, b| [logic(a, b)]).enter(self.scope)
590    }
591
592    pub(crate) fn import_index<'outer>(
593        &mut self,
594        outer: Scope<'outer, mz_repr::Timestamp>,
595        compute_state: &mut ComputeState,
596        tokens: &mut BTreeMap<GlobalId, Rc<dyn std::any::Any>>,
597        input_probe: probe::Handle<mz_repr::Timestamp>,
598        idx_id: GlobalId,
599        idx: &IndexDesc<LirScalarExpr>,
600        typ: &ReprRelationType,
601        snapshot_mode: SnapshotMode,
602        start_signal: StartSignal,
603    ) {
604        if let Some(traces) = compute_state.traces.get_mut(&idx_id) {
605            assert!(
606                PartialOrder::less_equal(&traces.compaction_frontier(), &self.as_of_frontier),
607                "Index {idx_id} has been allowed to compact beyond the dataflow as_of"
608            );
609
610            let token = traces.to_drop().clone();
611
612            let (mut oks, ok_button) = traces.oks_mut().import_frontier_core(
613                outer,
614                &format!("Index({}, {:?})", idx.on_id, idx.key),
615                self.as_of_frontier.clone(),
616                self.until.clone(),
617            );
618
619            oks.stream = oks.stream.probe_with(&input_probe);
620
621            let (err_arranged, err_button) = traces.errs_mut().import_frontier_core(
622                outer,
623                &format!("ErrIndex({}, {:?})", idx.on_id, idx.key),
624                self.as_of_frontier.clone(),
625                self.until.clone(),
626            );
627
628            let bundle = match snapshot_mode {
629                SnapshotMode::Include => {
630                    let ok_arranged = oks
631                        .enter(self.scope)
632                        .with_start_signal(start_signal.clone());
633                    let err_arranged = err_arranged
634                        .enter(self.scope)
635                        .with_start_signal(start_signal);
636                    CollectionBundle::from_expressions(
637                        idx.key.clone(),
638                        ArrangementFlavor::Trace(idx_id, ok_arranged, err_arranged),
639                    )
640                }
641                SnapshotMode::Exclude => {
642                    // When we import an index without a snapshot, we have two balancing considerations:
643                    // - It's easy to filter out irrelevant batches from the stream, but hard to filter them out from an arrangement.
644                    //   (The `TraceFrontier` wrapper allows us to set an "until" frontier, but not a lower.)
645                    // - We do not actually need to reference the arrangement in this dataflow, since all operators that use the arrangement
646                    //   (joins, reduces, etc.) also require the snapshot data.
647                    // So: when the snapshot is excluded, we import only the (filtered) collection itself and ignore the arrangement.
648                    let oks = {
649                        let mut datums = DatumVec::new();
650                        let (permutation, _thinning) =
651                            permutation_for_arrangement(&idx.key, typ.arity());
652                        self.import_filtered_index_collection(
653                            oks,
654                            start_signal.clone(),
655                            move |k: DatumSeq, v: DatumSeq| {
656                                let temp_storage = RowArena::new();
657                                let mut datums_borrow = datums.borrow();
658                                k.extend_datums(&temp_storage, &mut datums_borrow, None);
659                                v.extend_datums(&temp_storage, &mut datums_borrow, None);
660                                SharedRow::pack(permutation.iter().map(|i| datums_borrow[*i]))
661                            },
662                        )
663                    };
664                    let errs = self.import_filtered_index_collection(
665                        err_arranged,
666                        start_signal,
667                        |e, _| e.clone(),
668                    );
669                    CollectionBundle::from_collections(oks, errs)
670                }
671            };
672            self.update_id(Id::Global(idx.on_id), bundle);
673            tokens.insert(
674                idx_id,
675                Rc::new((PressOnDrop(ok_button), PressOnDrop(err_button), token)),
676            );
677        } else {
678            panic!(
679                "import of index {} failed while building dataflow {}",
680                idx_id, self.dataflow_id
681            );
682        }
683    }
684}
685
686// This implementation block requires the scopes have the same timestamp as the trace manager.
687// That makes some sense, because we are hoping to deposit an arrangement in the trace manager.
688impl<'g> Context<'g, mz_repr::Timestamp> {
689    pub(crate) fn export_index(
690        &self,
691        compute_state: &mut ComputeState,
692        tokens: &BTreeMap<GlobalId, Rc<dyn std::any::Any>>,
693        dependency_ids: BTreeSet<GlobalId>,
694        idx_id: GlobalId,
695        idx: &IndexDesc<LirScalarExpr>,
696        output_probe: &MzProbeHandle<mz_repr::Timestamp>,
697    ) {
698        // put together tokens that belong to the export
699        let mut needed_tokens = Vec::new();
700        for dep_id in dependency_ids {
701            if let Some(token) = tokens.get(&dep_id) {
702                needed_tokens.push(Rc::clone(token));
703            }
704        }
705        let bundle = self.lookup_id(Id::Global(idx_id)).unwrap_or_else(|| {
706            panic!(
707                "Arrangement alarmingly absent! id: {:?}",
708                Id::Global(idx_id)
709            )
710        });
711
712        let key = &idx.key;
713        match bundle.arrangement(key) {
714            Some(ArrangementFlavor::Local(mut oks, mut errs)) => {
715                // NOTE: Do not give an exported arrangement a second reader that holds a trace
716                // handle, such as a `reduce`. Such a reader pins the shared spine's physical
717                // frontier at its own lagging progress, and `ArrangementManager::maintenance` can
718                // then no longer advance it, so batches pile up in `Spine::pending`. A cursor is
719                // only checked for straddling over pending batches, so an importing dataflow's
720                // `cursor_through` eventually panics with `upper` straddles batch. Watching
721                // `errs.stream` in `output_probe` does not help, and neither does discarding the
722                // reader's output. Stream-level readers like `as_collection` are unaffected. This is
723                // why error multiplicity is not collapsed here, leaving multiplicity that crosses
724                // an index boundary unbounded. TODO(CPU-209): bound it without a trace reader.
725
726                // Ensure that the frontier does not advance past the expiration time, if set.
727                // Otherwise, we might write down incorrect data.
728                if let Some(&expiration) = self.dataflow_expiration.as_option() {
729                    oks.stream = oks.stream.expire_stream_at(
730                        &format!("{}_export_index_oks", self.debug_name),
731                        expiration,
732                    );
733                    errs.stream = errs.stream.expire_stream_at(
734                        &format!("{}_export_index_errs", self.debug_name),
735                        expiration,
736                    );
737                }
738
739                oks.stream = oks.stream.probe_notify_with(vec![output_probe.clone()]);
740
741                // Attach logging of dataflow errors.
742                if let Some(logger) = compute_state.compute_logger.clone() {
743                    errs.stream = errs.stream.log_dataflow_errors(logger, idx_id);
744                }
745
746                compute_state.traces.set(
747                    idx_id,
748                    TraceBundle::new(oks.trace, errs.trace).with_drop(needed_tokens),
749                );
750            }
751            Some(ArrangementFlavor::Trace(gid, _, _)) => {
752                // Duplicate of existing arrangement with id `gid`, so
753                // just create another handle to that arrangement.
754                let trace = compute_state.traces.get(&gid).unwrap().clone();
755                compute_state.traces.set(idx_id, trace);
756            }
757            None => {
758                println!("collection available: {:?}", bundle.collection.is_none());
759                println!(
760                    "keys available: {:?}",
761                    bundle.arranged.keys().collect::<Vec<_>>()
762                );
763                panic!(
764                    "Arrangement alarmingly absent! id: {:?}, keys: {:?}",
765                    Id::Global(idx_id),
766                    key
767                );
768            }
769        };
770    }
771}
772
773// This implementation block requires the scopes have the same timestamp as the trace manager.
774// That makes some sense, because we are hoping to deposit an arrangement in the trace manager.
775impl<'g, T> Context<'g, T>
776where
777    T: RenderTimestamp,
778{
779    pub(crate) fn export_index_iterative<'outer>(
780        &self,
781        outer: Scope<'outer, mz_repr::Timestamp>,
782        compute_state: &mut ComputeState,
783        tokens: &BTreeMap<GlobalId, Rc<dyn std::any::Any>>,
784        dependency_ids: BTreeSet<GlobalId>,
785        idx_id: GlobalId,
786        idx: &IndexDesc<LirScalarExpr>,
787        output_probe: &MzProbeHandle<mz_repr::Timestamp>,
788    ) {
789        // put together tokens that belong to the export
790        let mut needed_tokens = Vec::new();
791        for dep_id in dependency_ids {
792            if let Some(token) = tokens.get(&dep_id) {
793                needed_tokens.push(Rc::clone(token));
794            }
795        }
796        let bundle = self.lookup_id(Id::Global(idx_id)).unwrap_or_else(|| {
797            panic!(
798                "Arrangement alarmingly absent! id: {:?}",
799                Id::Global(idx_id)
800            )
801        });
802
803        let key = &idx.key;
804        match bundle.arrangement(key) {
805            Some(ArrangementFlavor::Local(oks, errs)) => {
806                // TODO: The following as_collection/leave/arrange sequence could be optimized.
807                //   * Combine as_collection and leave into a single function.
808                //   * Use columnar to extract columns from the batches to implement leave.
809                let mut oks = oks
810                    .as_collection(|k, v| (k.to_row(), v.to_row()))
811                    .leave(outer)
812                    .mz_arrange::<
813                        ColumnationChunker<_>,
814                        RowRowBatcher<_, _>,
815                        RowRowBuilder<_, _>,
816                        _,
817                    >(
818                        "Arrange export iterative",
819                    );
820
821                let mut errs = errs
822                    .as_collection(|k, v| (k.clone(), v.clone()))
823                    .leave(outer)
824                    .mz_arrange::<ColumnationChunker<_>, ErrBatcher<_, _>, ErrBuilder<_, _>, _>(
825                        "Arrange export iterative err",
826                    );
827
828                // Ensure that the frontier does not advance past the expiration time, if set.
829                // Otherwise, we might write down incorrect data.
830                if let Some(&expiration) = self.dataflow_expiration.as_option() {
831                    oks.stream = oks.stream.expire_stream_at(
832                        &format!("{}_export_index_iterative_oks", self.debug_name),
833                        expiration,
834                    );
835                    errs.stream = errs.stream.expire_stream_at(
836                        &format!("{}_export_index_iterative_err", self.debug_name),
837                        expiration,
838                    );
839                }
840
841                oks.stream = oks.stream.probe_notify_with(vec![output_probe.clone()]);
842
843                // Attach logging of dataflow errors.
844                if let Some(logger) = compute_state.compute_logger.clone() {
845                    errs.stream = errs.stream.log_dataflow_errors(logger, idx_id);
846                }
847
848                compute_state.traces.set(
849                    idx_id,
850                    TraceBundle::new(oks.trace, errs.trace).with_drop(needed_tokens),
851                );
852            }
853            Some(ArrangementFlavor::Trace(gid, _, _)) => {
854                // Duplicate of existing arrangement with id `gid`, so
855                // just create another handle to that arrangement.
856                let trace = compute_state.traces.get(&gid).unwrap().clone();
857                compute_state.traces.set(idx_id, trace);
858            }
859            None => {
860                println!("collection available: {:?}", bundle.collection.is_none());
861                println!(
862                    "keys available: {:?}",
863                    bundle.arranged.keys().collect::<Vec<_>>()
864                );
865                panic!(
866                    "Arrangement alarmingly absent! id: {:?}, keys: {:?}",
867                    Id::Global(idx_id),
868                    key,
869                );
870            }
871        };
872    }
873}
874
875/// Information about bindings, tracked in `render_recursive_plan` and
876/// `render_plan`, to be passed to `render_letfree_plan`.
877///
878/// `render_letfree_plan` uses these to produce nice output (e.g., `With ...
879/// Returning ...`) for local bindings in the `mz_lir_mapping` output.
880enum BindingInfo {
881    Body { in_let: bool },
882    Let { id: LocalId, last: bool },
883    LetRec { id: LocalId, last: bool },
884}
885
886impl<'scope> Context<'scope, Product<mz_repr::Timestamp, PointStamp<u64>>> {
887    /// Renders a plan to a differential dataflow, producing the collection of results.
888    ///
889    /// This method allows for `plan` to contain [`RecBind`]s, and is planned
890    /// in the context of `level` pre-existing iteration coordinates.
891    ///
892    /// This method recursively descends [`RecBind`] values, establishing nested scopes for each
893    /// and establishing the appropriate recursive dependencies among the bound variables.
894    /// Once all [`RecBind`]s have been rendered it calls in to `render_plan` which will error if
895    /// further [`RecBind`]s are found.
896    ///
897    /// The method requires that all variables conclude with a physical representation that
898    /// contains a collection (i.e. a non-arrangement), and it will panic otherwise.
899    fn render_recursive_plan(
900        &mut self,
901        object_id: GlobalId,
902        level: usize,
903        plan: RenderPlan,
904        binding: BindingInfo,
905    ) -> CollectionBundle<'scope, Product<mz_repr::Timestamp, PointStamp<u64>>> {
906        for BindStage { lets, recs } in plan.binds {
907            // Render the let bindings in order.
908            let mut let_iter = lets.into_iter().peekable();
909            while let Some(LetBind { id, value }) = let_iter.next() {
910                let bundle =
911                    self.scope
912                        .clone()
913                        .region_named(&format!("Binding({:?})", id), |region| {
914                            let depends = value.depends();
915                            let last = let_iter.peek().is_none();
916                            let binding = BindingInfo::Let { id, last };
917                            self.enter_region(region, Some(&depends))
918                                .render_letfree_plan(object_id, value, binding)
919                                .leave_region(self.scope)
920                        });
921                let bundle = self.distinct_binding_errs(bundle);
922                self.insert_id(Id::Local(id), bundle);
923            }
924
925            let rec_ids: Vec<_> = recs.iter().map(|r| r.id).collect();
926
927            // Define variables for rec bindings.
928            // It is important that we only use the `Variable` until the object is bound.
929            // At that point, all subsequent uses should have access to the object itself.
930            let mut variables = BTreeMap::new();
931            for id in rec_ids.iter() {
932                use differential_dataflow::dynamic::feedback_summary;
933                let inner = feedback_summary::<u64>(level + 1, 1);
934                let (oks_v, oks_collection) =
935                    Variable::new(self.scope, Product::new(Default::default(), inner.clone()));
936                let (err_v, err_collection) =
937                    Variable::new(self.scope, Product::new(Default::default(), inner));
938
939                self.insert_id(
940                    Id::Local(*id),
941                    CollectionBundle::from_collections(oks_collection, err_collection),
942                );
943                variables.insert(Id::Local(*id), (oks_v, err_v));
944            }
945            // Now render each of the rec bindings.
946            let mut rec_iter = recs.into_iter().peekable();
947            while let Some(RecBind { id, value, limit }) = rec_iter.next() {
948                let last = rec_iter.peek().is_none();
949                let binding = BindingInfo::LetRec { id, last };
950                let bundle = self.render_recursive_plan(object_id, level + 1, value, binding);
951                // We need to ensure that the raw collection exists, but do not have enough information
952                // here to cause that to happen.
953                let (oks, mut err) = bundle.collection.clone().unwrap();
954                let oks = oks.into_vec();
955                // Collapses what forward reads see. `err_v` below feeds reads rendered before this
956                // binding and is collapsed separately; without this, a `Get` in a later rec binding
957                // or in the body resolves to the bundle stored here and compounds level over level,
958                // which is exactly what the collapse prevents for non-recursive bindings.
959                let bundle = self.distinct_binding_errs(bundle);
960                self.insert_id(Id::Local(id), bundle);
961                let (oks_v, err_v) = variables.remove(&Id::Local(id)).unwrap();
962
963                // Set oks variable to `oks` but consolidated to ensure iteration ceases at fixed point.
964                let mut oks = CollectionExt::consolidate_named::<KeyBatcher<_, _, _>>(
965                    oks,
966                    "LetRecConsolidation",
967                );
968
969                if let Some(limit) = limit {
970                    // We swallow the results of the `max_iter`th iteration, because
971                    // these results would go into the `max_iter + 1`th iteration.
972                    let (in_limit, over_limit) =
973                        oks.inner.branch_when(move |Product { inner: ps, .. }| {
974                            // The iteration number, or if missing a zero (as trailing zeros are truncated).
975                            let iteration_index = *ps.get(level).unwrap_or(&0);
976                            // The pointstamp starts counting from 0, so we need to add 1.
977                            iteration_index + 1 >= limit.max_iters.into()
978                        });
979                    oks = VecCollection::new(in_limit);
980                    if !limit.return_at_limit {
981                        err = err.concat(VecCollection::new(over_limit).map(move |_data| {
982                            DataflowErrorSer::from(EvalError::LetRecLimitExceeded(
983                                format!("{}", limit.max_iters.get()).into(),
984                            ))
985                        }));
986                    }
987                }
988
989                // Set err variable to the distinct elements of `err`.
990                // Distinctness is important, as we otherwise might add the same error each iteration,
991                // say if the limit of `oks` has an error. This would result in non-terminating rather
992                // than a clean report of the error. The trade-off is that we lose information about
993                // multiplicities of errors, but .. this seems to be the better call.
994                let err: KeyCollection<_, _, _> = err.into();
995                let errs = err
996                    .mz_arrange::<
997                        ColumnationChunker<_>,
998                        ErrBatcher<_, _>,
999                        ErrBuilder<_, _>,
1000                        ErrSpine<_, _>,
1001                    >("Arrange recursive err")
1002                    .mz_reduce_abelian::<_, ErrBuilder<_, _>, ErrSpine<_, _>, _>(
1003                        "Distinct recursive err",
1004                        move |_k, _s, t| t.push(((), Diff::ONE)),
1005                    )
1006                    .as_collection(|k, _| k.clone());
1007
1008                oks_v.set(oks);
1009                err_v.set(errs);
1010            }
1011            // Now extract each of the rec bindings into the outer scope.
1012            for id in rec_ids.into_iter() {
1013                let bundle = self.remove_id(Id::Local(id)).unwrap();
1014                let (oks, err) = bundle.collection.unwrap();
1015                let oks = oks.into_vec();
1016                self.insert_id(
1017                    Id::Local(id),
1018                    CollectionBundle::from_collections(
1019                        oks.leave_dynamic(level + 1),
1020                        err.leave_dynamic(level + 1),
1021                    ),
1022                );
1023            }
1024        }
1025
1026        self.render_letfree_plan(object_id, plan.body, binding)
1027    }
1028}
1029
1030impl<'scope, T: RenderTimestamp + MaybeBucketByTime> Context<'scope, T> {
1031    /// Renders a non-recursive plan to a differential dataflow, producing the collection of
1032    /// results.
1033    ///
1034    /// The return type reflects the uncertainty about the data representation, perhaps
1035    /// as a stream of data, perhaps as an arrangement, perhaps as a stream of batches.
1036    ///
1037    /// # Panics
1038    ///
1039    /// Panics if the given plan contains any [`RecBind`]s. Recursive plans must be rendered using
1040    /// `render_recursive_plan` instead.
1041    fn render_plan(
1042        &mut self,
1043        object_id: GlobalId,
1044        plan: RenderPlan,
1045    ) -> CollectionBundle<'scope, T> {
1046        let mut in_let = false;
1047        for BindStage { lets, recs } in plan.binds {
1048            assert!(recs.is_empty());
1049
1050            let mut let_iter = lets.into_iter().peekable();
1051            while let Some(LetBind { id, value }) = let_iter.next() {
1052                // if we encounter a single let, the body is in a let
1053                in_let = true;
1054                let bundle =
1055                    self.scope
1056                        .clone()
1057                        .region_named(&format!("Binding({:?})", id), |region| {
1058                            let depends = value.depends();
1059                            let last = let_iter.peek().is_none();
1060                            let binding = BindingInfo::Let { id, last };
1061                            self.enter_region(region, Some(&depends))
1062                                .render_letfree_plan(object_id, value, binding)
1063                                .leave_region(self.scope)
1064                        });
1065                let bundle = self.distinct_binding_errs(bundle);
1066                self.insert_id(Id::Local(id), bundle);
1067            }
1068        }
1069
1070        self.scope.clone().region_named("Main Body", |region| {
1071            let depends = plan.body.depends();
1072            self.enter_region(region, Some(&depends))
1073                .render_letfree_plan(object_id, plan.body, BindingInfo::Body { in_let })
1074                .leave_region(self.scope)
1075        })
1076    }
1077
1078    /// Collapses a binding's error multiplicities.
1079    ///
1080    /// Applied to every binding, not only the multiply-read ones. Gating on the reference count
1081    /// would be a pure optimization, since collapsing a binding one `Get` reads is harmless, and
1082    /// there is almost nothing to gate: `NormalizeLets` inlines single-use bindings, so the ones
1083    /// reaching rendering are shared. See [`CollectionBundle::distinct_errs`] for why the collapse
1084    /// is needed at all, and why a binding's definition is the place for it rather than the
1085    /// multi-input operators where the duplicate copies happen to meet again.
1086    fn distinct_binding_errs(
1087        &self,
1088        bundle: CollectionBundle<'scope, T>,
1089    ) -> CollectionBundle<'scope, T> {
1090        if ENABLE_ERROR_DISTINCT.get(&self.config_set) {
1091            bundle.distinct_errs()
1092        } else {
1093            bundle
1094        }
1095    }
1096
1097    /// Renders a let-free plan to a differential dataflow, producing the collection of results.
1098    fn render_letfree_plan(
1099        &self,
1100        object_id: GlobalId,
1101        plan: LetFreePlan,
1102        binding: BindingInfo,
1103    ) -> CollectionBundle<'scope, T> {
1104        let (mut nodes, root_id, topological_order) = plan.destruct();
1105
1106        // Rendered collections by their `LirId`.
1107        let mut collections = BTreeMap::new();
1108
1109        // Mappings to send along.
1110        // To save overhead, we'll only compute mappings when we need to,
1111        // which means things get gated behind options. Unfortunately, that means we
1112        // have several `Option<...>` types that are _all_ `Some` or `None` together,
1113        // but there's no convenient way to express the invariant.
1114        let should_compute_lir_metadata = self.compute_logger.is_some();
1115        let mut lir_mapping_metadata = if should_compute_lir_metadata {
1116            Some(Vec::with_capacity(nodes.len()))
1117        } else {
1118            None
1119        };
1120
1121        let mut topo_iter = topological_order.into_iter().peekable();
1122        while let Some(lir_id) = topo_iter.next() {
1123            let node = nodes.remove(&lir_id).unwrap();
1124
1125            // TODO(mgree) need ExprHumanizer in DataflowDescription to get nice column names
1126            // ActiveComputeState can't have a catalog reference, so we'll need to capture the names
1127            // in some other structure and have that structure impl ExprHumanizer
1128            let metadata = if should_compute_lir_metadata {
1129                let operator = node.expr.humanize(&DummyHumanizer);
1130
1131                // mark the last operator in topo order with any binding decoration
1132                let operator = if topo_iter.peek().is_none() {
1133                    match &binding {
1134                        BindingInfo::Body { in_let: true } => format!("Returning {operator}"),
1135                        BindingInfo::Body { in_let: false } => operator,
1136                        BindingInfo::Let { id, last: true } => {
1137                            format!("With {id} = {operator}")
1138                        }
1139                        BindingInfo::Let { id, last: false } => {
1140                            format!("{id} = {operator}")
1141                        }
1142                        BindingInfo::LetRec { id, last: true } => {
1143                            format!("With Recursive {id} = {operator}")
1144                        }
1145                        BindingInfo::LetRec { id, last: false } => {
1146                            format!("{id} = {operator}")
1147                        }
1148                    }
1149                } else {
1150                    operator
1151                };
1152
1153                let operator_id_start = self.scope.worker().peek_identifier();
1154                Some((operator, operator_id_start))
1155            } else {
1156                None
1157            };
1158
1159            let mut bundle = self.render_plan_expr(node.expr, &collections);
1160
1161            if let Some((operator, operator_id_start)) = metadata {
1162                let operator_id_end = self.scope.worker().peek_identifier();
1163                let operator_span = (operator_id_start, operator_id_end);
1164
1165                if let Some(lir_mapping_metadata) = &mut lir_mapping_metadata {
1166                    lir_mapping_metadata.push((
1167                        lir_id,
1168                        LirMetadata::new(operator, node.parent, node.nesting, operator_span),
1169                    ))
1170                }
1171            }
1172
1173            self.log_operator_hydration(&mut bundle, lir_id);
1174
1175            collections.insert(lir_id, bundle);
1176        }
1177
1178        if let Some(lir_mapping_metadata) = lir_mapping_metadata {
1179            self.log_lir_mapping(object_id, lir_mapping_metadata);
1180        }
1181
1182        collections
1183            .remove(&root_id)
1184            .expect("LetFreePlan invariant (1)")
1185    }
1186
1187    /// Renders a [`render_plan::Expr`], producing the collection of results.
1188    ///
1189    /// # Panics
1190    ///
1191    /// Panics if any of the expr's inputs is not found in `collections`.
1192    /// Callers must ensure that input nodes have been rendered previously.
1193    fn render_plan_expr(
1194        &self,
1195        expr: render_plan::Expr,
1196        collections: &BTreeMap<LirId, CollectionBundle<'scope, T>>,
1197    ) -> CollectionBundle<'scope, T> {
1198        use render_plan::Expr::*;
1199
1200        let expect_input = |id| {
1201            collections
1202                .get(&id)
1203                .cloned()
1204                .unwrap_or_else(|| panic!("missing input collection: {id}"))
1205        };
1206
1207        match expr {
1208            Constant { rows } => {
1209                // Produce both rows and errs to avoid conditional dataflow construction.
1210                let (rows, errs) = match rows {
1211                    Ok(rows) => (rows, Vec::new()),
1212                    Err(e) => (Vec::new(), vec![e]),
1213                };
1214
1215                // We should advance times in constant collections to start from `as_of`.
1216                let as_of_frontier = self.as_of_frontier.clone();
1217                let until = self.until.clone();
1218                let ok_collection = rows
1219                    .into_iter()
1220                    .filter_map(move |(row, mut time, diff)| {
1221                        time.advance_by(as_of_frontier.borrow());
1222                        if !until.less_equal(&time) {
1223                            Some((
1224                                row.0,
1225                                <T as Refines<mz_repr::Timestamp>>::to_inner(time),
1226                                diff,
1227                            ))
1228                        } else {
1229                            None
1230                        }
1231                    })
1232                    .to_stream(self.scope)
1233                    .as_collection();
1234
1235                let mut error_time: mz_repr::Timestamp = Timestamp::minimum();
1236                error_time.advance_by(self.as_of_frontier.borrow());
1237                let err_collection = errs
1238                    .into_iter()
1239                    .map(move |e| {
1240                        (
1241                            DataflowErrorSer::from(e),
1242                            <T as Refines<mz_repr::Timestamp>>::to_inner(error_time),
1243                            Diff::ONE,
1244                        )
1245                    })
1246                    .to_stream(self.scope)
1247                    .as_collection();
1248
1249                CollectionBundle::from_collections(ok_collection, err_collection)
1250            }
1251            Get { id, keys, plan } => {
1252                // Recover the collection from `self` and then apply `mfp` to it.
1253                // If `mfp` happens to be trivial, we can just return the collection.
1254                let mut collection = self
1255                    .lookup_id(id)
1256                    .unwrap_or_else(|| panic!("Get({:?}) not found at render time", id));
1257                match plan {
1258                    mz_compute_types::plan::GetPlan::PassArrangements => {
1259                        // Assert that each of `keys` are present in `collection`.
1260                        assert!(
1261                            keys.arranged
1262                                .iter()
1263                                .all(|(key, _, _)| collection.arranged.contains_key(key))
1264                        );
1265                        assert!(keys.raw <= collection.collection.is_some());
1266                        // Retain only those keys we want to import.
1267                        collection.arranged.retain(|key, _value| {
1268                            keys.arranged.iter().any(|(key2, _, _)| key2 == key)
1269                        });
1270                        collection
1271                    }
1272                    mz_compute_types::plan::GetPlan::Arrangement(key, row, mfp) => {
1273                        let (oks, errs) = collection.as_collection_core(
1274                            mfp,
1275                            Some((key, row)),
1276                            self.until.clone(),
1277                            &self.config_set,
1278                        );
1279                        CollectionBundle::from_collections(oks, errs)
1280                    }
1281                    mz_compute_types::plan::GetPlan::Collection(mfp) => {
1282                        let (oks, errs) = collection.as_collection_core(
1283                            mfp,
1284                            None,
1285                            self.until.clone(),
1286                            &self.config_set,
1287                        );
1288                        CollectionBundle::from_collections(oks, errs)
1289                    }
1290                }
1291            }
1292            Mfp {
1293                input,
1294                mfp,
1295                input_key_val,
1296            } => {
1297                let input = expect_input(input);
1298                // If `mfp` is non-trivial, we should apply it and produce a collection.
1299                if mfp.is_identity() {
1300                    input
1301                } else {
1302                    let (oks, errs) = input.as_collection_core(
1303                        mfp,
1304                        input_key_val,
1305                        self.until.clone(),
1306                        &self.config_set,
1307                    );
1308                    CollectionBundle::from_collections(oks, errs)
1309                }
1310            }
1311            FlatMap {
1312                input_key,
1313                input,
1314                exprs,
1315                func,
1316                mfp_after: mfp,
1317            } => {
1318                let input = expect_input(input);
1319                self.render_flat_map(input_key, input, exprs, func, mfp)
1320            }
1321            Join { inputs, plan } => {
1322                let inputs = inputs.into_iter().map(expect_input).collect();
1323                match plan {
1324                    mz_compute_types::plan::join::JoinPlan::Linear(linear_plan) => {
1325                        self.render_join(inputs, linear_plan)
1326                    }
1327                    mz_compute_types::plan::join::JoinPlan::Delta(delta_plan) => {
1328                        self.render_delta_join(inputs, delta_plan)
1329                    }
1330                }
1331            }
1332            Reduce {
1333                input_key,
1334                input,
1335                key_val_plan,
1336                plan,
1337                mfp_after,
1338                temporal_bucketing_strategy,
1339            } => {
1340                let input = expect_input(input);
1341                let mfp_option = (!mfp_after.is_identity()).then_some(mfp_after);
1342                self.render_reduce(
1343                    input_key,
1344                    input,
1345                    key_val_plan,
1346                    plan,
1347                    mfp_option,
1348                    temporal_bucketing_strategy,
1349                )
1350            }
1351            TopK {
1352                input,
1353                top_k_plan,
1354                temporal_bucketing_strategy,
1355            } => {
1356                let input = expect_input(input);
1357                self.render_topk(input, top_k_plan, temporal_bucketing_strategy)
1358            }
1359            Negate { input } => {
1360                let input = expect_input(input);
1361                let (oks, errs) = input
1362                    .collection
1363                    .clone()
1364                    .expect("Negate input must be an unarranged collection");
1365                CollectionBundle::from_edge(oks.negate(), errs)
1366            }
1367            Threshold {
1368                input,
1369                threshold_plan,
1370            } => {
1371                let input = expect_input(input);
1372                self.render_threshold(input, threshold_plan)
1373            }
1374            Union {
1375                inputs,
1376                consolidate_output,
1377                temporal_bucketing_strategies,
1378            } => {
1379                let mut oks = Vec::new();
1380                let mut errs = Vec::new();
1381                for (input, strategy) in inputs.into_iter().zip_eq(temporal_bucketing_strategies) {
1382                    let (os, es) = expect_input(input)
1383                        .collection
1384                        .clone()
1385                        .expect("Union input must be an unarranged collection");
1386                    // Apply per-input temporal bucketing. No-op for `Direct`.
1387                    // Only consolidating Unions carry non-`Direct` strategies;
1388                    // see the `Union` arm of `lower_mir_expr_stack_safe`.
1389                    let os = if matches!(strategy, ArrangementStrategy::TemporalBucketing)
1390                        && ENABLE_COMPUTE_TEMPORAL_BUCKETING.get(&self.config_set)
1391                    {
1392                        let summary: mz_repr::Timestamp = TEMPORAL_BUCKETING_SUMMARY
1393                            .get(&self.config_set)
1394                            .try_into()
1395                            .expect("must fit");
1396                        let os = os.into_vec();
1397                        CollectionEdge::Vec(T::maybe_apply_temporal_bucketing(
1398                            os.inner,
1399                            self.as_of_frontier.clone(),
1400                            summary,
1401                        ))
1402                    } else {
1403                        os
1404                    };
1405                    oks.push(os);
1406                    errs.push(es);
1407                }
1408                let oks = CollectionEdge::concat_many(self.scope, oks);
1409                let oks = if consolidate_output {
1410                    oks.consolidate_named("UnionConsolidation")
1411                } else {
1412                    oks
1413                };
1414                let errs = differential_dataflow::collection::concatenate(self.scope, errs);
1415                CollectionBundle::from_edge(oks, errs)
1416            }
1417            ArrangeBy {
1418                input_key,
1419                input,
1420                input_mfp,
1421                forms: keys,
1422                strategy,
1423            } => {
1424                let input = expect_input(input);
1425                input.ensure_collections(
1426                    keys,
1427                    input_key,
1428                    input_mfp,
1429                    self.as_of_frontier.clone(),
1430                    self.until.clone(),
1431                    &self.config_set,
1432                    strategy,
1433                )
1434            }
1435        }
1436    }
1437
1438    fn log_dataflow_global_id(&self, dataflow_index: usize, global_id: GlobalId) {
1439        if let Some(logger) = &self.compute_logger {
1440            logger.log(&ComputeEvent::DataflowGlobal(DataflowGlobal {
1441                dataflow_index,
1442                global_id,
1443            }));
1444        }
1445    }
1446
1447    fn log_lir_mapping(&self, global_id: GlobalId, mapping: Vec<(LirId, LirMetadata)>) {
1448        if let Some(logger) = &self.compute_logger {
1449            logger.log(&ComputeEvent::LirMapping(LirMapping { global_id, mapping }));
1450        }
1451    }
1452
1453    fn log_operator_hydration(&self, bundle: &mut CollectionBundle<'scope, T>, lir_id: LirId) {
1454        // A `CollectionBundle` can contain more than one collection, which makes it not obvious to
1455        // which we should attach the logging operator.
1456        //
1457        // We could attach to each collection and track the lower bound of output frontiers.
1458        // However, that would be of limited use because we expect all collections to hydrate at
1459        // roughly the same time: The `ArrangeBy` operator is not fueled, so as soon as it sees the
1460        // frontier of the unarranged collection advance, it will perform all work necessary to
1461        // also advance its own frontier. We don't expect significant delays between frontier
1462        // advancements of the unarranged and arranged collections, so attaching the logging
1463        // operator to any one of them should produce accurate results.
1464        //
1465        // If the `CollectionBundle` contains both unarranged and arranged representations it is
1466        // beneficial to attach the logging operator to one of the arranged representation to avoid
1467        // unnecessary cloning of data. The unarranged collection feeds into the arrangements, so
1468        // if we attached the logging operator to it, we would introduce a fork in its output
1469        // stream, which would necessitate that all output data is cloned. In contrast, we can hope
1470        // that the output streams of the arrangements don't yet feed into anything else, so
1471        // attaching a (pass-through) logging operator does not introduce a fork.
1472
1473        match bundle.arranged.values_mut().next() {
1474            Some(arrangement) => {
1475                use ArrangementFlavor::*;
1476
1477                match arrangement {
1478                    Local(a, _) => {
1479                        a.stream = self.log_operator_hydration_inner(a.stream.clone(), lir_id);
1480                    }
1481                    Trace(_, a, _) => {
1482                        a.stream = self.log_operator_hydration_inner(a.stream.clone(), lir_id);
1483                    }
1484                }
1485            }
1486            None => {
1487                let (oks, _) = bundle
1488                    .collection
1489                    .as_mut()
1490                    .expect("CollectionBundle invariant");
1491                match oks {
1492                    CollectionEdge::Vec(c) => {
1493                        let stream = self.log_operator_hydration_inner(c.inner.clone(), lir_id);
1494                        *c = stream.as_collection();
1495                    }
1496                    CollectionEdge::Columnar(c) => {
1497                        let stream = self.log_operator_hydration_inner(c.inner.clone(), lir_id);
1498                        *c = stream.as_collection();
1499                    }
1500                }
1501            }
1502        }
1503    }
1504
1505    fn log_operator_hydration_inner<D>(
1506        &self,
1507        stream: Stream<'scope, T, D>,
1508        lir_id: LirId,
1509    ) -> Stream<'scope, T, D>
1510    where
1511        D: timely::Container + Clone + 'static,
1512    {
1513        let Some(logger) = self.compute_logger.clone() else {
1514            return stream.clone(); // hydration logging disabled
1515        };
1516
1517        let export_ids = self.export_ids.clone();
1518
1519        // Convert the dataflow as-of into a frontier we can compare with input frontiers.
1520        //
1521        // We (somewhat arbitrarily) define operators in iterative scopes to be hydrated when their
1522        // frontier advances to an outer time that's greater than the `as_of`. Comparing
1523        // `refine(as_of) < input_frontier` would find the moment when the first iteration was
1524        // complete, which is not what we want. We want `refine(as_of + 1) <= input_frontier`
1525        // instead.
1526        let mut hydration_frontier = Antichain::new();
1527        for time in self.as_of_frontier.iter() {
1528            if let Some(time) = time.try_step_forward() {
1529                hydration_frontier.insert(Refines::to_inner(time));
1530            }
1531        }
1532
1533        let name = format!("LogOperatorHydration ({lir_id})");
1534        stream.unary_frontier(Pipeline, &name, |_cap, _info| {
1535            let mut hydrated = false;
1536
1537            for &export_id in &export_ids {
1538                logger.log(&ComputeEvent::OperatorHydration(OperatorHydration {
1539                    export_id,
1540                    lir_id,
1541                    hydrated,
1542                }));
1543            }
1544
1545            move |(input, frontier), output| {
1546                // Pass through inputs.
1547                input.for_each(|cap, data| {
1548                    output.session(&cap).give_container(data);
1549                });
1550
1551                if hydrated {
1552                    return;
1553                }
1554
1555                if PartialOrder::less_equal(&hydration_frontier.borrow(), &frontier.frontier()) {
1556                    hydrated = true;
1557
1558                    for &export_id in &export_ids {
1559                        logger.log(&ComputeEvent::OperatorHydration(OperatorHydration {
1560                            export_id,
1561                            lir_id,
1562                            hydrated,
1563                        }));
1564                    }
1565                }
1566            }
1567        })
1568    }
1569}
1570
1571#[allow(dead_code)] // Some of the methods on this trait are unused, but useful to have.
1572/// A timestamp type that can be used for operations within MZ's dataflow layer.
1573pub trait RenderTimestamp: MzTimestamp + Default + Refines<mz_repr::Timestamp> {
1574    /// The system timestamp component of the timestamp.
1575    ///
1576    /// This is useful for manipulating the system time, as when delaying
1577    /// updates for subsequent cancellation, as with monotonic reduction.
1578    fn system_time(&mut self) -> &mut mz_repr::Timestamp;
1579    /// Effects a system delay in terms of the timestamp summary.
1580    fn system_delay(delay: mz_repr::Timestamp) -> <Self as Timestamp>::Summary;
1581    /// The event timestamp component of the timestamp.
1582    fn event_time(&self) -> mz_repr::Timestamp;
1583    /// The event timestamp component of the timestamp, as a mutable reference.
1584    fn event_time_mut(&mut self) -> &mut mz_repr::Timestamp;
1585    /// Effects an event delay in terms of the timestamp summary.
1586    fn event_delay(delay: mz_repr::Timestamp) -> <Self as Timestamp>::Summary;
1587    /// Steps the timestamp back so that logical compaction to the output will
1588    /// not conflate `self` with any historical times.
1589    fn step_back(&self) -> Self;
1590}
1591
1592/// Apply temporal bucketing to a stream when the timestamp type supports it.
1593///
1594/// Sibling to [`RenderTimestamp`]: bucketing is an arrangement-time concern, not a
1595/// general property of a render timestamp, so the dispatch lives in its own trait.
1596/// Total-ordered timestamps perform real bucketing; partially-ordered timestamps
1597/// (e.g. `Product<…>` in iterative scopes) implement this as a no-op.
1598pub trait MaybeBucketByTime: Timestamp {
1599    fn maybe_apply_temporal_bucketing<'scope, D>(
1600        stream: StreamVec<'scope, Self, (D, Self, Diff)>,
1601        as_of: Antichain<mz_repr::Timestamp>,
1602        summary: mz_repr::Timestamp,
1603    ) -> VecCollection<'scope, Self, D, Diff>
1604    where
1605        D: differential_dataflow::ExchangeData
1606            + crate::typedefs::MzData
1607            + differential_dataflow::Hashable;
1608}
1609
1610impl RenderTimestamp for mz_repr::Timestamp {
1611    fn system_time(&mut self) -> &mut mz_repr::Timestamp {
1612        self
1613    }
1614    fn system_delay(delay: mz_repr::Timestamp) -> <Self as Timestamp>::Summary {
1615        delay
1616    }
1617    fn event_time(&self) -> mz_repr::Timestamp {
1618        *self
1619    }
1620    fn event_time_mut(&mut self) -> &mut mz_repr::Timestamp {
1621        self
1622    }
1623    fn event_delay(delay: mz_repr::Timestamp) -> <Self as Timestamp>::Summary {
1624        delay
1625    }
1626    fn step_back(&self) -> Self {
1627        self.saturating_sub(1)
1628    }
1629}
1630
1631impl MaybeBucketByTime for mz_repr::Timestamp {
1632    fn maybe_apply_temporal_bucketing<'scope, D>(
1633        stream: StreamVec<'scope, Self, (D, Self, Diff)>,
1634        as_of: Antichain<mz_repr::Timestamp>,
1635        summary: mz_repr::Timestamp,
1636    ) -> VecCollection<'scope, Self, D, Diff>
1637    where
1638        D: differential_dataflow::ExchangeData
1639            + crate::typedefs::MzData
1640            + differential_dataflow::Hashable,
1641    {
1642        stream
1643            .bucket::<CapacityContainerBuilder<_>>(as_of, summary)
1644            .as_collection()
1645    }
1646}
1647
1648impl RenderTimestamp for Product<mz_repr::Timestamp, PointStamp<u64>> {
1649    fn system_time(&mut self) -> &mut mz_repr::Timestamp {
1650        &mut self.outer
1651    }
1652    fn system_delay(delay: mz_repr::Timestamp) -> <Self as Timestamp>::Summary {
1653        Product::new(delay, Default::default())
1654    }
1655    fn event_time(&self) -> mz_repr::Timestamp {
1656        self.outer
1657    }
1658    fn event_time_mut(&mut self) -> &mut mz_repr::Timestamp {
1659        &mut self.outer
1660    }
1661    fn event_delay(delay: mz_repr::Timestamp) -> <Self as Timestamp>::Summary {
1662        Product::new(delay, Default::default())
1663    }
1664    fn step_back(&self) -> Self {
1665        // It is necessary to step back both coordinates of a product,
1666        // and when one is a `PointStamp` that also means all coordinates
1667        // of the pointstamp.
1668        let inner = self.inner.clone();
1669        let mut vec = inner.into_inner();
1670        for item in vec.iter_mut() {
1671            *item = item.saturating_sub(1);
1672        }
1673        Product::new(self.outer.saturating_sub(1), PointStamp::new(vec))
1674    }
1675}
1676
1677impl MaybeBucketByTime for Product<mz_repr::Timestamp, PointStamp<u64>> {
1678    fn maybe_apply_temporal_bucketing<'scope, D>(
1679        stream: StreamVec<'scope, Self, (D, Self, Diff)>,
1680        _as_of: Antichain<mz_repr::Timestamp>,
1681        _summary: mz_repr::Timestamp,
1682    ) -> VecCollection<'scope, Self, D, Diff>
1683    where
1684        D: differential_dataflow::ExchangeData
1685            + crate::typedefs::MzData
1686            + differential_dataflow::Hashable,
1687    {
1688        // TODO: Implement bucketing on outer timestamp for iterative scopes.
1689        stream.as_collection()
1690    }
1691}
1692
1693/// A signal that can be awaited by operators to suspend them prior to startup.
1694///
1695/// Creating a signal also yields a token, dropping of which causes the signal to fire.
1696///
1697/// `StartSignal` is designed to be usable by both async and sync Timely operators.
1698///
1699///  * Async operators can simply `await` it.
1700///  * Sync operators should register an [`ActivateOnDrop`] value via [`StartSignal::drop_on_fire`]
1701///    and then check `StartSignal::has_fired()` on each activation.
1702#[derive(Clone)]
1703pub(crate) struct StartSignal {
1704    /// A future that completes when the signal fires.
1705    ///
1706    /// The inner type is `Infallible` because no data is ever expected on this channel. Instead the
1707    /// signal is activated by dropping the corresponding `Sender`.
1708    fut: futures::future::Shared<oneshot::Receiver<Infallible>>,
1709    /// A weak reference to the token, to register drop-on-fire values.
1710    token_ref: Weak<RefCell<Box<dyn Any>>>,
1711}
1712
1713impl StartSignal {
1714    /// Create a new `StartSignal` and a corresponding token that activates the signal when
1715    /// dropped.
1716    pub fn new() -> (Self, Rc<dyn Any>) {
1717        let (tx, rx) = oneshot::channel::<Infallible>();
1718        let token: Rc<RefCell<Box<dyn Any>>> = Rc::new(RefCell::new(Box::new(tx)));
1719        let signal = Self {
1720            fut: rx.shared(),
1721            token_ref: Rc::downgrade(&token),
1722        };
1723        (signal, token)
1724    }
1725
1726    pub fn has_fired(&self) -> bool {
1727        self.token_ref.strong_count() == 0
1728    }
1729
1730    /// Returns a Send-safe future that completes when the signal fires.
1731    ///
1732    /// Unlike `StartSignal` itself, the returned future does not retain a reference to the token,
1733    /// so it cannot be used for `drop_on_fire` or `has_fired` checks.
1734    pub fn into_send_future(self) -> impl Future<Output = ()> + Send {
1735        use futures::FutureExt;
1736        self.fut.map(|_| ())
1737    }
1738
1739    pub fn drop_on_fire(&self, to_drop: Box<dyn Any>) {
1740        if let Some(token) = self.token_ref.upgrade() {
1741            let mut token = token.borrow_mut();
1742            let inner = std::mem::replace(&mut *token, Box::new(()));
1743            *token = Box::new((inner, to_drop));
1744        }
1745    }
1746}
1747
1748impl Future for StartSignal {
1749    type Output = ();
1750
1751    fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
1752        self.fut.poll_unpin(cx).map(|_| ())
1753    }
1754}
1755
1756/// Extension trait to attach a `StartSignal` to operator outputs.
1757pub(crate) trait WithStartSignal {
1758    /// Delays data and progress updates until the start signal has fired.
1759    ///
1760    /// Note that this operator needs to buffer all incoming data, so it has some memory footprint,
1761    /// depending on the amount and shape of its inputs.
1762    fn with_start_signal(self, signal: StartSignal) -> Self;
1763}
1764
1765impl<'scope, Tr> WithStartSignal for Arranged<'scope, Tr>
1766where
1767    Tr: TraceReader<Time: RenderTimestamp> + Clone,
1768{
1769    fn with_start_signal(self, signal: StartSignal) -> Self {
1770        Arranged {
1771            stream: self.stream.with_start_signal(signal),
1772            trace: self.trace,
1773        }
1774    }
1775}
1776
1777impl<'scope, T: Timestamp, D> WithStartSignal for Stream<'scope, T, D>
1778where
1779    D: timely::Container + Clone + 'static,
1780{
1781    fn with_start_signal(self, signal: StartSignal) -> Self {
1782        let activations = self.scope().activations();
1783        self.unary(Pipeline, "StartSignal", |_cap, info| {
1784            let token = Box::new(ActivateOnDrop::new((), info.address, activations));
1785            signal.drop_on_fire(token);
1786
1787            let mut stash = Vec::new();
1788
1789            move |input, output| {
1790                // Stash incoming updates as long as the start signal has not fired.
1791                if !signal.has_fired() {
1792                    input.for_each(|cap, data| stash.push((cap, std::mem::take(data))));
1793                    return;
1794                }
1795
1796                // Release any data we might still have stashed.
1797                for (cap, mut data) in std::mem::take(&mut stash) {
1798                    output.session(&cap).give_container(&mut data);
1799                }
1800
1801                // Pass through all remaining input data.
1802                input.for_each(|cap, data| {
1803                    output.session(&cap).give_container(data);
1804                });
1805            }
1806        })
1807    }
1808}
1809
1810/// Suppress progress messages for times before the given `as_of`.
1811///
1812/// This operator exists specifically to work around a memory spike we'd otherwise see when
1813/// hydrating arrangements (database-issues#6368). The memory spike happens because when the `arrange_core`
1814/// operator observes a frontier advancement without data it inserts an empty batch into the spine.
1815/// When it later inserts the snapshot batch into the spine, an empty batch is already there and
1816/// the spine initiates a merge of these batches, which requires allocating a new batch the size of
1817/// the snapshot batch.
1818///
1819/// The strategy to avoid the spike is to prevent the insertion of that initial empty batch by
1820/// ensuring that the first frontier advancement downstream `arrange_core` operators observe is
1821/// beyond the `as_of`, so the snapshot data has already been collected.
1822///
1823/// To ensure this, this operator needs to take two measures:
1824///  * Keep around a minimum capability until the input announces progress beyond the `as_of`.
1825///  * Reclock all updates emitted at times not beyond the `as_of` to the minimum time.
1826///
1827/// The second measure requires elaboration: If we wouldn't reclock snapshot updates, they might
1828/// still be upstream of `arrange_core` operators when those get to know about us dropping the
1829/// minimum capability. The in-flight snapshot updates would hold back the input frontiers of
1830/// `arrange_core` operators to the `as_of`, which would cause them to insert empty batches.
1831fn suppress_early_progress<'scope, T: Timestamp, D>(
1832    stream: Stream<'scope, T, D>,
1833    as_of: Antichain<T>,
1834) -> Stream<'scope, T, D>
1835where
1836    D: Data + timely::Container,
1837{
1838    stream.unary_frontier(Pipeline, "SuppressEarlyProgress", |default_cap, _info| {
1839        let mut early_cap = Some(default_cap);
1840
1841        move |(input, frontier), output| {
1842            input.for_each_time(|data_cap, data| {
1843                if as_of.less_than(data_cap.time()) {
1844                    let mut session = output.session(&data_cap);
1845                    for data in data {
1846                        session.give_container(data);
1847                    }
1848                } else {
1849                    let cap = early_cap.as_ref().expect("early_cap can't be dropped yet");
1850                    let mut session = output.session(&cap);
1851                    for data in data {
1852                        session.give_container(data);
1853                    }
1854                }
1855            });
1856
1857            if !PartialOrder::less_equal(&frontier.frontier(), &as_of.borrow()) {
1858                early_cap.take();
1859            }
1860        }
1861    })
1862}
1863
1864/// Extension trait for [`Stream`] to selectively limit progress.
1865trait LimitProgress<T: Timestamp> {
1866    /// Limit the progress of the stream until its frontier reaches the given `upper` bound. Expects
1867    /// the implementation to observe times in data, and release capabilities based on the probe's
1868    /// frontier, after applying `slack` to round up timestamps.
1869    ///
1870    /// The implementation of this operator is subtle to avoid regressions in the rest of the
1871    /// system. Specifically joins hold back compaction on the other side of the join, so we need to
1872    /// make sure we release capabilities as soon as possible. This is why we only limit progress
1873    /// for times before the `upper`, which is the time until which the source can distinguish
1874    /// updates at the time of rendering. Once we make progress to the `upper`, we need to release
1875    /// our capability.
1876    ///
1877    /// This isn't perfect, and can result in regressions if on of the inputs lags behind. We could
1878    /// consider using the join of the uppers, i.e, use lower bound upper of all available inputs.
1879    ///
1880    /// Once the input frontier reaches `[]`, the implementation must release any capability to
1881    /// allow downstream operators to release resources.
1882    ///
1883    /// The implementation should limit the number of pending times to `limit` if it is `Some` to
1884    /// avoid unbounded memory usage.
1885    ///
1886    /// * `handle` is a probe installed on the dataflow's outputs as late as possible, but before
1887    ///   any timestamp rounding happens (c.f., `REFRESH EVERY` materialized views).
1888    /// * `slack_ms` is the number of milliseconds to round up timestamps to.
1889    /// * `name` is a human-readable name for the operator.
1890    /// * `limit` is the maximum number of pending times to keep around.
1891    /// * `upper` is the upper bound of the stream's frontier until which the implementation can
1892    ///   retain a capability.
1893    fn limit_progress(
1894        self,
1895        handle: MzProbeHandle<T>,
1896        slack_ms: u64,
1897        limit: Option<usize>,
1898        upper: Antichain<T>,
1899        name: String,
1900    ) -> Self;
1901}
1902
1903// TODO: We could make this generic over a `T` that can be converted to and from a u64 millisecond
1904// number.
1905impl<'scope, D, R> LimitProgress<mz_repr::Timestamp>
1906    for StreamVec<'scope, mz_repr::Timestamp, (D, mz_repr::Timestamp, R)>
1907where
1908    D: Clone + 'static,
1909    R: Clone + 'static,
1910{
1911    fn limit_progress(
1912        self,
1913        handle: MzProbeHandle<mz_repr::Timestamp>,
1914        slack_ms: u64,
1915        limit: Option<usize>,
1916        upper: Antichain<mz_repr::Timestamp>,
1917        name: String,
1918    ) -> Self {
1919        let scope = self.scope();
1920        let stream =
1921            self.unary_frontier(Pipeline, &format!("LimitProgress({name})"), |_cap, info| {
1922                // Times that we've observed on our input.
1923                let mut pending_times: BTreeSet<mz_repr::Timestamp> = BTreeSet::new();
1924                // Capability for the lower bound of `pending_times`, if any.
1925                let mut retained_cap: Option<Capability<mz_repr::Timestamp>> = None;
1926
1927                let activator = scope.activator_for(info.address);
1928                handle.activate(activator.clone());
1929
1930                move |(input, frontier), output| {
1931                    input.for_each(|cap, data| {
1932                        for time in data
1933                            .iter()
1934                            .flat_map(|(_, time, _)| u64::from(time).checked_add(slack_ms))
1935                        {
1936                            // `slack_ms == 0` means no rounding; otherwise round up to the next
1937                            // multiple of `slack_ms`. Avoids a divide-by-zero panic when the
1938                            // operator is configured without slack.
1939                            let rounded_time = if slack_ms == 0 {
1940                                time
1941                            } else {
1942                                (time / slack_ms).saturating_add(1).saturating_mul(slack_ms)
1943                            };
1944                            if !upper.less_than(&rounded_time.into()) {
1945                                pending_times.insert(rounded_time.into());
1946                            }
1947                        }
1948                        output.session(&cap).give_container(data);
1949                        if retained_cap.as_ref().is_none_or(|c| {
1950                            !c.time().less_than(cap.time()) && !upper.less_than(cap.time())
1951                        }) {
1952                            retained_cap = Some(cap.retain(0));
1953                        }
1954                    });
1955
1956                    handle.with_frontier(|f| {
1957                        while pending_times
1958                            .first()
1959                            .map_or(false, |retained_time| !f.less_than(&retained_time))
1960                        {
1961                            let _ = pending_times.pop_first();
1962                        }
1963                    });
1964
1965                    while limit.map_or(false, |limit| pending_times.len() > limit) {
1966                        let _ = pending_times.pop_first();
1967                    }
1968
1969                    match (retained_cap.as_mut(), pending_times.first()) {
1970                        (Some(cap), Some(first)) => cap.downgrade(first),
1971                        (_, None) => retained_cap = None,
1972                        _ => {}
1973                    }
1974
1975                    if frontier.is_empty() {
1976                        retained_cap = None;
1977                        pending_times.clear();
1978                    }
1979
1980                    if !pending_times.is_empty() {
1981                        tracing::debug!(
1982                            name,
1983                            info.global_id,
1984                            pending_times = %PendingTimesDisplay(pending_times.iter().cloned()),
1985                            frontier = ?frontier.frontier().get(0),
1986                            probe = ?handle.with_frontier(|f| f.get(0).cloned()),
1987                            ?upper,
1988                            "pending times",
1989                        );
1990                    }
1991                }
1992            });
1993        stream
1994    }
1995}
1996
1997/// A formatter for an iterator of timestamps that displays the first element, and subsequently
1998/// the difference between timestamps.
1999struct PendingTimesDisplay<T>(T);
2000
2001impl<T> std::fmt::Display for PendingTimesDisplay<T>
2002where
2003    T: IntoIterator<Item = mz_repr::Timestamp> + Clone,
2004{
2005    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2006        let mut iter = self.0.clone().into_iter();
2007        write!(f, "[")?;
2008        if let Some(first) = iter.next() {
2009            write!(f, "{}", first)?;
2010            let mut last = u64::from(first);
2011            for time in iter {
2012                write!(f, ", +{}", u64::from(time) - last)?;
2013                last = u64::from(time);
2014            }
2015        }
2016        write!(f, "]")?;
2017        Ok(())
2018    }
2019}
2020
2021/// Helper to merge pairs of datum iterators into a row or split a datum iterator
2022/// into two rows, given the arity of the first component.
2023#[derive(Clone, Copy, Debug)]
2024struct Pairer {
2025    split_arity: usize,
2026}
2027
2028impl Pairer {
2029    /// Creates a pairer with knowledge of the arity of first component in the pair.
2030    fn new(split_arity: usize) -> Self {
2031        Self { split_arity }
2032    }
2033
2034    /// Merges a pair of datum iterators creating a `Row` instance.
2035    fn merge<'a, I1, I2>(&self, first: I1, second: I2) -> Row
2036    where
2037        I1: IntoIterator<Item = Datum<'a>>,
2038        I2: IntoIterator<Item = Datum<'a>>,
2039    {
2040        SharedRow::pack(first.into_iter().chain(second))
2041    }
2042
2043    /// Splits a datum iterator into a pair of `Row` instances.
2044    fn split<'a>(&self, datum_iter: impl IntoIterator<Item = Datum<'a>>) -> (Row, Row) {
2045        let mut datum_iter = datum_iter.into_iter();
2046        let mut row_builder = SharedRow::get();
2047        let first = row_builder.pack_using(datum_iter.by_ref().take(self.split_arity));
2048        let second = row_builder.pack_using(datum_iter);
2049        (first, second)
2050    }
2051}