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