mz_compute_types/explain/
text.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//! `EXPLAIN ... AS TEXT` support for LIR structures.
11//!
12//! The format adheres to the following conventions:
13//! 1. In general, every line that starts with an uppercase character
14//!    corresponds to a [`Plan`] variant.
15//! 2. Whenever the variant has an attached `~Plan`, the printed name is
16//!    `$V::$P` where `$V` identifies the variant and `$P` the plan.
17//! 3. The fields of a `~Plan` struct attached to a [`Plan`] are rendered as if
18//!    they were part of the variant themself.
19//! 4. Non-recursive parameters of each sub-plan are written as `$key=$val`
20//!    pairs on the same line or as lowercase `$key` fields on indented lines.
21//! 5. A single non-recursive parameter can be written just as `$val`.
22
23use std::fmt;
24use std::ops::Deref;
25
26use itertools::{Itertools, izip};
27use mz_expr::explain::{HumanizedExplain, HumanizerMode, fmt_text_constant_rows};
28use mz_expr::{Id, MirScalarExpr};
29use mz_ore::str::{IndentLike, StrExt, separated};
30use mz_repr::explain::text::DisplayText;
31use mz_repr::explain::{
32    CompactScalarSeq, CompactScalars, ExplainConfig, Indices, PlanRenderingContext,
33};
34
35use crate::plan::join::delta_join::{DeltaPathPlan, DeltaStagePlan};
36use crate::plan::join::linear_join::LinearStagePlan;
37use crate::plan::join::{DeltaJoinPlan, JoinClosure, LinearJoinPlan};
38use crate::plan::reduce::{
39    AccumulablePlan, BasicPlan, CollationPlan, HierarchicalPlan, SingleBasicPlan,
40};
41use crate::plan::{AvailableCollections, LirId, Plan, PlanNode};
42
43impl DisplayText<PlanRenderingContext<'_, Plan>> for Plan {
44    fn fmt_text(
45        &self,
46        f: &mut fmt::Formatter<'_>,
47        ctx: &mut PlanRenderingContext<'_, Plan>,
48    ) -> fmt::Result {
49        use PlanNode::*;
50
51        let mode = HumanizedExplain::new(ctx.config.redacted);
52        let annotations = PlanAnnotations::new(ctx.config.clone(), self);
53
54        match &self.node {
55            Constant { rows } => match rows {
56                Ok(rows) => {
57                    if !rows.is_empty() {
58                        writeln!(f, "{}Constant{}", ctx.indent, annotations)?;
59                        ctx.indented(|ctx| {
60                            fmt_text_constant_rows(
61                                f,
62                                rows.iter().map(|(data, _, diff)| (data, diff)),
63                                &mut ctx.indent,
64                                ctx.config.redacted,
65                            )
66                        })?;
67                    } else {
68                        writeln!(f, "{}Constant <empty>{}", ctx.indent, annotations)?;
69                    }
70                }
71                Err(err) => {
72                    if mode.redacted() {
73                        writeln!(f, "{}Error █{}", ctx.indent, annotations)?;
74                    } else {
75                        writeln!(
76                            f,
77                            "{}Error {}{}",
78                            ctx.indent,
79                            err.to_string().quoted(),
80                            annotations
81                        )?;
82                    }
83                }
84            },
85            Get { id, keys, plan } => {
86                ctx.indent.set(); // mark the current indent level
87
88                // Resolve the id as a string.
89                let id = match id {
90                    Id::Local(id) => id.to_string(),
91                    Id::Global(id) => ctx
92                        .humanizer
93                        .humanize_id(*id)
94                        .unwrap_or_else(|| id.to_string()),
95                };
96                // Render plan-specific fields.
97                use crate::plan::GetPlan;
98                match plan {
99                    GetPlan::PassArrangements => {
100                        writeln!(
101                            f,
102                            "{}Get::PassArrangements {}{}",
103                            ctx.indent, id, annotations
104                        )?;
105                        ctx.indent += 1;
106                    }
107                    GetPlan::Arrangement(key, val, mfp) => {
108                        writeln!(f, "{}Get::Arrangement {}{}", ctx.indent, id, annotations)?;
109                        ctx.indent += 1;
110                        mode.expr(mfp, None).fmt_text(f, ctx)?;
111                        {
112                            let key = mode.seq(key, None);
113                            let key = CompactScalars(key);
114                            writeln!(f, "{}key={}", ctx.indent, key)?;
115                        }
116                        if let Some(val) = val {
117                            let val = mode.expr(val, None);
118                            writeln!(f, "{}val={}", ctx.indent, val)?;
119                        }
120                    }
121                    GetPlan::Collection(mfp) => {
122                        writeln!(f, "{}Get::Collection {}{}", ctx.indent, id, annotations)?;
123                        ctx.indent += 1;
124                        mode.expr(mfp, None).fmt_text(f, ctx)?;
125                    }
126                }
127
128                // Render plan-agnostic fields (common for all plans for this variant).
129                keys.fmt_text(f, ctx)?;
130
131                ctx.indent.reset(); // reset the original indent level
132            }
133            Let { id, value, body } => {
134                let mut bindings = vec![(id, value.as_ref())];
135                let mut head = body.as_ref();
136
137                // Render Let-blocks nested in the body an outer Let-block in one step
138                // with a flattened list of bindings
139                while let Let { id, value, body } = &head.node {
140                    bindings.push((id, value.as_ref()));
141                    head = body.as_ref();
142                }
143
144                writeln!(f, "{}With", ctx.indent)?;
145                ctx.indented(|ctx| {
146                    for (id, value) in bindings.iter() {
147                        writeln!(f, "{}cte {} =", ctx.indent, *id)?;
148                        ctx.indented(|ctx| value.fmt_text(f, ctx))?;
149                    }
150                    Ok(())
151                })?;
152                writeln!(f, "{}Return{}", ctx.indent, annotations)?;
153                ctx.indented(|ctx| head.fmt_text(f, ctx))?;
154            }
155            LetRec {
156                ids,
157                values,
158                limits,
159                body,
160            } => {
161                let bindings = izip!(ids.iter(), values, limits).collect_vec();
162                let head = body.as_ref();
163
164                writeln!(f, "{}With Mutually Recursive", ctx.indent)?;
165                ctx.indented(|ctx| {
166                    for (id, value, limit) in bindings.iter() {
167                        if let Some(limit) = limit {
168                            writeln!(f, "{}cte {} {} =", ctx.indent, limit, *id)?;
169                        } else {
170                            writeln!(f, "{}cte {} =", ctx.indent, *id)?;
171                        }
172                        ctx.indented(|ctx| value.fmt_text(f, ctx))?;
173                    }
174                    Ok(())
175                })?;
176                writeln!(f, "{}Return{}", ctx.indent, annotations)?;
177                ctx.indented(|ctx| head.fmt_text(f, ctx))?;
178            }
179            Mfp {
180                input,
181                mfp,
182                input_key_val,
183            } => {
184                writeln!(f, "{}Mfp{}", ctx.indent, annotations)?;
185                ctx.indented(|ctx| {
186                    mode.expr(mfp, None).fmt_text(f, ctx)?;
187                    if let Some((key, val)) = input_key_val {
188                        {
189                            let key = mode.seq(key, None);
190                            let key = CompactScalars(key);
191                            writeln!(f, "{}input_key={}", ctx.indent, key)?;
192                        }
193                        if let Some(val) = val {
194                            let val = mode.expr(val, None);
195                            writeln!(f, "{}input_val={}", ctx.indent, val)?;
196                        }
197                    }
198                    input.fmt_text(f, ctx)
199                })?;
200            }
201            FlatMap {
202                input,
203                func,
204                exprs,
205                mfp_after,
206                input_key,
207            } => {
208                let exprs = mode.seq(exprs, None);
209                let exprs = CompactScalars(exprs);
210                writeln!(
211                    f,
212                    "{}FlatMap {}({}){}",
213                    ctx.indent, func, exprs, annotations
214                )?;
215                ctx.indented(|ctx| {
216                    if !mfp_after.is_identity() {
217                        writeln!(f, "{}mfp_after", ctx.indent)?;
218                        ctx.indented(|ctx| mode.expr(mfp_after, None).fmt_text(f, ctx))?;
219                    }
220                    if let Some(key) = input_key {
221                        let key = mode.seq(key, None);
222                        let key = CompactScalars(key);
223                        writeln!(f, "{}input_key={}", ctx.indent, key)?;
224                    }
225                    input.fmt_text(f, ctx)
226                })?;
227            }
228            Join { inputs, plan } => {
229                use crate::plan::join::JoinPlan;
230                match plan {
231                    JoinPlan::Linear(plan) => {
232                        writeln!(f, "{}Join::Linear{}", ctx.indent, annotations)?;
233                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
234                    }
235                    JoinPlan::Delta(plan) => {
236                        writeln!(f, "{}Join::Delta{}", ctx.indent, annotations)?;
237                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
238                    }
239                }
240                ctx.indented(|ctx| {
241                    for input in inputs {
242                        input.fmt_text(f, ctx)?;
243                    }
244                    Ok(())
245                })?;
246            }
247            Reduce {
248                input,
249                key_val_plan,
250                plan,
251                input_key,
252                mfp_after,
253            } => {
254                use crate::plan::reduce::ReducePlan;
255                match plan {
256                    ReducePlan::Distinct => {
257                        writeln!(f, "{}Reduce::Distinct{}", ctx.indent, annotations)?;
258                    }
259                    ReducePlan::Accumulable(plan) => {
260                        writeln!(f, "{}Reduce::Accumulable{}", ctx.indent, annotations)?;
261                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
262                    }
263                    ReducePlan::Hierarchical(plan) => {
264                        writeln!(f, "{}Reduce::Hierarchical{}", ctx.indent, annotations)?;
265                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
266                    }
267                    ReducePlan::Basic(plan) => {
268                        writeln!(f, "{}Reduce::Basic{}", ctx.indent, annotations)?;
269                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
270                    }
271                    ReducePlan::Collation(plan) => {
272                        writeln!(f, "{}Reduce::Collation{}", ctx.indent, annotations)?;
273                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
274                    }
275                }
276                ctx.indented(|ctx| {
277                    if key_val_plan.val_plan.deref().is_identity() {
278                        writeln!(f, "{}val_plan=id", ctx.indent)?;
279                    } else {
280                        writeln!(f, "{}val_plan", ctx.indent)?;
281                        ctx.indented(|ctx| {
282                            let val_plan = mode.expr(key_val_plan.val_plan.deref(), None);
283                            val_plan.fmt_text(f, ctx)
284                        })?;
285                    }
286                    if key_val_plan.key_plan.deref().is_identity() {
287                        writeln!(f, "{}key_plan=id", ctx.indent)?;
288                    } else {
289                        writeln!(f, "{}key_plan", ctx.indent)?;
290                        ctx.indented(|ctx| {
291                            let key_plan = mode.expr(key_val_plan.key_plan.deref(), None);
292                            key_plan.fmt_text(f, ctx)
293                        })?;
294                    }
295                    if let Some(key) = input_key {
296                        let key = mode.seq(key, None);
297                        let key = CompactScalars(key);
298                        writeln!(f, "{}input_key={}", ctx.indent, key)?;
299                    }
300                    if !mfp_after.is_identity() {
301                        writeln!(f, "{}mfp_after", ctx.indent)?;
302                        ctx.indented(|ctx| mode.expr(mfp_after, None).fmt_text(f, ctx))?;
303                    }
304
305                    input.fmt_text(f, ctx)
306                })?;
307            }
308            TopK { input, top_k_plan } => {
309                use crate::plan::top_k::TopKPlan;
310                match top_k_plan {
311                    TopKPlan::MonotonicTop1(plan) => {
312                        write!(f, "{}TopK::MonotonicTop1", ctx.indent)?;
313                        if plan.group_key.len() > 0 {
314                            let group_by = mode.seq(&plan.group_key, None);
315                            let group_by = CompactScalars(group_by);
316                            write!(f, " group_by=[{}]", group_by)?;
317                        }
318                        if plan.order_key.len() > 0 {
319                            let order_by = mode.seq(&plan.order_key, None);
320                            let order_by = separated(", ", order_by);
321                            write!(f, " order_by=[{}]", order_by)?;
322                        }
323                        if plan.must_consolidate {
324                            write!(f, " must_consolidate")?;
325                        }
326                    }
327                    TopKPlan::MonotonicTopK(plan) => {
328                        write!(f, "{}TopK::MonotonicTopK", ctx.indent)?;
329                        if plan.group_key.len() > 0 {
330                            let group_by = mode.seq(&plan.group_key, None);
331                            let group_by = CompactScalars(group_by);
332                            write!(f, " group_by=[{}]", group_by)?;
333                        }
334                        if plan.order_key.len() > 0 {
335                            let order_by = mode.seq(&plan.order_key, None);
336                            let order_by = separated(", ", order_by);
337                            write!(f, " order_by=[{}]", order_by)?;
338                        }
339                        if let Some(limit) = &plan.limit {
340                            let limit = mode.expr(limit, None);
341                            write!(f, " limit={}", limit)?;
342                        }
343                        if plan.must_consolidate {
344                            write!(f, " must_consolidate")?;
345                        }
346                    }
347                    TopKPlan::Basic(plan) => {
348                        write!(f, "{}TopK::Basic", ctx.indent)?;
349                        if plan.group_key.len() > 0 {
350                            let group_by = mode.seq(&plan.group_key, None);
351                            let group_by = CompactScalars(group_by);
352                            write!(f, " group_by=[{}]", group_by)?;
353                        }
354                        if plan.order_key.len() > 0 {
355                            let order_by = mode.seq(&plan.order_key, None);
356                            let order_by = separated(", ", order_by);
357                            write!(f, " order_by=[{}]", order_by)?;
358                        }
359                        if let Some(limit) = &plan.limit {
360                            let limit = mode.expr(limit, None);
361                            write!(f, " limit={}", limit)?;
362                        }
363                        if &plan.offset > &0 {
364                            write!(f, " offset={}", plan.offset)?;
365                        }
366                    }
367                }
368                writeln!(f, "{}", annotations)?;
369                ctx.indented(|ctx| input.fmt_text(f, ctx))?;
370            }
371            Negate { input } => {
372                writeln!(f, "{}Negate{}", ctx.indent, annotations)?;
373                ctx.indented(|ctx| input.fmt_text(f, ctx))?;
374            }
375            Threshold {
376                input,
377                threshold_plan,
378            } => {
379                use crate::plan::threshold::ThresholdPlan;
380                match threshold_plan {
381                    ThresholdPlan::Basic(plan) => {
382                        let ensure_arrangement = Arrangement::from(&plan.ensure_arrangement);
383                        write!(f, "{}Threshold::Basic", ctx.indent)?;
384                        write!(f, " ensure_arrangement=")?;
385                        ensure_arrangement.fmt_text(f, ctx)?;
386                        writeln!(f, "{}", annotations)?;
387                    }
388                };
389                ctx.indented(|ctx| input.fmt_text(f, ctx))?;
390            }
391            Union {
392                inputs,
393                consolidate_output,
394            } => {
395                if *consolidate_output {
396                    writeln!(
397                        f,
398                        "{}Union consolidate_output={}{}",
399                        ctx.indent, consolidate_output, annotations
400                    )?;
401                } else {
402                    writeln!(f, "{}Union{}", ctx.indent, annotations)?;
403                }
404                ctx.indented(|ctx| {
405                    for input in inputs.iter() {
406                        input.fmt_text(f, ctx)?;
407                    }
408                    Ok(())
409                })?;
410            }
411            ArrangeBy {
412                input,
413                forms,
414                input_key,
415                input_mfp,
416            } => {
417                writeln!(f, "{}ArrangeBy{}", ctx.indent, annotations)?;
418                ctx.indented(|ctx| {
419                    if let Some(key) = input_key {
420                        let key = mode.seq(key, None);
421                        let key = CompactScalars(key);
422                        writeln!(f, "{}input_key=[{}]", ctx.indent, key)?;
423                    }
424                    mode.expr(input_mfp, None).fmt_text(f, ctx)?;
425                    forms.fmt_text(f, ctx)?;
426                    // Render input
427                    input.fmt_text(f, ctx)
428                })?;
429            }
430        }
431
432        Ok(())
433    }
434}
435
436impl DisplayText<PlanRenderingContext<'_, Plan>> for AvailableCollections {
437    fn fmt_text(
438        &self,
439        f: &mut fmt::Formatter<'_>,
440        ctx: &mut PlanRenderingContext<'_, Plan>,
441    ) -> fmt::Result {
442        // raw field
443        let raw = &self.raw;
444        writeln!(f, "{}raw={}", ctx.indent, raw)?;
445        // arranged field
446        for (i, arrangement) in self.arranged.iter().enumerate() {
447            let arrangement = Arrangement::from(arrangement);
448            write!(f, "{}arrangements[{}]=", ctx.indent, i)?;
449            arrangement.fmt_text(f, ctx)?;
450            writeln!(f, "")?;
451        }
452        // types field
453        if let Some(types) = self.types.as_ref() {
454            if types.len() > 0 {
455                write!(f, "{}types=[", ctx.indent)?;
456                write!(
457                    f,
458                    "{}",
459                    separated(
460                        ", ",
461                        types
462                            .iter()
463                            .map(|c| ctx.humanizer.humanize_column_type(c, false))
464                    )
465                )?;
466                writeln!(f, "]")?;
467            }
468        }
469        Ok(())
470    }
471}
472
473impl DisplayText<PlanRenderingContext<'_, Plan>> for LinearJoinPlan {
474    fn fmt_text(
475        &self,
476        f: &mut fmt::Formatter<'_>,
477        ctx: &mut PlanRenderingContext<'_, Plan>,
478    ) -> fmt::Result {
479        let mode = HumanizedExplain::new(ctx.config.redacted);
480        let plan = self;
481        if let Some(closure) = plan.final_closure.as_ref() {
482            if !closure.is_identity() {
483                writeln!(f, "{}final_closure", ctx.indent)?;
484                ctx.indented(|ctx| closure.fmt_text(f, ctx))?;
485            }
486        }
487        for (i, plan) in plan.stage_plans.iter().enumerate().rev() {
488            writeln!(f, "{}linear_stage[{}]", ctx.indent, i)?;
489            ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
490        }
491        if let Some(closure) = plan.initial_closure.as_ref() {
492            if !closure.is_identity() {
493                writeln!(f, "{}initial_closure", ctx.indent)?;
494                ctx.indented(|ctx| closure.fmt_text(f, ctx))?;
495            }
496        }
497        match &plan.source_key {
498            Some(source_key) => {
499                let source_key = mode.seq(source_key, None);
500                let source_key = CompactScalars(source_key);
501                writeln!(
502                    f,
503                    "{}source={{ relation={}, key=[{}] }}",
504                    ctx.indent, &plan.source_relation, source_key
505                )?
506            }
507            None => writeln!(
508                f,
509                "{}source={{ relation={}, key=[] }}",
510                ctx.indent, &plan.source_relation
511            )?,
512        };
513        Ok(())
514    }
515}
516
517impl DisplayText<PlanRenderingContext<'_, Plan>> for LinearStagePlan {
518    fn fmt_text(
519        &self,
520        f: &mut fmt::Formatter<'_>,
521        ctx: &mut PlanRenderingContext<'_, Plan>,
522    ) -> fmt::Result {
523        let mode = HumanizedExplain::new(ctx.config.redacted);
524
525        let plan = self;
526        if !plan.closure.is_identity() {
527            writeln!(f, "{}closure", ctx.indent)?;
528            ctx.indented(|ctx| plan.closure.fmt_text(f, ctx))?;
529        }
530        {
531            let lookup_relation = &plan.lookup_relation;
532            let lookup_key = CompactScalarSeq(&plan.lookup_key);
533            writeln!(
534                f,
535                "{}lookup={{ relation={}, key=[{}] }}",
536                ctx.indent, lookup_relation, lookup_key
537            )?;
538        }
539        {
540            let stream_key = mode.seq(&plan.stream_key, None);
541            let stream_key = CompactScalars(stream_key);
542            let stream_thinning = Indices(&plan.stream_thinning);
543            writeln!(
544                f,
545                "{}stream={{ key=[{}], thinning=({}) }}",
546                ctx.indent, stream_key, stream_thinning
547            )?;
548        }
549        Ok(())
550    }
551}
552
553impl DisplayText<PlanRenderingContext<'_, Plan>> for DeltaJoinPlan {
554    fn fmt_text(
555        &self,
556        f: &mut fmt::Formatter<'_>,
557        ctx: &mut PlanRenderingContext<'_, Plan>,
558    ) -> fmt::Result {
559        for (i, plan) in self.path_plans.iter().enumerate() {
560            writeln!(f, "{}plan_path[{}]", ctx.indent, i)?;
561            ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
562        }
563        Ok(())
564    }
565}
566
567impl DisplayText<PlanRenderingContext<'_, Plan>> for DeltaPathPlan {
568    fn fmt_text(
569        &self,
570        f: &mut fmt::Formatter<'_>,
571        ctx: &mut PlanRenderingContext<'_, Plan>,
572    ) -> fmt::Result {
573        let mode = HumanizedExplain::new(ctx.config.redacted);
574        let plan = self;
575        if let Some(closure) = plan.final_closure.as_ref() {
576            if !closure.is_identity() {
577                writeln!(f, "{}final_closure", ctx.indent)?;
578                ctx.indented(|ctx| closure.fmt_text(f, ctx))?;
579            }
580        }
581        for (i, plan) in plan.stage_plans.iter().enumerate().rev() {
582            writeln!(f, "{}delta_stage[{}]", ctx.indent, i)?;
583            ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
584        }
585        if !plan.initial_closure.is_identity() {
586            writeln!(f, "{}initial_closure", ctx.indent)?;
587            ctx.indented(|ctx| plan.initial_closure.fmt_text(f, ctx))?;
588        }
589        {
590            let source_relation = &plan.source_relation;
591            let source_key = mode.seq(&plan.source_key, None);
592            let source_key = CompactScalars(source_key);
593            writeln!(
594                f,
595                "{}source={{ relation={}, key=[{}] }}",
596                ctx.indent, source_relation, source_key
597            )?;
598        }
599        Ok(())
600    }
601}
602
603impl DisplayText<PlanRenderingContext<'_, Plan>> for DeltaStagePlan {
604    fn fmt_text(
605        &self,
606        f: &mut fmt::Formatter<'_>,
607        ctx: &mut PlanRenderingContext<'_, Plan>,
608    ) -> fmt::Result {
609        let mode = HumanizedExplain::new(ctx.config.redacted);
610        let plan = self;
611        if !plan.closure.is_identity() {
612            writeln!(f, "{}closure", ctx.indent)?;
613            ctx.indented(|ctx| plan.closure.fmt_text(f, ctx))?;
614        }
615        {
616            let lookup_relation = &plan.lookup_relation;
617            let lookup_key = mode.seq(&plan.lookup_key, None);
618            let lookup_key = CompactScalars(lookup_key);
619            writeln!(
620                f,
621                "{}lookup={{ relation={}, key=[{}] }}",
622                ctx.indent, lookup_relation, lookup_key
623            )?;
624        }
625        {
626            let stream_key = mode.seq(&plan.stream_key, None);
627            let stream_key = CompactScalars(stream_key);
628            let stream_thinning = mode.seq(&plan.stream_thinning, None);
629            let stream_thinning = CompactScalars(stream_thinning);
630            writeln!(
631                f,
632                "{}stream={{ key=[{}], thinning=({}) }}",
633                ctx.indent, stream_key, stream_thinning
634            )?;
635        }
636        Ok(())
637    }
638}
639
640impl DisplayText<PlanRenderingContext<'_, Plan>> for JoinClosure {
641    fn fmt_text(
642        &self,
643        f: &mut fmt::Formatter<'_>,
644        ctx: &mut PlanRenderingContext<'_, Plan>,
645    ) -> fmt::Result {
646        let mode = HumanizedExplain::new(ctx.config.redacted);
647        mode.expr(self.before.deref(), None).fmt_text(f, ctx)?;
648        if !self.ready_equivalences.is_empty() {
649            let equivalences = separated(
650                " AND ",
651                self.ready_equivalences
652                    .iter()
653                    .map(|equivalence| separated(" = ", mode.seq(equivalence, None))),
654            );
655            writeln!(f, "{}ready_equivalences={}", ctx.indent, equivalences)?;
656        }
657        Ok(())
658    }
659}
660
661impl DisplayText<PlanRenderingContext<'_, Plan>> for AccumulablePlan {
662    fn fmt_text(
663        &self,
664        f: &mut fmt::Formatter<'_>,
665        ctx: &mut PlanRenderingContext<'_, Plan>,
666    ) -> fmt::Result {
667        let mode = HumanizedExplain::new(ctx.config.redacted);
668        // full_aggrs (skipped because they are repeated in simple_aggrs ∪ distinct_aggrs)
669        // for (i, aggr) in self.full_aggrs.iter().enumerate() {
670        //     write!(f, "{}full_aggrs[{}]=", ctx.indent, i)?;
671        //     aggr.fmt_text(f, &mut ())?;
672        //     writeln!(f)?;
673        // }
674        // simple_aggrs
675        for (i, (i_aggs, i_datum, agg)) in self.simple_aggrs.iter().enumerate() {
676            let agg = mode.expr(agg, None);
677            write!(f, "{}simple_aggrs[{}]=", ctx.indent, i)?;
678            writeln!(f, "({}, {}, {})", i_aggs, i_datum, agg)?;
679        }
680        // distinct_aggrs
681        for (i, (i_aggs, i_datum, agg)) in self.distinct_aggrs.iter().enumerate() {
682            let agg = mode.expr(agg, None);
683            write!(f, "{}distinct_aggrs[{}]=", ctx.indent, i)?;
684            writeln!(f, "({}, {}, {})", i_aggs, i_datum, agg)?;
685        }
686        Ok(())
687    }
688}
689
690impl DisplayText<PlanRenderingContext<'_, Plan>> for HierarchicalPlan {
691    fn fmt_text(
692        &self,
693        f: &mut fmt::Formatter<'_>,
694        ctx: &mut PlanRenderingContext<'_, Plan>,
695    ) -> fmt::Result {
696        let mode = HumanizedExplain::new(ctx.config.redacted);
697        match self {
698            HierarchicalPlan::Monotonic(plan) => {
699                let aggr_funcs = mode.seq(&plan.aggr_funcs, None);
700                let aggr_funcs = separated(", ", aggr_funcs);
701                writeln!(f, "{}aggr_funcs=[{}]", ctx.indent, aggr_funcs)?;
702                let skips = separated(", ", &plan.skips);
703                writeln!(f, "{}skips=[{}]", ctx.indent, skips)?;
704                writeln!(f, "{}monotonic", ctx.indent)?;
705                if plan.must_consolidate {
706                    writeln!(f, "{}must_consolidate", ctx.indent)?;
707                }
708            }
709            HierarchicalPlan::Bucketed(plan) => {
710                let aggr_funcs = mode.seq(&plan.aggr_funcs, None);
711                let aggr_funcs = separated(", ", aggr_funcs);
712                writeln!(f, "{}aggr_funcs=[{}]", ctx.indent, aggr_funcs)?;
713                let skips = separated(", ", &plan.skips);
714                writeln!(f, "{}skips=[{}]", ctx.indent, skips)?;
715                let buckets = separated(", ", &plan.buckets);
716                writeln!(f, "{}buckets=[{}]", ctx.indent, buckets)?;
717            }
718        }
719        Ok(())
720    }
721}
722
723impl DisplayText<PlanRenderingContext<'_, Plan>> for BasicPlan {
724    fn fmt_text(
725        &self,
726        f: &mut fmt::Formatter<'_>,
727        ctx: &mut PlanRenderingContext<'_, Plan>,
728    ) -> fmt::Result {
729        let mode = HumanizedExplain::new(ctx.config.redacted);
730        match self {
731            BasicPlan::Single(SingleBasicPlan {
732                index,
733                expr,
734                fused_unnest_list,
735            }) => {
736                let agg = mode.expr(expr, None);
737                let fused_unnest_list = if *fused_unnest_list {
738                    ", fused_unnest_list=true"
739                } else {
740                    ""
741                };
742                writeln!(
743                    f,
744                    "{}aggr=({}, {}{})",
745                    ctx.indent, index, agg, fused_unnest_list
746                )?;
747            }
748            BasicPlan::Multiple(aggs) => {
749                for (i, (i_datum, agg)) in aggs.iter().enumerate() {
750                    let agg = mode.expr(agg, None);
751                    writeln!(f, "{}aggrs[{}]=({}, {})", ctx.indent, i, i_datum, agg)?;
752                }
753            }
754        }
755        Ok(())
756    }
757}
758
759impl DisplayText<PlanRenderingContext<'_, Plan>> for CollationPlan {
760    fn fmt_text(
761        &self,
762        f: &mut fmt::Formatter<'_>,
763        ctx: &mut PlanRenderingContext<'_, Plan>,
764    ) -> fmt::Result {
765        {
766            use crate::plan::reduce::ReductionType;
767            let aggregate_types = &self
768                .aggregate_types
769                .iter()
770                .map(|reduction_type| match reduction_type {
771                    ReductionType::Accumulable => "a".to_string(),
772                    ReductionType::Hierarchical => "h".to_string(),
773                    ReductionType::Basic => "b".to_string(),
774                })
775                .collect::<Vec<_>>();
776            let aggregate_types = separated(", ", aggregate_types);
777            writeln!(f, "{}aggregate_types=[{}]", ctx.indent, aggregate_types)?;
778        }
779        if let Some(plan) = &self.accumulable {
780            writeln!(f, "{}accumulable", ctx.indent)?;
781            ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
782        }
783        if let Some(plan) = &self.hierarchical {
784            writeln!(f, "{}hierarchical", ctx.indent)?;
785            ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
786        }
787        if let Some(plan) = &self.basic {
788            writeln!(f, "{}basic", ctx.indent)?;
789            ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
790        }
791        Ok(())
792    }
793}
794
795/// Helper struct for rendering an arrangement.
796struct Arrangement<'a> {
797    key: &'a Vec<MirScalarExpr>,
798    permutation: Permutation<'a>,
799    thinning: &'a Vec<usize>,
800}
801
802impl<'a> From<&'a (Vec<MirScalarExpr>, Vec<usize>, Vec<usize>)> for Arrangement<'a> {
803    fn from(
804        (key, permutation, thinning): &'a (Vec<MirScalarExpr>, Vec<usize>, Vec<usize>),
805    ) -> Self {
806        Arrangement {
807            key,
808            permutation: Permutation(permutation),
809            thinning,
810        }
811    }
812}
813
814impl<'a> DisplayText<PlanRenderingContext<'_, Plan>> for Arrangement<'a> {
815    fn fmt_text(
816        &self,
817        f: &mut fmt::Formatter<'_>,
818        ctx: &mut PlanRenderingContext<'_, Plan>,
819    ) -> fmt::Result {
820        let mode = HumanizedExplain::new(ctx.config.redacted);
821        // prepare key
822        let key = mode.seq(self.key, None);
823        let key = CompactScalars(key);
824        // prepare perumation map
825        let permutation = &self.permutation;
826        // prepare thinning
827        let thinning = Indices(self.thinning);
828        // write the arrangement spec
829        write!(
830            f,
831            "{{ key=[{}], permutation={}, thinning=({}) }}",
832            key, permutation, thinning
833        )
834    }
835}
836
837/// Helper struct for rendering a permutation.
838struct Permutation<'a>(&'a Vec<usize>);
839
840impl<'a> fmt::Display for Permutation<'a> {
841    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
842        let mut pairs = vec![];
843        for (x, y) in self.0.iter().enumerate().filter(|(x, y)| x != *y) {
844            pairs.push(format!("#{}: #{}", x, y));
845        }
846
847        if pairs.len() > 0 {
848            write!(f, "{{{}}}", separated(", ", pairs))
849        } else {
850            write!(f, "id")
851        }
852    }
853}
854
855/// Annotations for physical plans.
856struct PlanAnnotations {
857    config: ExplainConfig,
858    node_id: LirId,
859}
860
861// The current implementation deviates from the `AnnotatedPlan` used in `Mir~`-based plans. This is
862// fine, since at the moment the only attribute we are going to explain is the `node_id`, which at
863// the moment is kept inline with the `Plan` variants. If at some point in the future we want to
864// start deriving and printing attributes that are derived ad-hoc, however, we might want to adopt
865// `AnnotatedPlan` here as well.
866impl PlanAnnotations {
867    fn new(config: ExplainConfig, plan: &Plan) -> Self {
868        let node_id = plan.lir_id;
869        Self { config, node_id }
870    }
871}
872
873impl fmt::Display for PlanAnnotations {
874    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
875        if self.config.node_ids {
876            f.debug_struct(" //")
877                .field("node_id", &self.node_id)
878                .finish()
879        } else {
880            // No physical plan annotations enabled.
881            Ok(())
882        }
883    }
884}