Skip to main content

mz_compute_types/
plan.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//! An explicit representation of a rendering plan for provided dataflows.
11
12#![warn(missing_debug_implementations)]
13
14use std::collections::{BTreeMap, BTreeSet};
15
16use columnar::Columnar;
17use mz_expr::{
18    CollectionPlan, EvalError, Id, LetRecLimit, LocalId, MapFilterProject, MfpPlan,
19    OptimizedMirRelationExpr, SafeMfpPlan, TableFunc,
20};
21use mz_ore::metric;
22use mz_ore::metrics::MetricsRegistry;
23use mz_ore::metrics::raw::IntCounterVec;
24use mz_ore::soft_assert_eq_no_log;
25use mz_ore::str::Indent;
26use mz_repr::explain::text::text_string_at;
27use mz_repr::explain::{DummyHumanizer, ExplainConfig, ExprHumanizer, PlanRenderingContext};
28use mz_repr::optimize::OptimizerFeatures;
29use mz_repr::{Diff, GlobalId, StableRow, Timestamp};
30use serde::{Deserialize, Serialize};
31
32use crate::dataflows::DataflowDescription;
33use crate::plan::join::JoinPlan;
34use crate::plan::reduce::{KeyValPlan, ReducePlan};
35use crate::plan::scalar::LirScalarExpr;
36use crate::plan::threshold::ThresholdPlan;
37use crate::plan::top_k::TopKPlan;
38use crate::plan::transform::{Transform, TransformConfig};
39
40mod lowering;
41
42pub mod interpret;
43pub mod join;
44pub mod reduce;
45pub mod render_plan;
46pub mod scalar;
47pub mod threshold;
48pub mod top_k;
49pub mod transform;
50
51/// Metrics collected during MIR to LIR lowering.
52#[derive(Debug, Clone)]
53pub struct LoweringMetrics {
54    /// Counts non-`None` results of `MapFilterProject::literal_constraints` during lowering,
55    /// labeled by the call site (`"get"` or `"mfp"`).
56    literal_constraints: IntCounterVec,
57}
58
59impl LoweringMetrics {
60    /// Registers the lowering metrics into `registry`.
61    pub fn register_into(registry: &MetricsRegistry) -> Self {
62        Self {
63            literal_constraints: registry.register(metric!(
64                name: "mz_optimizer_lowering_literal_constraints_total",
65                help: "How often the MFP-based literal-constraint detector succeeded, by call site.",
66                var_labels: ["case"],
67            )),
68        }
69    }
70
71    /// Records that a `literal_constraints` call at `case` produced a usable constraint.
72    pub fn inc_literal_constraints(&self, case: &str) {
73        self.literal_constraints.with_label_values(&[case]).inc();
74    }
75}
76
77/// The forms in which an operator's output is available.
78///
79/// These forms may include "raw", meaning as a streamed collection, but also any
80/// number of "arranged" representations.
81///
82/// Each arranged representation is described by a `(to_key, permutation, thinning)`
83/// triple, built by `permutation_for_arrangement`. `to_key` (length `K`) are the key
84/// expressions over a row. `permutation` (length `A`, the raw/unthinned arity) maps
85/// each row column to its position in the `(key, value)` concatenation. `thinning`
86/// (length `M`) lists the row columns that form the value, in value order, so a value
87/// datum at concatenation position `c >= K` came from row column `thinning[c - K]`.
88///
89/// This triple is unrelated to `KeyValRowMapping`'s `(to_key, to_val, to_row)` fields
90/// despite the visual resemblance: `permutation` here plays the role of
91/// `KeyValRowMapping::to_row`, and `thinning` plays the role of
92/// `KeyValRowMapping::to_val`. Do not assume the same field order.
93#[derive(
94    Clone,
95    Debug,
96    Default,
97    Deserialize,
98    Eq,
99    Ord,
100    PartialEq,
101    PartialOrd,
102    Serialize
103)]
104pub struct AvailableCollections {
105    /// Whether the collection exists in unarranged form.
106    pub raw: bool,
107    /// The list of available arrangements, each a `(to_key, permutation, thinning)`
108    /// triple. See the struct-level documentation for field semantics.
109    pub arranged: Vec<(Vec<LirScalarExpr>, Vec<usize>, Vec<usize>)>,
110}
111
112impl AvailableCollections {
113    /// Represent a collection that has no arrangements.
114    pub fn new_raw() -> Self {
115        Self {
116            raw: true,
117            arranged: Vec::new(),
118        }
119    }
120
121    /// Represent a collection that is arranged in the specified ways.
122    pub fn new_arranged(arranged: Vec<(Vec<LirScalarExpr>, Vec<usize>, Vec<usize>)>) -> Self {
123        assert!(
124            !arranged.is_empty(),
125            "Invariant violated: at least one collection must exist"
126        );
127        Self {
128            raw: false,
129            arranged,
130        }
131    }
132
133    /// Get some arrangement, if one exists.
134    pub fn arbitrary_arrangement(&self) -> Option<&(Vec<LirScalarExpr>, Vec<usize>, Vec<usize>)> {
135        assert!(
136            self.raw || !self.arranged.is_empty(),
137            "Invariant violated: at least one collection must exist"
138        );
139        self.arranged.get(0)
140    }
141}
142
143/// How to render the arrangements requested by an `ArrangeBy`.
144///
145/// Decided during LIR lowering and consumed by the renderer. The variant says what the
146/// renderer will do, not what it knows about the input.
147#[derive(
148    Clone,
149    Copy,
150    Debug,
151    Deserialize,
152    Eq,
153    Ord,
154    PartialEq,
155    PartialOrd,
156    Serialize
157)]
158pub enum ArrangementStrategy {
159    /// Form arrangements directly from the input collection.
160    Direct,
161    /// Insert temporal bucketing in front of the arrangement, to delay future-stamped
162    /// updates (e.g., from `mz_now()` MFPs) until their bucket boundary releases them.
163    /// Honoured only when `ENABLE_COMPUTE_TEMPORAL_BUCKETING` is set; otherwise behaves like
164    /// `Direct`.
165    TemporalBucketing,
166}
167
168impl std::fmt::Display for ArrangementStrategy {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        match self {
171            ArrangementStrategy::Direct => write!(f, "Direct"),
172            ArrangementStrategy::TemporalBucketing => write!(f, "TemporalBucketing"),
173        }
174    }
175}
176
177/// An identifier for an LIR node.
178#[derive(
179    Clone,
180    Copy,
181    Debug,
182    Deserialize,
183    Eq,
184    Ord,
185    PartialEq,
186    PartialOrd,
187    Serialize,
188    Columnar
189)]
190pub struct LirId(u64);
191
192impl LirId {
193    fn as_u64(&self) -> u64 {
194        self.0
195    }
196}
197
198impl From<LirId> for u64 {
199    fn from(value: LirId) -> Self {
200        value.as_u64()
201    }
202}
203
204impl std::fmt::Display for LirId {
205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206        write!(f, "{}", self.0)
207    }
208}
209
210/// Version of the stable LIR serialization format.
211///
212/// Bump this when the serialized representation of [`LirRelationExpr`] or
213/// anything it transitively contains changes. The schema snapshot test in
214/// `tests/lir_schema.rs` enforces that the traced schema matches the
215/// checked-in `tests/snapshots/lir_v{LIR_VERSION}.json`.
216pub const LIR_VERSION: u64 = 1;
217
218pub use constant_rows_serde::ConstantRows;
219
220/// Serializes `LirRelationNode::Constant`'s rows through the named
221/// [`ConstantRows`] mirror enum instead of std `Result`.
222///
223/// The stable LIR schema registry maps each container name to a single
224/// format, and `Result` would clash with the differently instantiated
225/// `Result` in `LirScalarExpr::Literal`. The mirror has the same variant
226/// order as `Result`, so the encoded bytes are unchanged.
227mod constant_rows_serde {
228    use mz_expr::{EvalError, StableEvalError, StableEvalErrorRef};
229    use mz_repr::{Diff, StableRow, Timestamp};
230    use serde::{Deserialize, Deserializer, Serialize, Serializer};
231
232    /// The serialized form of `LirRelationNode::Constant`'s rows.
233    #[derive(Debug, Serialize, Deserialize)]
234    pub enum ConstantRows {
235        /// See `Result::Ok`.
236        Ok(Vec<(StableRow, Timestamp, Diff)>),
237        /// See `Result::Err`.
238        Err(StableEvalError),
239    }
240
241    /// Borrowing mirror of [`ConstantRows`], to serialize without cloning.
242    #[derive(Serialize)]
243    #[serde(rename = "ConstantRows")]
244    enum ConstantRowsRef<'a> {
245        Ok(&'a Vec<(StableRow, Timestamp, Diff)>),
246        Err(StableEvalErrorRef<'a>),
247    }
248
249    pub fn serialize<S: Serializer>(
250        rows: &Result<Vec<(StableRow, Timestamp, Diff)>, EvalError>,
251        serializer: S,
252    ) -> Result<S::Ok, S::Error> {
253        let mirror = match rows {
254            Ok(rows) => ConstantRowsRef::Ok(rows),
255            Err(err) => ConstantRowsRef::Err(StableEvalErrorRef(err)),
256        };
257        mirror.serialize(serializer)
258    }
259
260    pub fn deserialize<'de, D: Deserializer<'de>>(
261        deserializer: D,
262    ) -> Result<Result<Vec<(StableRow, Timestamp, Diff)>, EvalError>, D::Error> {
263        Ok(match ConstantRows::deserialize(deserializer)? {
264            ConstantRows::Ok(rows) => Ok(rows),
265            ConstantRows::Err(err) => Err(err.0),
266        })
267    }
268}
269
270/// A rendering plan with as much conditional logic as possible removed.
271#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
272pub struct LirRelationExpr {
273    /// A dataflow-local identifier.
274    pub lir_id: LirId,
275    /// The underlying operator.
276    pub node: LirRelationNode,
277}
278
279/// The actual AST node of the `LirRelationExpr`.
280#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
281pub enum LirRelationNode {
282    /// A collection containing a pre-determined collection.
283    Constant {
284        /// Explicit update triples for the collection.
285        #[serde(with = "constant_rows_serde")]
286        rows: Result<Vec<(StableRow, Timestamp, Diff)>, EvalError>,
287    },
288    /// A reference to a bound collection.
289    ///
290    /// This is commonly either an external reference to an existing source or
291    /// maintained arrangement, or an internal reference to a `Let` identifier.
292    Get {
293        /// A global or local identifier naming the collection.
294        id: Id,
295        /// Arrangements that will be available.
296        ///
297        /// The collection will also be loaded if available, which it will
298        /// not be for imported data, but which it may be for locally defined
299        /// data.
300        // TODO: Be more explicit about whether a collection is available,
301        // although one can always produce it from an arrangement, and it
302        // seems generally advantageous to do that instead (to avoid cloning
303        // rows, by using `mfp` first on borrowed data).
304        keys: AvailableCollections,
305        /// The actions to take when introducing the collection.
306        plan: GetPlan,
307    },
308    /// Binds `value` to `id`, and then results in `body` with that binding.
309    ///
310    /// This stage has the effect of sharing `value` across multiple possible
311    /// uses in `body`, and is the only mechanism we have for sharing collection
312    /// information across parts of a dataflow.
313    ///
314    /// The binding is not available outside of `body`.
315    Let {
316        /// The local identifier to be used, available to `body` as `Id::Local(id)`.
317        id: LocalId,
318        /// The collection that should be bound to `id`.
319        value: Box<LirRelationExpr>,
320        /// The collection that results, which is allowed to contain `Get` stages
321        /// that reference `Id::Local(id)`.
322        body: Box<LirRelationExpr>,
323    },
324    /// Binds `values` to `ids`, evaluates them potentially recursively, and returns `body`.
325    ///
326    /// All bindings are available to all bindings, and to `body`.
327    /// The contents of each binding are initially empty, and then updated through a sequence
328    /// of iterations in which each binding is updated in sequence, from the most recent values
329    /// of all bindings.
330    LetRec {
331        /// The local identifiers to be used, available to `body` as `Id::Local(id)`.
332        ids: Vec<LocalId>,
333        /// The collection that should be bound to `id`.
334        values: Vec<LirRelationExpr>,
335        /// Maximum number of iterations. See further info on the MIR `LetRec`.
336        limits: Vec<Option<LetRecLimit>>,
337        /// The collection that results, which is allowed to contain `Get` stages
338        /// that reference `Id::Local(id)`.
339        body: Box<LirRelationExpr>,
340    },
341    /// Map, Filter, and Project operators.
342    ///
343    /// This stage contains work that we would ideally like to fuse to other plan
344    /// stages, but for practical reasons cannot. For example: threshold, topk,
345    /// and sometimes reduce stages are not able to absorb this operator.
346    Mfp {
347        /// The input collection.
348        input: Box<LirRelationExpr>,
349        /// Linear operator to apply to each record.
350        mfp: MfpPlan<LirScalarExpr>,
351        /// Whether the input is from an arrangement, and if so,
352        /// whether we can seek to a specific value therein
353        input_key_val: Option<(Vec<LirScalarExpr>, Option<StableRow>)>,
354    },
355    /// A variable number of output records for each input record.
356    ///
357    /// This stage is a bit of a catch-all for logic that does not easily fit in
358    /// map stages. This includes table valued functions, but also functions of
359    /// multiple arguments, and functions that modify the sign of updates.
360    ///
361    /// This stage allows a `MapFilterProject` operator to be fused to its output,
362    /// and this can be very important as otherwise the output of `func` is just
363    /// appended to the input record, for as many outputs as it has. This has the
364    /// unpleasant default behavior of repeating potentially large records that
365    /// are being unpacked, producing quadratic output in those cases. Instead,
366    /// in these cases use a `mfp` member that projects away these large fields.
367    FlatMap {
368        /// The particular arrangement of the input we expect to use,
369        /// if any
370        input_key: Option<Vec<LirScalarExpr>>,
371        /// The input collection.
372        input: Box<LirRelationExpr>,
373        /// Expressions that for each row prepare the arguments to `func`.
374        exprs: Vec<LirScalarExpr>,
375        /// The variable-record emitting function.
376        func: TableFunc,
377        /// Linear operator to apply to each record produced by `func`.
378        mfp_after: MfpPlan<LirScalarExpr>,
379    },
380    /// A multiway relational equijoin, with fused map, filter, and projection.
381    ///
382    /// This stage performs a multiway join among `inputs`, using the equality
383    /// constraints expressed in `plan`. The plan also describes the implementation
384    /// strategy we will use, and any pushed down per-record work.
385    Join {
386        /// An ordered list of inputs that will be joined.
387        inputs: Vec<LirRelationExpr>,
388        /// Detailed information about the implementation of the join.
389        ///
390        /// This includes information about the implementation strategy, but also
391        /// any map, filter, project work that we might follow the join with, but
392        /// potentially pushed down into the implementation of the join.
393        plan: JoinPlan,
394    },
395    /// Aggregation by key.
396    Reduce {
397        /// The particular arrangement of the input we expect to use,
398        /// if any
399        input_key: Option<Vec<LirScalarExpr>>,
400        /// The input collection.
401        input: Box<LirRelationExpr>,
402        /// A plan for changing input records into key, value pairs.
403        key_val_plan: KeyValPlan,
404        /// A plan for performing the reduce.
405        ///
406        /// The implementation of reduction has several different strategies based
407        /// on the properties of the reduction, and the input itself. Please check
408        /// out the documentation for this type for more detail.
409        plan: ReducePlan,
410        /// An MFP that must be applied to results. The projection part of this
411        /// MFP must preserve the key for the reduction; otherwise, the results
412        /// become undefined. Additionally, the MFP is guaranteed to be free from
413        /// temporal predicates so that it can be readily evaluated.
414        mfp_after: SafeMfpPlan<LirScalarExpr>,
415        /// Strategy for forming the internal input arrangement built by `Reduce`
416        /// (materialized via `key_val_plan`).
417        ///
418        /// Set by the lowering from the input's `has_future_updates` flag. The
419        /// renderer applies it to the keyed `(key, val)` stream feeding the
420        /// reduce. See `render_reduce` for the rationale on why this is
421        /// plumbed through `Reduce` rather than handled at the arrangement site.
422        ///
423        /// Note: unrelated to the hash buckets used by hierarchical reductions
424        /// (e.g. `ReducePlan::Hierarchical`'s `buckets`), which are an internal
425        /// sharding scheme for `min`/`max`-style aggregations. Here "bucketing"
426        /// refers exclusively to temporal (time-domain) bucketing of
427        /// future-stamped updates.
428        temporal_bucketing_strategy: ArrangementStrategy,
429    },
430    /// Key-based "Top K" operator, retaining the first K records in each group.
431    TopK {
432        /// The input collection.
433        input: Box<LirRelationExpr>,
434        /// A plan for performing the Top-K.
435        ///
436        /// The implementation of reduction has several different strategies based
437        /// on the properties of the reduction, and the input itself. Please check
438        /// out the documentation for this type for more detail.
439        top_k_plan: TopKPlan,
440        /// Strategy for bucketing the input collection ahead of the Top-K operator.
441        ///
442        /// Set by the lowering from the input's `has_future_updates` flag. The
443        /// renderer applies it to the per-row input stream at the top of
444        /// `render_topk`, covering all three `TopKPlan` arms uniformly. See
445        /// `LirRelationNode::Reduce::temporal_bucketing_strategy` for the underlying
446        /// convention.
447        temporal_bucketing_strategy: ArrangementStrategy,
448    },
449    /// Inverts the sign of each update.
450    Negate {
451        /// The input collection.
452        input: Box<LirRelationExpr>,
453    },
454    /// Filters records that accumulate negatively.
455    ///
456    /// Although the operator suppresses updates, it is a stateful operator taking
457    /// resources proportional to the number of records with non-zero accumulation.
458    Threshold {
459        /// The input collection.
460        input: Box<LirRelationExpr>,
461        /// A plan for performing the threshold.
462        ///
463        /// The implementation of reduction has several different strategies based
464        /// on the properties of the reduction, and the input itself. Please check
465        /// out the documentation for this type for more detail.
466        threshold_plan: ThresholdPlan,
467    },
468    /// Adds the contents of the input collections.
469    ///
470    /// Importantly, this is *multiset* union, so the multiplicities of records will
471    /// add. This is in contrast to *set* union, where the multiplicities would be
472    /// capped at one. A set union can be formed with `Union` followed by `Reduce`
473    /// implementing the "distinct" operator.
474    Union {
475        /// The input collections
476        inputs: Vec<LirRelationExpr>,
477        /// Whether to consolidate the output, e.g., cancel negated records.
478        consolidate_output: bool,
479        /// Per-input bucketing strategies. Lockstep with `inputs`: index `i` is the
480        /// strategy applied to `inputs[i]` before concatenation.
481        ///
482        /// Set by the lowering from each input's `has_future_updates` flag. Only
483        /// consolidating Unions (`consolidate_output: true`) carry non-`Direct`
484        /// entries, because bucketing only pays off ahead of a consolidating
485        /// downstream operator. See `LirRelationNode::Reduce::temporal_bucketing_strategy`
486        /// for the underlying convention.
487        temporal_bucketing_strategies: Vec<ArrangementStrategy>,
488    },
489    /// The `input` plan, but with additional arrangements.
490    ///
491    /// This operator does not change the logical contents of `input`, but ensures
492    /// that certain arrangements are available in the results. This operator can
493    /// be important for e.g. the `Join` stage which benefits from multiple arrangements
494    /// or to cap a `LirRelationExpr` so that indexes can be exported.
495    ArrangeBy {
496        /// The key that must be used to access the input.
497        input_key: Option<Vec<LirScalarExpr>>,
498        /// The input collection.
499        input: Box<LirRelationExpr>,
500        /// The MFP that must be applied to the input.
501        input_mfp: MfpPlan<LirScalarExpr>,
502        /// A list of arrangement keys, and possibly a raw collection,
503        /// that will be added to those of the input. Does not include
504        /// any other existing arrangements.
505        forms: AvailableCollections,
506        /// How the renderer should form the arrangements requested by `forms`.
507        strategy: ArrangementStrategy,
508    },
509}
510
511impl LirRelationNode {
512    /// Iterates through references to child expressions.
513    pub fn children(&self) -> impl Iterator<Item = &LirRelationExpr> {
514        let mut first = None;
515        let mut second = None;
516        let mut rest = None;
517        let mut last = None;
518
519        use LirRelationNode::*;
520        match self {
521            Constant { .. } | Get { .. } => (),
522            Let { value, body, .. } => {
523                first = Some(&**value);
524                second = Some(&**body);
525            }
526            LetRec { values, body, .. } => {
527                rest = Some(values);
528                last = Some(&**body);
529            }
530            Mfp { input, .. }
531            | FlatMap { input, .. }
532            | Reduce { input, .. }
533            | TopK { input, .. }
534            | Negate { input, .. }
535            | Threshold { input, .. }
536            | ArrangeBy { input, .. } => {
537                first = Some(&**input);
538            }
539            Join { inputs, .. } | Union { inputs, .. } => {
540                rest = Some(inputs);
541            }
542        }
543
544        first
545            .into_iter()
546            .chain(second)
547            .chain(rest.into_iter().flatten())
548            .chain(last)
549    }
550
551    /// Iterates through mutable references to child expressions.
552    pub fn children_mut(&mut self) -> impl Iterator<Item = &mut LirRelationExpr> {
553        let mut first = None;
554        let mut second = None;
555        let mut rest = None;
556        let mut last = None;
557
558        use LirRelationNode::*;
559        match self {
560            Constant { .. } | Get { .. } => (),
561            Let { value, body, .. } => {
562                first = Some(&mut **value);
563                second = Some(&mut **body);
564            }
565            LetRec { values, body, .. } => {
566                rest = Some(values);
567                last = Some(&mut **body);
568            }
569            Mfp { input, .. }
570            | FlatMap { input, .. }
571            | Reduce { input, .. }
572            | TopK { input, .. }
573            | Negate { input, .. }
574            | Threshold { input, .. }
575            | ArrangeBy { input, .. } => {
576                first = Some(&mut **input);
577            }
578            Join { inputs, .. } | Union { inputs, .. } => {
579                rest = Some(inputs);
580            }
581        }
582
583        first
584            .into_iter()
585            .chain(second)
586            .chain(rest.into_iter().flatten())
587            .chain(last)
588    }
589}
590
591impl LirRelationNode {
592    /// Attach an `lir_id` to a `LirRelationNode` to make a complete `LirRelationExpr`.
593    pub fn as_plan(self, lir_id: LirId) -> LirRelationExpr {
594        LirRelationExpr { lir_id, node: self }
595    }
596}
597
598impl LirRelationExpr {
599    /// Pretty-print this [LirRelationExpr] to a string.
600    pub fn pretty(&self) -> String {
601        let config = ExplainConfig::default();
602        self.debug_explain(&config, None)
603    }
604
605    /// Pretty-print this [LirRelationExpr] to a string using a custom
606    /// [ExplainConfig] and an optionally provided [ExprHumanizer].
607    /// This is intended for debugging and tests, not users.
608    pub fn debug_explain(
609        &self,
610        config: &ExplainConfig,
611        humanizer: Option<&dyn ExprHumanizer>,
612    ) -> String {
613        text_string_at(self, || PlanRenderingContext {
614            indent: Indent::default(),
615            humanizer: humanizer.unwrap_or(&DummyHumanizer),
616            annotations: BTreeMap::default(),
617            config,
618            ambiguous_ids: BTreeSet::default(),
619        })
620    }
621}
622
623/// How a `Get` stage will be rendered.
624#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
625pub enum GetPlan {
626    /// Simply pass input arrangements on to the next stage.
627    PassArrangements,
628    /// Using the supplied key, optionally seek the row, and apply the MFP.
629    Arrangement(
630        Vec<LirScalarExpr>,
631        Option<StableRow>,
632        MfpPlan<LirScalarExpr>,
633    ),
634    /// Scan the input collection (unarranged) and apply the MFP.
635    Collection(MfpPlan<LirScalarExpr>),
636}
637
638impl LirRelationExpr {
639    /// Convert the dataflow description into one that uses render plans.
640    #[mz_ore::instrument(
641        target = "optimizer",
642        level = "debug",
643        fields(path.segment = "finalize_dataflow")
644    )]
645    pub fn finalize_dataflow(
646        desc: DataflowDescription<OptimizedMirRelationExpr>,
647        features: &OptimizerFeatures,
648        metrics: Option<&LoweringMetrics>,
649    ) -> Result<DataflowDescription<Self>, String> {
650        fail::fail_point!("finalize_dataflow");
651
652        // First, we lower the dataflow description from MIR to LIR. Lowering
653        // also moves common parts of the MFPs pushed onto each source's reads into the source
654        // itself (see `Context::refine_source_mfps`).
655        let mut dataflow = Self::lower_dataflow(desc, features, metrics)?;
656
657        // Note: `consolidate_output` for `Union` and per-input
658        // `temporal_bucketing_strategies` are decided at lowering time (see the
659        // `Union` arm of `lower_mir_expr_stack_safe`). The pre-existing
660        // `refine_union_negate_consolidation` pass — which used to flip
661        // `consolidate_output` to `true` for Unions with a `Negate` child — has
662        // been folded into the lowering, since lowering is the only point where
663        // the bucketing decision (which depends on `has_future_updates`) is
664        // available.
665
666        if dataflow.is_single_time() {
667            // The relaxation of the `must_consolidate` flag performs an LIR-based
668            // analysis and transform under checked recursion. By a similar argument
669            // made in `from_mir`, we do not expect the recursion limit to be hit.
670            // However, if that happens, we propagate an error to the caller.
671            // To apply the transform, we first obtain monotonic source and index
672            // global IDs and add them to a `TransformConfig` instance.
673            let monotonic_ids = dataflow
674                .source_imports
675                .iter()
676                .filter_map(|(id, source_import)| source_import.monotonic.then_some(*id))
677                .chain(
678                    dataflow
679                        .index_imports
680                        .iter()
681                        .filter_map(|(_id, index_import)| {
682                            if index_import.monotonic {
683                                Some(index_import.desc.on_id)
684                            } else {
685                                None
686                            }
687                        }),
688                )
689                .collect::<BTreeSet<_>>();
690
691            let config = TransformConfig { monotonic_ids };
692            Self::refine_single_time_consolidation(&mut dataflow, &config)?;
693
694            // For non-recursive delta joins in single-time dataflows, only the delta path for the
695            // first relation produces updates: the other paths discard updates at the as-of, which
696            // is the only time present. We keep just that path and, where the first input's
697            // arrangement existed solely to seed it, drop the arrangement and consume the input as a
698            // raw collection. This requires rewriting the path's initial closure to address the raw
699            // row layout rather than the arranged `(key, value)` layout.
700            for build_desc in dataflow.objects_to_build.iter_mut() {
701                // Worklist of plan nodes. `LetRec` bodies are explored but `LetRec` values are not,
702                // which excludes recursive (WMR) joins from this transform.
703                let mut todo = vec![&mut build_desc.plan];
704                while let Some(expr) = todo.pop() {
705                    match &mut expr.node {
706                        // TODO: also handle binary differential joins, which can likewise shed a
707                        // bespoke arrangement on their first input.
708                        LirRelationNode::Join {
709                            inputs,
710                            plan: JoinPlan::Delta(plan),
711                        } => {
712                            // Only the first relation's path survives at a single time.
713                            plan.path_plans.truncate(1);
714
715                            let source_relation = plan.path_plans[0].source_relation;
716                            // Replace the source input's bespoke arrangement with a raw collection,
717                            // but only when the surviving path's source is fed by an `ArrangeBy`
718                            // that exists solely to build that arrangement. A source backed directly
719                            // by an arranged import has no `ArrangeBy` node here, so this guard skips
720                            // it and the path keeps reading it arranged.
721                            if let Some(source_key) = plan.path_plans[0].source_key.clone() {
722                                if let LirRelationNode::ArrangeBy { forms, .. } =
723                                    &mut inputs[source_relation].node
724                                {
725                                    // Drop arrangement forms other than the source key, which the
726                                    // remaining path no longer needs.
727                                    forms.arranged.retain(|(key, _, _)| key == &source_key);
728                                    if let Some((to_key, permutation, thinning)) =
729                                        forms.arranged.pop()
730                                    {
731                                        // Make the input a raw collection and unset the source key.
732                                        // Clearing every arrangement form is safe: `truncate(1)`
733                                        // already dropped the sibling paths that were the only other
734                                        // consumers, and this `ArrangeBy` is private to this join
735                                        // input. What remains is the input's raw collection.
736                                        forms.raw = true;
737                                        forms.arranged.clear();
738                                        plan.path_plans[0].source_key = None;
739
740                                        // The initial closure addresses the arranged `(key, value)`
741                                        // layout: columns `[0, K)` are key datums and columns
742                                        // `[K, K + M)` are the thinned value datums. We rewrite it to
743                                        // address the raw row instead.
744                                        //
745                                        // `to_key` (length `K`) are the key expressions over a row.
746                                        // `permutation` (length `A`, the raw arity) maps each row
747                                        // column to its position in the `(key, value)` concatenation.
748                                        // `thinning` (length `M`) lists the row columns that form the
749                                        // value.
750                                        let key_len = to_key.len();
751                                        let row_arity = permutation.len();
752                                        let closure = &mut plan.path_plans[0].initial_closure;
753
754                                        // Step 1: rewrite `ready_equivalences`, which reference the
755                                        // arranged layout. A key column becomes its defining
756                                        // expression. A value column becomes the row column it was
757                                        // projected from.
758                                        for class in closure.ready_equivalences.iter_mut() {
759                                            for expr in class.iter_mut() {
760                                                let mut todo = vec![expr];
761                                                while let Some(expr) = todo.pop() {
762                                                    if let LirScalarExpr::Column(c, _) = expr {
763                                                        if let Some(key_expr) = to_key.get(*c) {
764                                                            *expr = key_expr.clone();
765                                                        } else {
766                                                            *c = thinning[*c - key_len];
767                                                        }
768                                                    } else {
769                                                        todo.extend(expr.children_mut());
770                                                    }
771                                                }
772                                            }
773                                        }
774
775                                        // Step 2: rewrite the `before` MFP. Starting from a raw row,
776                                        // materialize the key datums and project to the arranged
777                                        // `(key, value)` layout the original MFP expects, then apply
778                                        // it.
779                                        let (m, f, p) = closure.before.as_map_filter_project();
780                                        let mfp = MapFilterProject::new(row_arity)
781                                            .map(to_key)
782                                            .project(
783                                                (row_arity..row_arity + key_len).chain(thinning),
784                                            )
785                                            .map(m)
786                                            .filter(f)
787                                            .project(p);
788                                        closure.before =
789                                            mfp.into_plan().unwrap().into_nontemporal().unwrap();
790                                    }
791                                }
792                            }
793
794                            todo.extend(inputs.iter_mut());
795                        }
796                        LirRelationNode::LetRec { body, .. } => {
797                            todo.push(body);
798                        }
799                        x => {
800                            todo.extend(x.children_mut());
801                        }
802                    }
803                }
804            }
805        }
806
807        soft_assert_eq_no_log!(dataflow.check_invariants(), Ok(()));
808
809        mz_repr::explain::trace_plan(&dataflow);
810
811        Ok(dataflow)
812    }
813
814    /// Lowers the dataflow description from MIR to LIR. To this end, the
815    /// method collects all available arrangements and based on this information
816    /// creates plans for every object to be built for the dataflow.
817    #[mz_ore::instrument(
818        target = "optimizer",
819        level = "debug",
820        fields(path.segment ="mir_to_lir")
821    )]
822    fn lower_dataflow(
823        desc: DataflowDescription<OptimizedMirRelationExpr>,
824        features: &OptimizerFeatures,
825        metrics: Option<&LoweringMetrics>,
826    ) -> Result<DataflowDescription<Self>, String> {
827        let context = lowering::Context::new(desc.debug_name.clone(), features, metrics);
828        let dataflow = context.lower(desc)?;
829
830        mz_repr::explain::trace_plan(&dataflow);
831
832        Ok(dataflow)
833    }
834
835    /// Refines the plans of objects to be built as part of a single-time `dataflow` to relax
836    /// the setting of the `must_consolidate` attribute of monotonic operators, if necessary,
837    /// whenever the input is deemed to be physically monotonic.
838    #[mz_ore::instrument(
839        target = "optimizer",
840        level = "debug",
841        fields(path.segment = "refine_single_time_consolidation")
842    )]
843    fn refine_single_time_consolidation(
844        dataflow: &mut DataflowDescription<Self>,
845        config: &TransformConfig,
846    ) -> Result<(), String> {
847        // We should only reach here if we have a one-shot SELECT query, i.e.,
848        // a single-time dataflow.
849        assert!(dataflow.is_single_time());
850
851        let transform = transform::RelaxMustConsolidate;
852        for build_desc in dataflow.objects_to_build.iter_mut() {
853            transform
854                .transform(config, &mut build_desc.plan)
855                .map_err(|_| "Maximum recursion limit error in consolidation relaxation.")?;
856        }
857        mz_repr::explain::trace_plan(dataflow);
858        Ok(())
859    }
860}
861
862impl CollectionPlan for LirRelationNode {
863    fn depends_on_into(&self, out: &mut BTreeSet<GlobalId>) {
864        match self {
865            LirRelationNode::Constant { rows: _ } => (),
866            LirRelationNode::Get {
867                id,
868                keys: _,
869                plan: _,
870            } => match id {
871                Id::Global(id) => {
872                    out.insert(*id);
873                }
874                Id::Local(_) => (),
875            },
876            LirRelationNode::Let { id: _, value, body } => {
877                value.depends_on_into(out);
878                body.depends_on_into(out);
879            }
880            LirRelationNode::LetRec {
881                ids: _,
882                values,
883                limits: _,
884                body,
885            } => {
886                for value in values.iter() {
887                    value.depends_on_into(out);
888                }
889                body.depends_on_into(out);
890            }
891            LirRelationNode::Join { inputs, plan: _ }
892            | LirRelationNode::Union {
893                inputs,
894                consolidate_output: _,
895                temporal_bucketing_strategies: _,
896            } => {
897                for input in inputs {
898                    input.depends_on_into(out);
899                }
900            }
901            LirRelationNode::Mfp {
902                input,
903                mfp: _,
904                input_key_val: _,
905            }
906            | LirRelationNode::FlatMap {
907                input_key: _,
908                input,
909                exprs: _,
910                func: _,
911                mfp_after: _,
912            }
913            | LirRelationNode::ArrangeBy {
914                input_key: _,
915                input,
916                input_mfp: _,
917                forms: _,
918                strategy: _,
919            }
920            | LirRelationNode::Reduce {
921                input_key: _,
922                input,
923                key_val_plan: _,
924                plan: _,
925                mfp_after: _,
926                temporal_bucketing_strategy: _,
927            }
928            | LirRelationNode::TopK {
929                input,
930                top_k_plan: _,
931                temporal_bucketing_strategy: _,
932            }
933            | LirRelationNode::Negate { input }
934            | LirRelationNode::Threshold {
935                input,
936                threshold_plan: _,
937            } => {
938                input.depends_on_into(out);
939            }
940        }
941    }
942}
943
944impl CollectionPlan for LirRelationExpr {
945    fn depends_on_into(&self, out: &mut BTreeSet<GlobalId>) {
946        self.node.depends_on_into(out);
947    }
948}
949
950/// Returns bucket sizes, descending, suitable for hierarchical decomposition of an operator, based
951/// on the expected number of rows that will have the same group key.
952fn bucketing_of_expected_group_size(expected_group_size: Option<u64>) -> Vec<u64> {
953    // NOTE(vmarcos): The fan-in of 16 defined below is used in the tuning advice built-in view
954    // mz_introspection.mz_expected_group_size_advice.
955    let mut buckets = vec![];
956    let mut current = 16;
957
958    // Plan for 4B records in the expected case if the user didn't specify a group size.
959    let limit = expected_group_size.unwrap_or(4_000_000_000);
960
961    // Distribute buckets in powers of 16, so that we can strike a balance between how many inputs
962    // each layer gets from the preceding layer, while also limiting the number of layers.
963    while current < limit {
964        buckets.push(current);
965        current = current.saturating_mul(16);
966    }
967
968    buckets.reverse();
969    buckets
970}