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