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