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, 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;
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/// A rendering plan with as much conditional logic as possible removed.
211#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
212pub struct LirRelationExpr {
213 /// A dataflow-local identifier.
214 pub lir_id: LirId,
215 /// The underlying operator.
216 pub node: LirRelationNode,
217}
218
219/// The actual AST node of the `LirRelationExpr`.
220#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
221pub enum LirRelationNode {
222 /// A collection containing a pre-determined collection.
223 Constant {
224 /// Explicit update triples for the collection.
225 rows: Result<Vec<(Row, Timestamp, Diff)>, EvalError>,
226 },
227 /// A reference to a bound collection.
228 ///
229 /// This is commonly either an external reference to an existing source or
230 /// maintained arrangement, or an internal reference to a `Let` identifier.
231 Get {
232 /// A global or local identifier naming the collection.
233 id: Id,
234 /// Arrangements that will be available.
235 ///
236 /// The collection will also be loaded if available, which it will
237 /// not be for imported data, but which it may be for locally defined
238 /// data.
239 // TODO: Be more explicit about whether a collection is available,
240 // although one can always produce it from an arrangement, and it
241 // seems generally advantageous to do that instead (to avoid cloning
242 // rows, by using `mfp` first on borrowed data).
243 keys: AvailableCollections,
244 /// The actions to take when introducing the collection.
245 plan: GetPlan,
246 },
247 /// Binds `value` to `id`, and then results in `body` with that binding.
248 ///
249 /// This stage has the effect of sharing `value` across multiple possible
250 /// uses in `body`, and is the only mechanism we have for sharing collection
251 /// information across parts of a dataflow.
252 ///
253 /// The binding is not available outside of `body`.
254 Let {
255 /// The local identifier to be used, available to `body` as `Id::Local(id)`.
256 id: LocalId,
257 /// The collection that should be bound to `id`.
258 value: Box<LirRelationExpr>,
259 /// The collection that results, which is allowed to contain `Get` stages
260 /// that reference `Id::Local(id)`.
261 body: Box<LirRelationExpr>,
262 },
263 /// Binds `values` to `ids`, evaluates them potentially recursively, and returns `body`.
264 ///
265 /// All bindings are available to all bindings, and to `body`.
266 /// The contents of each binding are initially empty, and then updated through a sequence
267 /// of iterations in which each binding is updated in sequence, from the most recent values
268 /// of all bindings.
269 LetRec {
270 /// The local identifiers to be used, available to `body` as `Id::Local(id)`.
271 ids: Vec<LocalId>,
272 /// The collection that should be bound to `id`.
273 values: Vec<LirRelationExpr>,
274 /// Maximum number of iterations. See further info on the MIR `LetRec`.
275 limits: Vec<Option<LetRecLimit>>,
276 /// The collection that results, which is allowed to contain `Get` stages
277 /// that reference `Id::Local(id)`.
278 body: Box<LirRelationExpr>,
279 },
280 /// Map, Filter, and Project operators.
281 ///
282 /// This stage contains work that we would ideally like to fuse to other plan
283 /// stages, but for practical reasons cannot. For example: threshold, topk,
284 /// and sometimes reduce stages are not able to absorb this operator.
285 Mfp {
286 /// The input collection.
287 input: Box<LirRelationExpr>,
288 /// Linear operator to apply to each record.
289 mfp: MfpPlan<LirScalarExpr>,
290 /// Whether the input is from an arrangement, and if so,
291 /// whether we can seek to a specific value therein
292 input_key_val: Option<(Vec<LirScalarExpr>, Option<Row>)>,
293 },
294 /// A variable number of output records for each input record.
295 ///
296 /// This stage is a bit of a catch-all for logic that does not easily fit in
297 /// map stages. This includes table valued functions, but also functions of
298 /// multiple arguments, and functions that modify the sign of updates.
299 ///
300 /// This stage allows a `MapFilterProject` operator to be fused to its output,
301 /// and this can be very important as otherwise the output of `func` is just
302 /// appended to the input record, for as many outputs as it has. This has the
303 /// unpleasant default behavior of repeating potentially large records that
304 /// are being unpacked, producing quadratic output in those cases. Instead,
305 /// in these cases use a `mfp` member that projects away these large fields.
306 FlatMap {
307 /// The particular arrangement of the input we expect to use,
308 /// if any
309 input_key: Option<Vec<LirScalarExpr>>,
310 /// The input collection.
311 input: Box<LirRelationExpr>,
312 /// Expressions that for each row prepare the arguments to `func`.
313 exprs: Vec<LirScalarExpr>,
314 /// The variable-record emitting function.
315 func: TableFunc,
316 /// Linear operator to apply to each record produced by `func`.
317 mfp_after: MfpPlan<LirScalarExpr>,
318 },
319 /// A multiway relational equijoin, with fused map, filter, and projection.
320 ///
321 /// This stage performs a multiway join among `inputs`, using the equality
322 /// constraints expressed in `plan`. The plan also describes the implementation
323 /// strategy we will use, and any pushed down per-record work.
324 Join {
325 /// An ordered list of inputs that will be joined.
326 inputs: Vec<LirRelationExpr>,
327 /// Detailed information about the implementation of the join.
328 ///
329 /// This includes information about the implementation strategy, but also
330 /// any map, filter, project work that we might follow the join with, but
331 /// potentially pushed down into the implementation of the join.
332 plan: JoinPlan,
333 },
334 /// Aggregation by key.
335 Reduce {
336 /// The particular arrangement of the input we expect to use,
337 /// if any
338 input_key: Option<Vec<LirScalarExpr>>,
339 /// The input collection.
340 input: Box<LirRelationExpr>,
341 /// A plan for changing input records into key, value pairs.
342 key_val_plan: KeyValPlan,
343 /// A plan for performing the reduce.
344 ///
345 /// The implementation of reduction has several different strategies based
346 /// on the properties of the reduction, and the input itself. Please check
347 /// out the documentation for this type for more detail.
348 plan: ReducePlan,
349 /// An MFP that must be applied to results. The projection part of this
350 /// MFP must preserve the key for the reduction; otherwise, the results
351 /// become undefined. Additionally, the MFP is guaranteed to be free from
352 /// temporal predicates so that it can be readily evaluated.
353 mfp_after: SafeMfpPlan<LirScalarExpr>,
354 /// Strategy for forming the internal input arrangement built by `Reduce`
355 /// (materialized via `key_val_plan`).
356 ///
357 /// Set by the lowering from the input's `has_future_updates` flag. The
358 /// renderer applies it to the keyed `(key, val)` stream feeding the
359 /// reduce. See `render_reduce` for the rationale on why this is
360 /// plumbed through `Reduce` rather than handled at the arrangement site.
361 ///
362 /// Note: unrelated to the hash buckets used by hierarchical reductions
363 /// (e.g. `ReducePlan::Hierarchical`'s `buckets`), which are an internal
364 /// sharding scheme for `min`/`max`-style aggregations. Here "bucketing"
365 /// refers exclusively to temporal (time-domain) bucketing of
366 /// future-stamped updates.
367 temporal_bucketing_strategy: ArrangementStrategy,
368 },
369 /// Key-based "Top K" operator, retaining the first K records in each group.
370 TopK {
371 /// The input collection.
372 input: Box<LirRelationExpr>,
373 /// A plan for performing the Top-K.
374 ///
375 /// The implementation of reduction has several different strategies based
376 /// on the properties of the reduction, and the input itself. Please check
377 /// out the documentation for this type for more detail.
378 top_k_plan: TopKPlan,
379 /// Strategy for bucketing the input collection ahead of the Top-K operator.
380 ///
381 /// Set by the lowering from the input's `has_future_updates` flag. The
382 /// renderer applies it to the per-row input stream at the top of
383 /// `render_topk`, covering all three `TopKPlan` arms uniformly. See
384 /// `LirRelationNode::Reduce::temporal_bucketing_strategy` for the underlying
385 /// convention.
386 temporal_bucketing_strategy: ArrangementStrategy,
387 },
388 /// Inverts the sign of each update.
389 Negate {
390 /// The input collection.
391 input: Box<LirRelationExpr>,
392 },
393 /// Filters records that accumulate negatively.
394 ///
395 /// Although the operator suppresses updates, it is a stateful operator taking
396 /// resources proportional to the number of records with non-zero accumulation.
397 Threshold {
398 /// The input collection.
399 input: Box<LirRelationExpr>,
400 /// A plan for performing the threshold.
401 ///
402 /// The implementation of reduction has several different strategies based
403 /// on the properties of the reduction, and the input itself. Please check
404 /// out the documentation for this type for more detail.
405 threshold_plan: ThresholdPlan,
406 },
407 /// Adds the contents of the input collections.
408 ///
409 /// Importantly, this is *multiset* union, so the multiplicities of records will
410 /// add. This is in contrast to *set* union, where the multiplicities would be
411 /// capped at one. A set union can be formed with `Union` followed by `Reduce`
412 /// implementing the "distinct" operator.
413 Union {
414 /// The input collections
415 inputs: Vec<LirRelationExpr>,
416 /// Whether to consolidate the output, e.g., cancel negated records.
417 consolidate_output: bool,
418 /// Per-input bucketing strategies. Lockstep with `inputs`: index `i` is the
419 /// strategy applied to `inputs[i]` before concatenation.
420 ///
421 /// Set by the lowering from each input's `has_future_updates` flag. Only
422 /// consolidating Unions (`consolidate_output: true`) carry non-`Direct`
423 /// entries, because bucketing only pays off ahead of a consolidating
424 /// downstream operator. See `LirRelationNode::Reduce::temporal_bucketing_strategy`
425 /// for the underlying convention.
426 temporal_bucketing_strategies: Vec<ArrangementStrategy>,
427 },
428 /// The `input` plan, but with additional arrangements.
429 ///
430 /// This operator does not change the logical contents of `input`, but ensures
431 /// that certain arrangements are available in the results. This operator can
432 /// be important for e.g. the `Join` stage which benefits from multiple arrangements
433 /// or to cap a `LirRelationExpr` so that indexes can be exported.
434 ArrangeBy {
435 /// The key that must be used to access the input.
436 input_key: Option<Vec<LirScalarExpr>>,
437 /// The input collection.
438 input: Box<LirRelationExpr>,
439 /// The MFP that must be applied to the input.
440 input_mfp: MfpPlan<LirScalarExpr>,
441 /// A list of arrangement keys, and possibly a raw collection,
442 /// that will be added to those of the input. Does not include
443 /// any other existing arrangements.
444 forms: AvailableCollections,
445 /// How the renderer should form the arrangements requested by `forms`.
446 strategy: ArrangementStrategy,
447 },
448}
449
450impl LirRelationNode {
451 /// Iterates through references to child expressions.
452 pub fn children(&self) -> impl Iterator<Item = &LirRelationExpr> {
453 let mut first = None;
454 let mut second = None;
455 let mut rest = None;
456 let mut last = None;
457
458 use LirRelationNode::*;
459 match self {
460 Constant { .. } | Get { .. } => (),
461 Let { value, body, .. } => {
462 first = Some(&**value);
463 second = Some(&**body);
464 }
465 LetRec { values, body, .. } => {
466 rest = Some(values);
467 last = Some(&**body);
468 }
469 Mfp { input, .. }
470 | FlatMap { input, .. }
471 | Reduce { input, .. }
472 | TopK { input, .. }
473 | Negate { input, .. }
474 | Threshold { input, .. }
475 | ArrangeBy { input, .. } => {
476 first = Some(&**input);
477 }
478 Join { inputs, .. } | Union { inputs, .. } => {
479 rest = Some(inputs);
480 }
481 }
482
483 first
484 .into_iter()
485 .chain(second)
486 .chain(rest.into_iter().flatten())
487 .chain(last)
488 }
489
490 /// Iterates through mutable references to child expressions.
491 pub fn children_mut(&mut self) -> impl Iterator<Item = &mut LirRelationExpr> {
492 let mut first = None;
493 let mut second = None;
494 let mut rest = None;
495 let mut last = None;
496
497 use LirRelationNode::*;
498 match self {
499 Constant { .. } | Get { .. } => (),
500 Let { value, body, .. } => {
501 first = Some(&mut **value);
502 second = Some(&mut **body);
503 }
504 LetRec { values, body, .. } => {
505 rest = Some(values);
506 last = Some(&mut **body);
507 }
508 Mfp { input, .. }
509 | FlatMap { input, .. }
510 | Reduce { input, .. }
511 | TopK { input, .. }
512 | Negate { input, .. }
513 | Threshold { input, .. }
514 | ArrangeBy { input, .. } => {
515 first = Some(&mut **input);
516 }
517 Join { inputs, .. } | Union { inputs, .. } => {
518 rest = Some(inputs);
519 }
520 }
521
522 first
523 .into_iter()
524 .chain(second)
525 .chain(rest.into_iter().flatten())
526 .chain(last)
527 }
528}
529
530impl LirRelationNode {
531 /// Attach an `lir_id` to a `LirRelationNode` to make a complete `LirRelationExpr`.
532 pub fn as_plan(self, lir_id: LirId) -> LirRelationExpr {
533 LirRelationExpr { lir_id, node: self }
534 }
535}
536
537impl LirRelationExpr {
538 /// Pretty-print this [LirRelationExpr] to a string.
539 pub fn pretty(&self) -> String {
540 let config = ExplainConfig::default();
541 self.debug_explain(&config, None)
542 }
543
544 /// Pretty-print this [LirRelationExpr] to a string using a custom
545 /// [ExplainConfig] and an optionally provided [ExprHumanizer].
546 /// This is intended for debugging and tests, not users.
547 pub fn debug_explain(
548 &self,
549 config: &ExplainConfig,
550 humanizer: Option<&dyn ExprHumanizer>,
551 ) -> String {
552 text_string_at(self, || PlanRenderingContext {
553 indent: Indent::default(),
554 humanizer: humanizer.unwrap_or(&DummyHumanizer),
555 annotations: BTreeMap::default(),
556 config,
557 ambiguous_ids: BTreeSet::default(),
558 })
559 }
560}
561
562/// How a `Get` stage will be rendered.
563#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)]
564pub enum GetPlan {
565 /// Simply pass input arrangements on to the next stage.
566 PassArrangements,
567 /// Using the supplied key, optionally seek the row, and apply the MFP.
568 Arrangement(Vec<LirScalarExpr>, Option<Row>, MfpPlan<LirScalarExpr>),
569 /// Scan the input collection (unarranged) and apply the MFP.
570 Collection(MfpPlan<LirScalarExpr>),
571}
572
573impl LirRelationExpr {
574 /// Convert the dataflow description into one that uses render plans.
575 #[mz_ore::instrument(
576 target = "optimizer",
577 level = "debug",
578 fields(path.segment = "finalize_dataflow")
579 )]
580 pub fn finalize_dataflow(
581 desc: DataflowDescription<OptimizedMirRelationExpr>,
582 features: &OptimizerFeatures,
583 metrics: Option<&LoweringMetrics>,
584 ) -> Result<DataflowDescription<Self>, String> {
585 // First, we lower the dataflow description from MIR to LIR. Lowering
586 // also moves common parts of the MFPs pushed onto each source's reads into the source
587 // itself (see `Context::refine_source_mfps`).
588 let mut dataflow = Self::lower_dataflow(desc, features, metrics)?;
589
590 // Note: `consolidate_output` for `Union` and per-input
591 // `temporal_bucketing_strategies` are decided at lowering time (see the
592 // `Union` arm of `lower_mir_expr_stack_safe`). The pre-existing
593 // `refine_union_negate_consolidation` pass — which used to flip
594 // `consolidate_output` to `true` for Unions with a `Negate` child — has
595 // been folded into the lowering, since lowering is the only point where
596 // the bucketing decision (which depends on `has_future_updates`) is
597 // available.
598
599 if dataflow.is_single_time() {
600 // The relaxation of the `must_consolidate` flag performs an LIR-based
601 // analysis and transform under checked recursion. By a similar argument
602 // made in `from_mir`, we do not expect the recursion limit to be hit.
603 // However, if that happens, we propagate an error to the caller.
604 // To apply the transform, we first obtain monotonic source and index
605 // global IDs and add them to a `TransformConfig` instance.
606 let monotonic_ids = dataflow
607 .source_imports
608 .iter()
609 .filter_map(|(id, source_import)| source_import.monotonic.then_some(*id))
610 .chain(
611 dataflow
612 .index_imports
613 .iter()
614 .filter_map(|(_id, index_import)| {
615 if index_import.monotonic {
616 Some(index_import.desc.on_id)
617 } else {
618 None
619 }
620 }),
621 )
622 .collect::<BTreeSet<_>>();
623
624 let config = TransformConfig { monotonic_ids };
625 Self::refine_single_time_consolidation(&mut dataflow, &config)?;
626
627 // For non-recursive delta joins in single-time dataflows, only the delta path for the
628 // first relation produces updates: the other paths discard updates at the as-of, which
629 // is the only time present. We keep just that path and, where the first input's
630 // arrangement existed solely to seed it, drop the arrangement and consume the input as a
631 // raw collection. This requires rewriting the path's initial closure to address the raw
632 // row layout rather than the arranged `(key, value)` layout.
633 for build_desc in dataflow.objects_to_build.iter_mut() {
634 // Worklist of plan nodes. `LetRec` bodies are explored but `LetRec` values are not,
635 // which excludes recursive (WMR) joins from this transform.
636 let mut todo = vec![&mut build_desc.plan];
637 while let Some(expr) = todo.pop() {
638 match &mut expr.node {
639 // TODO: also handle binary differential joins, which can likewise shed a
640 // bespoke arrangement on their first input.
641 LirRelationNode::Join {
642 inputs,
643 plan: JoinPlan::Delta(plan),
644 } => {
645 // Only the first relation's path survives at a single time.
646 plan.path_plans.truncate(1);
647
648 let source_relation = plan.path_plans[0].source_relation;
649 // Replace the source input's bespoke arrangement with a raw collection,
650 // but only when the surviving path's source is fed by an `ArrangeBy`
651 // that exists solely to build that arrangement. A source backed directly
652 // by an arranged import has no `ArrangeBy` node here, so this guard skips
653 // it and the path keeps reading it arranged.
654 if let Some(source_key) = plan.path_plans[0].source_key.clone() {
655 if let LirRelationNode::ArrangeBy { forms, .. } =
656 &mut inputs[source_relation].node
657 {
658 // Drop arrangement forms other than the source key, which the
659 // remaining path no longer needs.
660 forms.arranged.retain(|(key, _, _)| key == &source_key);
661 if let Some((to_key, permutation, thinning)) =
662 forms.arranged.pop()
663 {
664 // Make the input a raw collection and unset the source key.
665 // Clearing every arrangement form is safe: `truncate(1)`
666 // already dropped the sibling paths that were the only other
667 // consumers, and this `ArrangeBy` is private to this join
668 // input. What remains is the input's raw collection.
669 forms.raw = true;
670 forms.arranged.clear();
671 plan.path_plans[0].source_key = None;
672
673 // The initial closure addresses the arranged `(key, value)`
674 // layout: columns `[0, K)` are key datums and columns
675 // `[K, K + M)` are the thinned value datums. We rewrite it to
676 // address the raw row instead.
677 //
678 // `to_key` (length `K`) are the key expressions over a row.
679 // `permutation` (length `A`, the raw arity) maps each row
680 // column to its position in the `(key, value)` concatenation.
681 // `thinning` (length `M`) lists the row columns that form the
682 // value.
683 let key_len = to_key.len();
684 let row_arity = permutation.len();
685 let closure = &mut plan.path_plans[0].initial_closure;
686
687 // Step 1: rewrite `ready_equivalences`, which reference the
688 // arranged layout. A key column becomes its defining
689 // expression. A value column becomes the row column it was
690 // projected from.
691 for class in closure.ready_equivalences.iter_mut() {
692 for expr in class.iter_mut() {
693 let mut todo = vec![expr];
694 while let Some(expr) = todo.pop() {
695 if let LirScalarExpr::Column(c, _) = expr {
696 if let Some(key_expr) = to_key.get(*c) {
697 *expr = key_expr.clone();
698 } else {
699 *c = thinning[*c - key_len];
700 }
701 } else {
702 todo.extend(expr.children_mut());
703 }
704 }
705 }
706 }
707
708 // Step 2: rewrite the `before` MFP. Starting from a raw row,
709 // materialize the key datums and project to the arranged
710 // `(key, value)` layout the original MFP expects, then apply
711 // it.
712 let (m, f, p) = closure.before.as_map_filter_project();
713 let mfp = MapFilterProject::new(row_arity)
714 .map(to_key)
715 .project(
716 (row_arity..row_arity + key_len).chain(thinning),
717 )
718 .map(m)
719 .filter(f)
720 .project(p);
721 closure.before =
722 mfp.into_plan().unwrap().into_nontemporal().unwrap();
723 }
724 }
725 }
726
727 todo.extend(inputs.iter_mut());
728 }
729 LirRelationNode::LetRec { body, .. } => {
730 todo.push(body);
731 }
732 x => {
733 todo.extend(x.children_mut());
734 }
735 }
736 }
737 }
738 }
739
740 soft_assert_eq_no_log!(dataflow.check_invariants(), Ok(()));
741
742 mz_repr::explain::trace_plan(&dataflow);
743
744 Ok(dataflow)
745 }
746
747 /// Lowers the dataflow description from MIR to LIR. To this end, the
748 /// method collects all available arrangements and based on this information
749 /// creates plans for every object to be built for the dataflow.
750 #[mz_ore::instrument(
751 target = "optimizer",
752 level = "debug",
753 fields(path.segment ="mir_to_lir")
754 )]
755 fn lower_dataflow(
756 desc: DataflowDescription<OptimizedMirRelationExpr>,
757 features: &OptimizerFeatures,
758 metrics: Option<&LoweringMetrics>,
759 ) -> Result<DataflowDescription<Self>, String> {
760 let context = lowering::Context::new(desc.debug_name.clone(), features, metrics);
761 let dataflow = context.lower(desc)?;
762
763 mz_repr::explain::trace_plan(&dataflow);
764
765 Ok(dataflow)
766 }
767
768 /// Refines the plans of objects to be built as part of a single-time `dataflow` to relax
769 /// the setting of the `must_consolidate` attribute of monotonic operators, if necessary,
770 /// whenever the input is deemed to be physically monotonic.
771 #[mz_ore::instrument(
772 target = "optimizer",
773 level = "debug",
774 fields(path.segment = "refine_single_time_consolidation")
775 )]
776 fn refine_single_time_consolidation(
777 dataflow: &mut DataflowDescription<Self>,
778 config: &TransformConfig,
779 ) -> Result<(), String> {
780 // We should only reach here if we have a one-shot SELECT query, i.e.,
781 // a single-time dataflow.
782 assert!(dataflow.is_single_time());
783
784 let transform = transform::RelaxMustConsolidate;
785 for build_desc in dataflow.objects_to_build.iter_mut() {
786 transform
787 .transform(config, &mut build_desc.plan)
788 .map_err(|_| "Maximum recursion limit error in consolidation relaxation.")?;
789 }
790 mz_repr::explain::trace_plan(dataflow);
791 Ok(())
792 }
793}
794
795impl CollectionPlan for LirRelationNode {
796 fn depends_on_into(&self, out: &mut BTreeSet<GlobalId>) {
797 match self {
798 LirRelationNode::Constant { rows: _ } => (),
799 LirRelationNode::Get {
800 id,
801 keys: _,
802 plan: _,
803 } => match id {
804 Id::Global(id) => {
805 out.insert(*id);
806 }
807 Id::Local(_) => (),
808 },
809 LirRelationNode::Let { id: _, value, body } => {
810 value.depends_on_into(out);
811 body.depends_on_into(out);
812 }
813 LirRelationNode::LetRec {
814 ids: _,
815 values,
816 limits: _,
817 body,
818 } => {
819 for value in values.iter() {
820 value.depends_on_into(out);
821 }
822 body.depends_on_into(out);
823 }
824 LirRelationNode::Join { inputs, plan: _ }
825 | LirRelationNode::Union {
826 inputs,
827 consolidate_output: _,
828 temporal_bucketing_strategies: _,
829 } => {
830 for input in inputs {
831 input.depends_on_into(out);
832 }
833 }
834 LirRelationNode::Mfp {
835 input,
836 mfp: _,
837 input_key_val: _,
838 }
839 | LirRelationNode::FlatMap {
840 input_key: _,
841 input,
842 exprs: _,
843 func: _,
844 mfp_after: _,
845 }
846 | LirRelationNode::ArrangeBy {
847 input_key: _,
848 input,
849 input_mfp: _,
850 forms: _,
851 strategy: _,
852 }
853 | LirRelationNode::Reduce {
854 input_key: _,
855 input,
856 key_val_plan: _,
857 plan: _,
858 mfp_after: _,
859 temporal_bucketing_strategy: _,
860 }
861 | LirRelationNode::TopK {
862 input,
863 top_k_plan: _,
864 temporal_bucketing_strategy: _,
865 }
866 | LirRelationNode::Negate { input }
867 | LirRelationNode::Threshold {
868 input,
869 threshold_plan: _,
870 } => {
871 input.depends_on_into(out);
872 }
873 }
874 }
875}
876
877impl CollectionPlan for LirRelationExpr {
878 fn depends_on_into(&self, out: &mut BTreeSet<GlobalId>) {
879 self.node.depends_on_into(out);
880 }
881}
882
883/// Returns bucket sizes, descending, suitable for hierarchical decomposition of an operator, based
884/// on the expected number of rows that will have the same group key.
885fn bucketing_of_expected_group_size(expected_group_size: Option<u64>) -> Vec<u64> {
886 // NOTE(vmarcos): The fan-in of 16 defined below is used in the tuning advice built-in view
887 // mz_introspection.mz_expected_group_size_advice.
888 let mut buckets = vec![];
889 let mut current = 16;
890
891 // Plan for 4B records in the expected case if the user didn't specify a group size.
892 let limit = expected_group_size.unwrap_or(4_000_000_000);
893
894 // Distribute buckets in powers of 16, so that we can strike a balance between how many inputs
895 // each layer gets from the preceding layer, while also limiting the number of layers.
896 while current < limit {
897 buckets.push(current);
898 current = current.saturating_mul(16);
899 }
900
901 buckets.reverse();
902 buckets
903}