Skip to main content

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 [`LirRelationExpr`] 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 [`LirRelationExpr`] 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;
27use mz_expr::Id;
28use mz_expr::MfpPlan;
29use mz_expr::explain::{HumanizedExplain, HumanizerMode, fmt_text_constant_rows};
30use mz_ore::soft_assert_or_log;
31use mz_ore::str::{IndentLike, StrExt, separated};
32use mz_repr::explain::text::DisplayText;
33use mz_repr::explain::{
34    CompactScalarSeq, CompactScalars, ExplainConfig, ExprHumanizer, Indices, PlanRenderingContext,
35};
36
37use crate::plan::join::delta_join::{DeltaPathPlan, DeltaStagePlan};
38use crate::plan::join::linear_join::LinearStagePlan;
39use crate::plan::join::{DeltaJoinPlan, JoinClosure, LinearJoinPlan};
40use crate::plan::reduce::{
41    AccumulablePlan, BasicPlan, BucketedPlan, HierarchicalPlan, MonotonicPlan, SingleBasicPlan,
42};
43use crate::plan::scalar::LirScalarExpr;
44use crate::plan::threshold::ThresholdPlan;
45use crate::plan::{
46    ArrangementStrategy, AvailableCollections, LirId, LirRelationExpr, LirRelationNode,
47};
48
49impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for LirRelationExpr {
50    fn fmt_text(
51        &self,
52        f: &mut fmt::Formatter<'_>,
53        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
54    ) -> fmt::Result {
55        if ctx.config.verbose_syntax {
56            self.fmt_verbose_text(f, ctx)
57        } else {
58            self.fmt_default_text(f, ctx)
59        }
60    }
61}
62
63impl LirRelationExpr {
64    // NOTE: This code needs to be kept in sync with the `Display` instance for
65    // `RenderPlan:ExprHumanizer`.
66    //
67    // This code determines what you see in `EXPLAIN`; that other code
68    // determine what you see when you run `mz_lir_mapping`.
69    fn fmt_default_text(
70        &self,
71        f: &mut fmt::Formatter<'_>,
72        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
73    ) -> fmt::Result {
74        use LirRelationNode::*;
75
76        let mode = HumanizedExplain::new(ctx.config.redacted);
77        let annotations = PlanAnnotations::new(ctx.config.clone(), self);
78
79        match &self.node {
80            Constant { rows } => {
81                write!(f, "{}→Constant ", ctx.indent)?;
82
83                match rows {
84                    Ok(rows) => write!(
85                        f,
86                        "({} row{})",
87                        rows.len(),
88                        if rows.len() == 1 { "" } else { "s" }
89                    )?,
90                    Err(err) => {
91                        if mode.redacted() {
92                            write!(f, "(error: █)")?;
93                        } else {
94                            write!(f, "(error: {})", err.to_string().quoted(),)?;
95                        }
96                    }
97                }
98
99                writeln!(f, "{annotations}")?;
100            }
101            Get { id, keys, plan } => {
102                ctx.indent.set(); // mark the current indent level
103
104                // Resolve the id as a string.
105                let id = match id {
106                    Id::Local(id) => id.to_string(),
107                    Id::Global(id) => ctx
108                        .humanizer
109                        .humanize_id(*id)
110                        .unwrap_or_else(|| id.to_string()),
111                };
112                // Render plan-specific fields.
113                use crate::plan::GetPlan;
114                match plan {
115                    GetPlan::PassArrangements => {
116                        if keys.raw && keys.arranged.is_empty() {
117                            writeln!(f, "{}→Stream {id}{annotations}", ctx.indent)?;
118                        } else {
119                            // we're not reporting on whether or not `raw` is set
120                            // we're not reporting on how many arrangements there are
121                            writeln!(f, "{}→Arranged {id}{annotations}", ctx.indent)?;
122                        }
123                    }
124                    GetPlan::Arrangement(key, Some(val), mfp) => {
125                        if !mfp.is_identity() {
126                            writeln!(f, "{}→Fused with Child Map/Filter/Project", ctx.indent)?;
127                            ctx.indent += 1;
128                            fmt_mfp_default_text(mfp, &mode, f, ctx)?;
129                            ctx.indent += 1;
130                        }
131
132                        writeln!(f, "{}→Index Lookup on {id}{annotations}", ctx.indent)?;
133                        ctx.indent += 1;
134                        let key = CompactScalars(mode.seq(key, None));
135                        write!(f, "{}Key: ({key}) ", ctx.indent)?;
136                        let val = mode.expr(val, None);
137                        writeln!(f, "Value: {val}")?;
138                    }
139                    GetPlan::Arrangement(key, None, mfp) => {
140                        if !mfp.is_identity() {
141                            writeln!(f, "{}→Fused with Child Map/Filter/Project", ctx.indent)?;
142                            ctx.indent += 1;
143                            fmt_mfp_default_text(mfp, &mode, f, ctx)?;
144                            ctx.indent += 1;
145                        }
146
147                        writeln!(f, "{}→Arranged {id}{annotations}", ctx.indent)?;
148                        ctx.indent += 1;
149                        let key = CompactScalars(mode.seq(key, None));
150                        writeln!(f, "{}Key: ({key})", ctx.indent)?;
151                    }
152                    GetPlan::Collection(mfp) => {
153                        if !mfp.is_identity() {
154                            writeln!(f, "{}→Fused with Child Map/Filter/Project", ctx.indent)?;
155                            ctx.indent += 1;
156                            fmt_mfp_default_text(mfp, &mode, f, ctx)?;
157                            ctx.indent += 1;
158                        }
159
160                        writeln!(f, "{}→Read {id}{annotations}", ctx.indent)?;
161                    }
162                }
163                ctx.indent.reset(); // reset the original indent level
164            }
165            Let { id, value, body } => {
166                let mut bindings = vec![(id, value.as_ref())];
167                let mut head = body.as_ref();
168
169                // Render Let-blocks nested in the body an outer Let-block in one step
170                // with a flattened list of bindings
171                while let Let { id, value, body } = &head.node {
172                    bindings.push((id, value.as_ref()));
173                    head = body.as_ref();
174                }
175
176                writeln!(f, "{}→With", ctx.indent)?;
177                ctx.indented(|ctx| {
178                    for (id, value) in bindings.iter() {
179                        writeln!(f, "{}cte {} =", ctx.indent, *id)?;
180                        ctx.indented(|ctx| value.fmt_text(f, ctx))?;
181                    }
182                    Ok(())
183                })?;
184                writeln!(f, "{}→Return{annotations}", ctx.indent)?;
185                ctx.indented(|ctx| head.fmt_text(f, ctx))?;
186            }
187            LetRec {
188                ids,
189                values,
190                limits,
191                body,
192            } => {
193                let head = body.as_ref();
194
195                writeln!(f, "{}→With Mutually Recursive", ctx.indent)?;
196                ctx.indented(|ctx| {
197                    let bindings = ids.iter().zip_eq(values).zip_eq(limits);
198                    for ((id, value), limit) in bindings {
199                        if let Some(limit) = limit {
200                            writeln!(f, "{}cte {} {} =", ctx.indent, limit, *id)?;
201                        } else {
202                            writeln!(f, "{}cte {} =", ctx.indent, *id)?;
203                        }
204                        ctx.indented(|ctx| value.fmt_text(f, ctx))?;
205                    }
206                    Ok(())
207                })?;
208                writeln!(f, "{}→Return{annotations}", ctx.indent)?;
209                ctx.indented(|ctx| head.fmt_text(f, ctx))?;
210            }
211            Mfp {
212                input,
213                mfp,
214                input_key_val: _,
215            } => {
216                writeln!(f, "{}→Map/Filter/Project{annotations}", ctx.indent)?;
217                ctx.indent.set();
218
219                ctx.indent += 1;
220                fmt_mfp_default_text(mfp, &mode, f, ctx)?;
221
222                // one more nesting level if we showed anything for the MFP
223                if !mfp.is_identity() {
224                    ctx.indent += 1;
225                }
226                input.fmt_text(f, ctx)?;
227                ctx.indent.reset();
228            }
229            FlatMap {
230                input_key: _,
231                input,
232                exprs,
233                func,
234                mfp_after,
235            } => {
236                ctx.indent.set();
237                if !mfp_after.is_identity() {
238                    writeln!(f, "{}→Fused with Child Map/Filter/Project", ctx.indent)?;
239                    ctx.indent += 1;
240                    fmt_mfp_default_text(mfp_after, &mode, f, ctx)?;
241                    ctx.indent += 1;
242                }
243
244                let exprs = mode.seq(exprs, None);
245                let exprs = CompactScalars(exprs);
246                writeln!(
247                    f,
248                    "{}→Table Function {func}({exprs}){annotations}",
249                    ctx.indent
250                )?;
251                ctx.indent += 1;
252
253                input.fmt_text(f, ctx)?;
254
255                ctx.indent.reset();
256            }
257            Join { inputs, plan } => {
258                use crate::plan::join::JoinPlan;
259                match plan {
260                    JoinPlan::Linear(plan) => {
261                        let label = if plan.has_cross_stage() {
262                            "→Differential Cross Join"
263                        } else {
264                            "→Differential Join"
265                        };
266                        write!(f, "{}{label} ", ctx.indent)?;
267                        fmt_join_chain(
268                            f,
269                            ctx.humanizer,
270                            &mode,
271                            inputs,
272                            plan.source_relation,
273                            plan.source_key.as_ref(),
274                            plan.stage_plans
275                                .iter()
276                                .map(|s| (s.lookup_relation, &s.lookup_key)),
277                        )?;
278                        writeln!(f, "{annotations}")?;
279                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
280                    }
281                    JoinPlan::Delta(plan) => {
282                        // A single path only survives when the plan was reduced to a one-shot
283                        // (single-time) join, e.g. a `SELECT`. Call it out so this plan is not
284                        // mistaken for the multi-path plan an index or materialized view would use.
285                        let one_shot = plan.path_plans.len() == 1;
286                        let label = match (one_shot, plan.has_cross_stage()) {
287                            (true, true) => "→One-Shot Delta Cross Join",
288                            (true, false) => "→One-Shot Delta Join",
289                            (false, true) => "→Delta Cross Join",
290                            (false, false) => "→Delta Join",
291                        };
292                        write!(f, "{}{label}", ctx.indent)?;
293                        for dpp in &plan.path_plans {
294                            write!(f, " [")?;
295                            fmt_join_chain(
296                                f,
297                                ctx.humanizer,
298                                &mode,
299                                inputs,
300                                dpp.source_relation,
301                                dpp.source_key.as_ref(),
302                                dpp.stage_plans
303                                    .iter()
304                                    .map(|s| (s.lookup_relation, &s.lookup_key)),
305                            )?;
306                            write!(f, "]")?;
307                        }
308                        writeln!(f, "{annotations}")?;
309                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
310                    }
311                }
312
313                ctx.indented(|ctx| {
314                    for input in inputs {
315                        input.fmt_text(f, ctx)?;
316                    }
317                    Ok(())
318                })?;
319            }
320            Reduce {
321                input_key: _,
322                input,
323                key_val_plan,
324                plan,
325                mfp_after,
326                temporal_bucketing_strategy,
327            } => {
328                ctx.indent.set();
329                if !mfp_after.is_identity() {
330                    writeln!(f, "{}→Fused with Child Map/Filter/Project", ctx.indent)?;
331                    ctx.indent += 1;
332                    mode.expr(mfp_after.deref(), None)
333                        .fmt_default_text(f, ctx)?;
334                    ctx.indent += 1;
335                }
336
337                let temporally_bucketed = matches!(
338                    temporal_bucketing_strategy,
339                    ArrangementStrategy::TemporalBucketing
340                );
341
342                use crate::plan::reduce::ReducePlan;
343                match plan {
344                    ReducePlan::Distinct => {
345                        write!(f, "{}→", ctx.indent)?;
346                        if temporally_bucketed {
347                            write!(f, "Temporally-Bucketed ")?;
348                        }
349                        writeln!(f, "Distinct GroupAggregate{annotations}")?;
350                    }
351                    ReducePlan::Accumulable(plan) => {
352                        write!(f, "{}→", ctx.indent)?;
353                        if temporally_bucketed {
354                            write!(f, "Temporally-Bucketed ")?;
355                        }
356                        writeln!(f, "Accumulable GroupAggregate{annotations}")?;
357                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
358                    }
359                    ReducePlan::Hierarchical(
360                        plan @ HierarchicalPlan::Bucketed(BucketedPlan { buckets, .. }),
361                    ) => {
362                        write!(f, "{}→", ctx.indent)?;
363                        if temporally_bucketed {
364                            write!(f, "Temporally-Bucketed ")?;
365                        }
366                        write!(f, "Bucketed Hierarchical GroupAggregate (buckets:")?;
367                        for bucket in buckets {
368                            write!(f, " {bucket}")?;
369                        }
370                        writeln!(f, "){annotations}")?;
371                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
372                    }
373                    ReducePlan::Hierarchical(
374                        plan @ HierarchicalPlan::Monotonic(MonotonicPlan {
375                            must_consolidate, ..
376                        }),
377                    ) => {
378                        write!(f, "{}→", ctx.indent)?;
379                        if temporally_bucketed {
380                            write!(f, "Temporally-Bucketed ")?;
381                        }
382                        if *must_consolidate {
383                            write!(f, "Consolidating ")?;
384                        }
385                        writeln!(f, "Monotonic GroupAggregate{annotations}",)?;
386                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
387                    }
388                    ReducePlan::Basic(plan) => {
389                        ctx.indent.set();
390                        if let BasicPlan::Single(SingleBasicPlan {
391                            fused_unnest_list, ..
392                        }) = &plan
393                        {
394                            if *fused_unnest_list {
395                                writeln!(
396                                    f,
397                                    "{}→Fused with Child Table Function unnest_list",
398                                    ctx.indent
399                                )?;
400                                ctx.indent += 1;
401                            }
402                        }
403                        write!(f, "{}→", ctx.indent)?;
404                        if temporally_bucketed {
405                            write!(f, "Temporally-Bucketed ")?;
406                        }
407                        writeln!(f, "Non-incremental GroupAggregate{annotations}")?;
408                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
409                        ctx.indent.reset();
410                    }
411                }
412
413                ctx.indented(|ctx| {
414                    let kvp = key_val_plan.key_plan.deref();
415                    if !kvp.is_identity() {
416                        writeln!(f, "{}Key:", ctx.indent)?;
417                        ctx.indented(|ctx| {
418                            let key_plan = mode.expr(kvp, None);
419                            key_plan.fmt_default_text(f, ctx)
420                        })?;
421                    }
422
423                    input.fmt_text(f, ctx)
424                })?;
425
426                ctx.indent.reset();
427            }
428            TopK {
429                input,
430                top_k_plan,
431                temporal_bucketing_strategy,
432            } => {
433                let temporally_bucketed = matches!(
434                    temporal_bucketing_strategy,
435                    ArrangementStrategy::TemporalBucketing
436                );
437                use crate::plan::top_k::TopKPlan;
438                match top_k_plan {
439                    TopKPlan::MonotonicTop1(plan) => {
440                        write!(f, "{}→", ctx.indent)?;
441                        if temporally_bucketed {
442                            write!(f, "Temporally-Bucketed ")?;
443                        }
444                        if plan.must_consolidate {
445                            write!(f, "Consolidating ")?;
446                        }
447                        writeln!(f, "Monotonic Top1{annotations}")?;
448
449                        ctx.indented(|ctx| {
450                            if plan.group_key.len() > 0 {
451                                let group_by = CompactScalars(mode.seq(&plan.group_key, None));
452                                writeln!(f, "{}Group By {group_by}", ctx.indent)?;
453                            }
454                            if plan.order_key.len() > 0 {
455                                let order_by = separated(", ", mode.seq(&plan.order_key, None));
456                                writeln!(f, "{}Order By {order_by}", ctx.indent)?;
457                            }
458                            Ok(())
459                        })?;
460                    }
461                    TopKPlan::MonotonicTopK(plan) => {
462                        write!(f, "{}→", ctx.indent)?;
463                        if temporally_bucketed {
464                            write!(f, "Temporally-Bucketed ")?;
465                        }
466                        if plan.must_consolidate {
467                            write!(f, "Consolidating ")?;
468                        }
469                        writeln!(f, "Monotonic TopK{annotations}")?;
470
471                        ctx.indented(|ctx| {
472                            if plan.group_key.len() > 0 {
473                                let group_by = CompactScalars(mode.seq(&plan.group_key, None));
474                                writeln!(f, "{}Group By {group_by}", ctx.indent)?;
475                            }
476                            if plan.order_key.len() > 0 {
477                                let order_by = separated(", ", mode.seq(&plan.order_key, None));
478                                writeln!(f, "{}Order By {order_by}", ctx.indent)?;
479                            }
480                            if let Some(limit) = &plan.limit {
481                                let limit = mode.expr(limit, None);
482                                writeln!(f, "{}Limit {limit}", ctx.indent)?;
483                            }
484                            Ok(())
485                        })?;
486                    }
487                    TopKPlan::Basic(plan) => {
488                        write!(f, "{}→", ctx.indent)?;
489                        if temporally_bucketed {
490                            write!(f, "Temporally-Bucketed ")?;
491                        }
492                        writeln!(f, "Non-monotonic TopK{annotations}")?;
493
494                        ctx.indented(|ctx| {
495                            if plan.group_key.len() > 0 {
496                                let group_by = CompactScalars(mode.seq(&plan.group_key, None));
497                                writeln!(f, "{}Group By {group_by}", ctx.indent)?;
498                            }
499                            if plan.order_key.len() > 0 {
500                                let order_by = separated(", ", mode.seq(&plan.order_key, None));
501                                writeln!(f, "{}Order By {order_by}", ctx.indent)?;
502                            }
503                            if let Some(limit) = &plan.limit {
504                                let limit = mode.expr(limit, None);
505                                writeln!(f, "{}Limit {limit}", ctx.indent)?;
506                            }
507                            if plan.offset != 0 {
508                                let offset = plan.offset;
509                                writeln!(f, "{}Offset {offset}", ctx.indent)?;
510                            }
511                            Ok(())
512                        })?;
513                    }
514                }
515
516                ctx.indented(|ctx| input.fmt_text(f, ctx))?;
517            }
518            Negate { input } => {
519                writeln!(f, "{}→Negate Diffs{annotations}", ctx.indent)?;
520
521                ctx.indented(|ctx| input.fmt_text(f, ctx))?;
522            }
523            Threshold {
524                input,
525                threshold_plan,
526            } => {
527                match threshold_plan {
528                    ThresholdPlan::Basic(plan) => {
529                        write!(f, "{}→Threshold Diffs ", ctx.indent)?;
530                        let ensure_arrangement = Arrangement::from(&plan.ensure_arrangement);
531                        ensure_arrangement.fmt_text(f, ctx)?;
532                        writeln!(f, "{annotations}")?;
533                    }
534                };
535
536                ctx.indented(|ctx| input.fmt_text(f, ctx))?;
537            }
538            Union {
539                inputs,
540                consolidate_output,
541                temporal_bucketing_strategies,
542            } => {
543                let any_temporally_bucketed = temporal_bucketing_strategies
544                    .iter()
545                    .any(|s| matches!(s, ArrangementStrategy::TemporalBucketing));
546                write!(f, "{}→", ctx.indent)?;
547                if any_temporally_bucketed {
548                    write!(f, "Temporally-Bucketed ")?;
549                }
550                if *consolidate_output {
551                    write!(f, "Consolidating ")?;
552                }
553                writeln!(f, "Union{annotations}")?;
554
555                ctx.indented(|ctx| {
556                    for input in inputs.iter() {
557                        input.fmt_text(f, ctx)?;
558                    }
559                    Ok(())
560                })?;
561            }
562            ArrangeBy {
563                input_key: _,
564                input,
565                input_mfp,
566                forms,
567                strategy: _,
568            } => {
569                ctx.indent.set();
570                if forms.raw && forms.arranged.is_empty() {
571                    soft_assert_or_log!(forms.raw, "raw stream with no arrangements");
572                    writeln!(f, "{}→Unarranged Raw Stream{annotations}", ctx.indent)?;
573                } else {
574                    write!(f, "{}→Arrange", ctx.indent)?;
575
576                    if !forms.arranged.is_empty() {
577                        let mode = HumanizedExplain::new(ctx.config.redacted);
578                        for (key, _, _) in &forms.arranged {
579                            if !key.is_empty() {
580                                let key = mode.seq(key, None);
581                                let key = CompactScalars(key);
582                                write!(f, " ({key})")?;
583                            } else {
584                                write!(f, " (empty key)")?;
585                            }
586                        }
587                    }
588                    writeln!(f, "{annotations}")?;
589                }
590
591                if !input_mfp.is_identity() {
592                    ctx.indent += 1;
593                    writeln!(f, "{}→Fused with Parent Map/Filter/Project", ctx.indent)?;
594                    ctx.indented(|ctx| fmt_mfp_default_text(input_mfp, &mode, f, ctx))?;
595                }
596
597                ctx.indent += 1;
598                input.fmt_text(f, ctx)?;
599                ctx.indent.reset();
600            }
601        }
602
603        Ok(())
604    }
605
606    fn fmt_verbose_text(
607        &self,
608        f: &mut fmt::Formatter<'_>,
609        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
610    ) -> fmt::Result {
611        use LirRelationNode::*;
612
613        let mode = HumanizedExplain::new(ctx.config.redacted);
614        let annotations = PlanAnnotations::new(ctx.config.clone(), self);
615
616        match &self.node {
617            Constant { rows } => match rows {
618                Ok(rows) => {
619                    if !rows.is_empty() {
620                        writeln!(f, "{}Constant{}", ctx.indent, annotations)?;
621                        ctx.indented(|ctx| {
622                            fmt_text_constant_rows(
623                                f,
624                                rows.iter().map(|(data, _, diff)| (data, diff)),
625                                &mut ctx.indent,
626                                ctx.config.redacted,
627                            )
628                        })?;
629                    } else {
630                        writeln!(f, "{}Constant <empty>{}", ctx.indent, annotations)?;
631                    }
632                }
633                Err(err) => {
634                    if mode.redacted() {
635                        writeln!(f, "{}Error █{}", ctx.indent, annotations)?;
636                    } else {
637                        {
638                            writeln!(
639                                f,
640                                "{}Error {}{}",
641                                ctx.indent,
642                                err.to_string().quoted(),
643                                annotations
644                            )?;
645                        }
646                    }
647                }
648            },
649
650            Get { id, keys, plan } => {
651                ctx.indent.set(); // mark the current indent level
652
653                // Resolve the id as a string.
654                let id = match id {
655                    Id::Local(id) => id.to_string(),
656                    Id::Global(id) => ctx
657                        .humanizer
658                        .humanize_id(*id)
659                        .unwrap_or_else(|| id.to_string()),
660                };
661                // Render plan-specific fields.
662                use crate::plan::GetPlan;
663                match plan {
664                    GetPlan::PassArrangements => {
665                        writeln!(
666                            f,
667                            "{}Get::PassArrangements {}{}",
668                            ctx.indent, id, annotations
669                        )?;
670                        ctx.indent += 1;
671                    }
672                    GetPlan::Arrangement(key, val, mfp) => {
673                        writeln!(f, "{}Get::Arrangement {}{}", ctx.indent, id, annotations)?;
674                        ctx.indent += 1;
675                        fmt_mfp_verbose_text(mfp, &mode, f, ctx)?;
676                        {
677                            let key = mode.seq(key, None);
678                            let key = CompactScalars(key);
679                            writeln!(f, "{}key={}", ctx.indent, key)?;
680                        }
681                        if let Some(val) = val {
682                            let val = mode.expr(val, None);
683                            writeln!(f, "{}val={}", ctx.indent, val)?;
684                        }
685                    }
686                    GetPlan::Collection(mfp) => {
687                        writeln!(f, "{}Get::Collection {}{}", ctx.indent, id, annotations)?;
688                        ctx.indent += 1;
689                        fmt_mfp_verbose_text(mfp, &mode, f, ctx)?;
690                    }
691                }
692
693                // Render plan-agnostic fields (common for all plans for this variant).
694                keys.fmt_text(f, ctx)?;
695
696                ctx.indent.reset(); // reset the original indent level
697            }
698            Let { id, value, body } => {
699                let mut bindings = vec![(id, value.as_ref())];
700                let mut head = body.as_ref();
701
702                // Render Let-blocks nested in the body an outer Let-block in one step
703                // with a flattened list of bindings
704                while let Let { id, value, body } = &head.node {
705                    bindings.push((id, value.as_ref()));
706                    head = body.as_ref();
707                }
708
709                writeln!(f, "{}With", ctx.indent)?;
710                ctx.indented(|ctx| {
711                    for (id, value) in bindings.iter() {
712                        writeln!(f, "{}cte {} =", ctx.indent, *id)?;
713                        ctx.indented(|ctx| value.fmt_text(f, ctx))?;
714                    }
715                    Ok(())
716                })?;
717                writeln!(f, "{}Return{}", ctx.indent, annotations)?;
718                ctx.indented(|ctx| head.fmt_text(f, ctx))?;
719            }
720            LetRec {
721                ids,
722                values,
723                limits,
724                body,
725            } => {
726                let head = body.as_ref();
727
728                writeln!(f, "{}With Mutually Recursive", ctx.indent)?;
729                ctx.indented(|ctx| {
730                    let bindings = ids.iter().zip_eq(values).zip_eq(limits);
731                    for ((id, value), limit) in bindings {
732                        if let Some(limit) = limit {
733                            writeln!(f, "{}cte {} {} =", ctx.indent, limit, *id)?;
734                        } else {
735                            writeln!(f, "{}cte {} =", ctx.indent, *id)?;
736                        }
737                        ctx.indented(|ctx| value.fmt_text(f, ctx))?;
738                    }
739                    Ok(())
740                })?;
741                writeln!(f, "{}Return{}", ctx.indent, annotations)?;
742                ctx.indented(|ctx| head.fmt_text(f, ctx))?;
743            }
744            Mfp {
745                input,
746                mfp,
747                input_key_val,
748            } => {
749                writeln!(f, "{}Mfp{}", ctx.indent, annotations)?;
750                ctx.indented(|ctx| {
751                    fmt_mfp_verbose_text(mfp, &mode, f, ctx)?;
752                    if let Some((key, val)) = input_key_val {
753                        {
754                            let key = mode.seq(key, None);
755                            let key = CompactScalars(key);
756                            writeln!(f, "{}input_key={}", ctx.indent, key)?;
757                        }
758                        if let Some(val) = val {
759                            let val = mode.expr(val, None);
760                            writeln!(f, "{}input_val={}", ctx.indent, val)?;
761                        }
762                    }
763                    input.fmt_text(f, ctx)
764                })?;
765            }
766            FlatMap {
767                input_key,
768                input,
769                exprs,
770                func,
771                mfp_after,
772            } => {
773                let exprs = mode.seq(exprs, None);
774                let exprs = CompactScalars(exprs);
775                writeln!(
776                    f,
777                    "{}FlatMap {}({}){}",
778                    ctx.indent, func, exprs, annotations
779                )?;
780                ctx.indented(|ctx| {
781                    if let Some(key) = input_key {
782                        let key = mode.seq(key, None);
783                        let key = CompactScalars(key);
784                        writeln!(f, "{}input_key={}", ctx.indent, key)?;
785                    }
786                    if !mfp_after.is_identity() {
787                        writeln!(f, "{}mfp_after", ctx.indent)?;
788                        ctx.indented(|ctx| fmt_mfp_verbose_text(mfp_after, &mode, f, ctx))?;
789                    }
790                    input.fmt_text(f, ctx)
791                })?;
792            }
793            Join { inputs, plan } => {
794                use crate::plan::join::JoinPlan;
795                match plan {
796                    JoinPlan::Linear(plan) => {
797                        writeln!(f, "{}Join::Linear{}", ctx.indent, annotations)?;
798                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
799                    }
800                    JoinPlan::Delta(plan) => {
801                        writeln!(f, "{}Join::Delta{}", ctx.indent, annotations)?;
802                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
803                    }
804                }
805                ctx.indented(|ctx| {
806                    for input in inputs {
807                        input.fmt_text(f, ctx)?;
808                    }
809                    Ok(())
810                })?;
811            }
812            Reduce {
813                input_key,
814                input,
815                key_val_plan,
816                plan,
817                mfp_after,
818                temporal_bucketing_strategy,
819            } => {
820                use crate::plan::reduce::ReducePlan;
821                match plan {
822                    ReducePlan::Distinct => {
823                        writeln!(f, "{}Reduce::Distinct{}", ctx.indent, annotations)?;
824                    }
825                    ReducePlan::Accumulable(plan) => {
826                        writeln!(f, "{}Reduce::Accumulable{}", ctx.indent, annotations)?;
827                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
828                    }
829                    ReducePlan::Hierarchical(plan) => {
830                        writeln!(f, "{}Reduce::Hierarchical{}", ctx.indent, annotations)?;
831                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
832                    }
833                    ReducePlan::Basic(plan) => {
834                        writeln!(f, "{}Reduce::Basic{}", ctx.indent, annotations)?;
835                        ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
836                    }
837                }
838                ctx.indented(|ctx| {
839                    if let Some(key) = input_key {
840                        let key = mode.seq(key, None);
841                        let key = CompactScalars(key);
842                        writeln!(f, "{}input_key={}", ctx.indent, key)?;
843                    }
844                    if !matches!(temporal_bucketing_strategy, ArrangementStrategy::Direct) {
845                        writeln!(
846                            f,
847                            "{}temporal_bucketing_strategy={}",
848                            ctx.indent, temporal_bucketing_strategy
849                        )?;
850                    }
851                    if key_val_plan.key_plan.deref().is_identity() {
852                        writeln!(f, "{}key_plan=id", ctx.indent)?;
853                    } else {
854                        writeln!(f, "{}key_plan", ctx.indent)?;
855                        ctx.indented(|ctx| {
856                            let key_plan = mode.expr(key_val_plan.key_plan.deref(), None);
857                            key_plan.fmt_text(f, ctx)
858                        })?;
859                    }
860                    if key_val_plan.val_plan.deref().is_identity() {
861                        writeln!(f, "{}val_plan=id", ctx.indent)?;
862                    } else {
863                        writeln!(f, "{}val_plan", ctx.indent)?;
864                        ctx.indented(|ctx| {
865                            let val_plan = mode.expr(key_val_plan.val_plan.deref(), None);
866                            val_plan.fmt_text(f, ctx)
867                        })?;
868                    }
869                    if !mfp_after.is_identity() {
870                        writeln!(f, "{}mfp_after", ctx.indent)?;
871                        ctx.indented(|ctx| mode.expr(mfp_after.deref(), None).fmt_text(f, ctx))?;
872                    }
873
874                    input.fmt_text(f, ctx)
875                })?;
876            }
877            TopK {
878                input,
879                top_k_plan,
880                temporal_bucketing_strategy,
881            } => {
882                use crate::plan::top_k::TopKPlan;
883                match top_k_plan {
884                    TopKPlan::MonotonicTop1(plan) => {
885                        write!(f, "{}TopK::MonotonicTop1", ctx.indent)?;
886                        if plan.group_key.len() > 0 {
887                            let group_by = mode.seq(&plan.group_key, None);
888                            let group_by = CompactScalars(group_by);
889                            write!(f, " group_by=[{}]", group_by)?;
890                        }
891                        if plan.order_key.len() > 0 {
892                            let order_by = mode.seq(&plan.order_key, None);
893                            let order_by = separated(", ", order_by);
894                            write!(f, " order_by=[{}]", order_by)?;
895                        }
896                        if plan.must_consolidate {
897                            write!(f, " must_consolidate")?;
898                        }
899                    }
900                    TopKPlan::MonotonicTopK(plan) => {
901                        write!(f, "{}TopK::MonotonicTopK", ctx.indent)?;
902                        if plan.group_key.len() > 0 {
903                            let group_by = mode.seq(&plan.group_key, None);
904                            let group_by = CompactScalars(group_by);
905                            write!(f, " group_by=[{}]", group_by)?;
906                        }
907                        if plan.order_key.len() > 0 {
908                            let order_by = mode.seq(&plan.order_key, None);
909                            let order_by = separated(", ", order_by);
910                            write!(f, " order_by=[{}]", order_by)?;
911                        }
912                        if let Some(limit) = &plan.limit {
913                            let limit = mode.expr(limit, None);
914                            write!(f, " limit={}", limit)?;
915                        }
916                        if plan.must_consolidate {
917                            write!(f, " must_consolidate")?;
918                        }
919                    }
920                    TopKPlan::Basic(plan) => {
921                        write!(f, "{}TopK::Basic", ctx.indent)?;
922                        if plan.group_key.len() > 0 {
923                            let group_by = mode.seq(&plan.group_key, None);
924                            let group_by = CompactScalars(group_by);
925                            write!(f, " group_by=[{}]", group_by)?;
926                        }
927                        if plan.order_key.len() > 0 {
928                            let order_by = mode.seq(&plan.order_key, None);
929                            let order_by = separated(", ", order_by);
930                            write!(f, " order_by=[{}]", order_by)?;
931                        }
932                        if let Some(limit) = &plan.limit {
933                            let limit = mode.expr(limit, None);
934                            write!(f, " limit={}", limit)?;
935                        }
936                        if &plan.offset > &0 {
937                            write!(f, " offset={}", plan.offset)?;
938                        }
939                    }
940                }
941                writeln!(f, "{}", annotations)?;
942                ctx.indented(|ctx| {
943                    if !matches!(temporal_bucketing_strategy, ArrangementStrategy::Direct) {
944                        writeln!(
945                            f,
946                            "{}temporal_bucketing_strategy={}",
947                            ctx.indent, temporal_bucketing_strategy
948                        )?;
949                    }
950                    input.fmt_text(f, ctx)
951                })?;
952            }
953            Negate { input } => {
954                writeln!(f, "{}Negate{}", ctx.indent, annotations)?;
955                ctx.indented(|ctx| input.fmt_text(f, ctx))?;
956            }
957            Threshold {
958                input,
959                threshold_plan,
960            } => {
961                use crate::plan::threshold::ThresholdPlan;
962                match threshold_plan {
963                    ThresholdPlan::Basic(plan) => {
964                        let ensure_arrangement = Arrangement::from(&plan.ensure_arrangement);
965                        write!(f, "{}Threshold::Basic", ctx.indent)?;
966                        write!(f, " ensure_arrangement=")?;
967                        ensure_arrangement.fmt_text(f, ctx)?;
968                        writeln!(f, "{}", annotations)?;
969                    }
970                };
971                ctx.indented(|ctx| input.fmt_text(f, ctx))?;
972            }
973            Union {
974                inputs,
975                consolidate_output,
976                temporal_bucketing_strategies,
977            } => {
978                if *consolidate_output {
979                    writeln!(
980                        f,
981                        "{}Union consolidate_output={}{}",
982                        ctx.indent, consolidate_output, annotations
983                    )?;
984                } else {
985                    writeln!(f, "{}Union{}", ctx.indent, annotations)?;
986                }
987                ctx.indented(|ctx| {
988                    if temporal_bucketing_strategies
989                        .iter()
990                        .any(|s| !matches!(s, ArrangementStrategy::Direct))
991                    {
992                        let strategies = temporal_bucketing_strategies
993                            .iter()
994                            .map(|s| format!("{}", s))
995                            .collect::<Vec<_>>()
996                            .join(", ");
997                        writeln!(
998                            f,
999                            "{}temporal_bucketing_strategies=[{}]",
1000                            ctx.indent, strategies
1001                        )?;
1002                    }
1003                    for input in inputs.iter() {
1004                        input.fmt_text(f, ctx)?;
1005                    }
1006                    Ok(())
1007                })?;
1008            }
1009            ArrangeBy {
1010                input_key,
1011                input,
1012                input_mfp,
1013                forms,
1014                strategy,
1015            } => {
1016                writeln!(f, "{}ArrangeBy{}", ctx.indent, annotations)?;
1017                ctx.indented(|ctx| {
1018                    if let Some(key) = input_key {
1019                        let key = mode.seq(key, None);
1020                        let key = CompactScalars(key);
1021                        writeln!(f, "{}input_key=[{}]", ctx.indent, key)?;
1022                    }
1023                    if !matches!(strategy, ArrangementStrategy::Direct) {
1024                        writeln!(f, "{}strategy={:?}", ctx.indent, strategy)?;
1025                    }
1026                    fmt_mfp_verbose_text(input_mfp, &mode, f, ctx)?;
1027                    forms.fmt_text(f, ctx)?;
1028                    // Render input
1029                    input.fmt_text(f, ctx)
1030                })?;
1031            }
1032        }
1033
1034        Ok(())
1035    }
1036}
1037
1038/// Format the temporal bounds of an `MfpPlan` as `mz_now()` inequalities.
1039///
1040/// Renders as `TemporalFilter: <lowers> <= mz_now() < <uppers>`, omitting
1041/// the `<lowers> <=` or `< <uppers>` parts when the corresponding bound
1042/// list is empty.
1043///
1044/// Format an `MfpPlan` using concise (default) syntax: project/filter/map + temporal bounds.
1045fn fmt_mfp_default_text(
1046    mfp_plan: &MfpPlan<LirScalarExpr>,
1047    mode: &HumanizedExplain,
1048    f: &mut fmt::Formatter<'_>,
1049    ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1050) -> fmt::Result {
1051    mode.expr(mfp_plan.safe_mfp().deref(), None)
1052        .fmt_default_text(f, ctx)?;
1053    fmt_temporal_bounds(mfp_plan, mode, f, ctx)
1054}
1055
1056/// Format an `MfpPlan` using verbose syntax: project/filter/map + temporal bounds.
1057fn fmt_mfp_verbose_text(
1058    mfp_plan: &MfpPlan<LirScalarExpr>,
1059    mode: &HumanizedExplain,
1060    f: &mut fmt::Formatter<'_>,
1061    ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1062) -> fmt::Result {
1063    mode.expr(mfp_plan.safe_mfp().deref(), None)
1064        .fmt_text(f, ctx)?;
1065    fmt_temporal_bounds(mfp_plan, mode, f, ctx)
1066}
1067
1068/// Render temporal bounds as `TemporalFilter: <lowers> <= mz_now() < <uppers>`.
1069///
1070/// TODO(mgree): It is possible to recover equalities (`mz_now() = expr`) and
1071/// other, finer-grained relationships from the bound expressions, but for now
1072/// we just display `<=` and `<`.
1073#[allow(clippy::needless_pass_by_ref_mut)]
1074fn fmt_temporal_bounds(
1075    mfp_plan: &MfpPlan<LirScalarExpr>,
1076    mode: &HumanizedExplain,
1077    f: &mut fmt::Formatter<'_>,
1078    ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1079) -> fmt::Result {
1080    let (_, lower_bounds, upper_bounds) = mfp_plan.as_parts();
1081
1082    if lower_bounds.is_empty() && upper_bounds.is_empty() {
1083        return Ok(());
1084    }
1085
1086    write!(f, "{}TemporalFilter: ", ctx.indent)?;
1087    if !lower_bounds.is_empty() {
1088        let lowers = lower_bounds.iter().map(|b| mode.expr(b, None));
1089        write!(f, "{} <= ", separated(", ", lowers))?;
1090    }
1091    write!(f, "mz_now()")?;
1092    if !upper_bounds.is_empty() {
1093        let uppers = upper_bounds.iter().map(|b| mode.expr(b, None));
1094        write!(f, " < {}", separated(", ", uppers))?;
1095    }
1096    writeln!(f)?;
1097
1098    Ok(())
1099}
1100
1101impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for AvailableCollections {
1102    fn fmt_text(
1103        &self,
1104        f: &mut fmt::Formatter<'_>,
1105        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1106    ) -> fmt::Result {
1107        if ctx.config.verbose_syntax {
1108            self.fmt_verbose_text(f, ctx)
1109        } else {
1110            self.fmt_default_text(f, ctx)
1111        }
1112    }
1113}
1114impl AvailableCollections {
1115    fn fmt_default_text(
1116        &self,
1117        f: &mut fmt::Formatter<'_>,
1118        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1119    ) -> fmt::Result {
1120        let plural = if self.arranged.len() == 1 { "" } else { "s" };
1121        write!(
1122            f,
1123            "{}Keys: {} arrangement{plural} available",
1124            ctx.indent,
1125            self.arranged.len()
1126        )?;
1127
1128        if self.raw {
1129            writeln!(f, ", plus raw stream")?;
1130        } else {
1131            writeln!(f, ", no raw stream")?;
1132        }
1133
1134        ctx.indented(|ctx| {
1135            for (i, arrangement) in self.arranged.iter().enumerate() {
1136                let arrangement = Arrangement::from(arrangement);
1137                write!(f, "{}Arrangement {i}: ", ctx.indent)?;
1138                arrangement.fmt_text(f, ctx)?;
1139                writeln!(f, "")?;
1140            }
1141            Ok(())
1142        })?;
1143
1144        Ok(())
1145    }
1146
1147    fn fmt_verbose_text(
1148        &self,
1149        f: &mut fmt::Formatter<'_>,
1150        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1151    ) -> fmt::Result {
1152        // raw field
1153        let raw = &self.raw;
1154        writeln!(f, "{}raw={}", ctx.indent, raw)?;
1155        // arranged field
1156        for (i, arrangement) in self.arranged.iter().enumerate() {
1157            let arrangement = Arrangement::from(arrangement);
1158            write!(f, "{}arrangements[{}]=", ctx.indent, i)?;
1159            arrangement.fmt_text(f, ctx)?;
1160            writeln!(f, "")?;
1161        }
1162        Ok(())
1163    }
1164}
1165
1166/// Format a join implementation chain like `%0:t[#0{a}] » %1:u[#0{c}] » %2[×]`.
1167///
1168/// Each position is rendered as `%pos:name` when an underlying [`LirRelationNode::Get`] can be
1169/// dug out of the corresponding input plan (see [`humanize_input_name`]),
1170/// otherwise just `%pos`. `[×]` (U+00D7) marks a cross product (empty lookup
1171/// key). A `None` `source_key` renders the source position with no bracketed
1172/// suffix.
1173fn fmt_join_chain<'a, I>(
1174    f: &mut fmt::Formatter<'_>,
1175    humanizer: &dyn ExprHumanizer,
1176    mode: &HumanizedExplain,
1177    inputs: &[LirRelationExpr],
1178    source_relation: usize,
1179    source_key: Option<&'a Vec<LirScalarExpr>>,
1180    stages: I,
1181) -> fmt::Result
1182where
1183    I: IntoIterator<Item = (usize, &'a Vec<LirScalarExpr>)>,
1184{
1185    write!(
1186        f,
1187        "{}",
1188        humanize_input_name(humanizer, &inputs[source_relation], source_relation)
1189    )?;
1190    if let Some(key) = source_key {
1191        fmt_join_key_brackets(f, mode, key)?;
1192    }
1193    for (lookup_relation, lookup_key) in stages {
1194        write!(
1195            f,
1196            " » {}",
1197            humanize_input_name(humanizer, &inputs[lookup_relation], lookup_relation)
1198        )?;
1199        fmt_join_key_brackets(f, mode, lookup_key)?;
1200    }
1201    Ok(())
1202}
1203
1204/// Render `[k1, k2, …]` for a non-empty join key, or `[×]` for a cross product.
1205fn fmt_join_key_brackets(
1206    f: &mut fmt::Formatter<'_>,
1207    mode: &HumanizedExplain,
1208    key: &Vec<LirScalarExpr>,
1209) -> fmt::Result {
1210    if key.is_empty() {
1211        write!(f, "[×]")
1212    } else {
1213        let key = CompactScalars(mode.seq(key, None));
1214        write!(f, "[{key}]")
1215    }
1216}
1217
1218/// Render a join input as `%pos:name` if we can dig a `Get` out of `plan`,
1219/// otherwise just `%pos`. Mirrors `dig_name_from_expr` in
1220/// `src/expr/src/explain/text.rs` for the MIR `EXPLAIN OPTIMIZED PLAN` output.
1221fn humanize_input_name(
1222    humanizer: &dyn ExprHumanizer,
1223    plan: &LirRelationExpr,
1224    pos: usize,
1225) -> String {
1226    fn dig(humanizer: &dyn ExprHumanizer, plan: &LirRelationExpr) -> Option<String> {
1227        use crate::plan::LirRelationNode::*;
1228        match &plan.node {
1229            Get { id, .. } => match id {
1230                Id::Local(lid) => Some(lid.to_string()),
1231                Id::Global(gid) => Some(
1232                    humanizer
1233                        .humanize_id_unqualified(*gid)
1234                        .unwrap_or_else(|| gid.to_string()),
1235                ),
1236            },
1237            // Transparent wrappers: keep digging.
1238            ArrangeBy { input, .. } => dig(humanizer, input),
1239            Mfp { input, .. } => dig(humanizer, input),
1240            _ => None,
1241        }
1242    }
1243    match dig(humanizer, plan) {
1244        Some(name) => format!("%{pos}:{name}"),
1245        None => format!("%{pos}"),
1246    }
1247}
1248
1249impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for LinearJoinPlan {
1250    fn fmt_text(
1251        &self,
1252        f: &mut fmt::Formatter<'_>,
1253        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1254    ) -> fmt::Result {
1255        if ctx.config.verbose_syntax {
1256            self.fmt_verbose_text(f, ctx)
1257        } else {
1258            self.fmt_default_text(f, ctx)
1259        }
1260    }
1261}
1262impl LinearJoinPlan {
1263    /// True iff at least one stage is a cross product (empty lookup key).
1264    fn has_cross_stage(&self) -> bool {
1265        self.stage_plans.iter().any(|s| s.lookup_key.is_empty())
1266    }
1267
1268    #[allow(clippy::needless_pass_by_ref_mut)]
1269    fn fmt_default_text(
1270        &self,
1271        f: &mut fmt::Formatter<'_>,
1272        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1273    ) -> fmt::Result {
1274        // Per-stage closures (natural 0..N order). The header chain already
1275        // shows each stage's position + key, so we only emit a block when
1276        // there's a non-identity closure to attribute to that stage.
1277        for stage in self.stage_plans.iter() {
1278            if stage.closure.maps_or_filters() {
1279                writeln!(f, "{}after %{}:", ctx.indent, stage.lookup_relation)?;
1280                ctx.indented(|ctx| stage.closure.fmt_default_text(f, ctx))?;
1281            }
1282        }
1283        if let Some(final_closure) = &self.final_closure {
1284            if final_closure.maps_or_filters() {
1285                writeln!(f, "{}Final closure:", ctx.indent)?;
1286                ctx.indented(|ctx| final_closure.fmt_default_text(f, ctx))?;
1287            }
1288        }
1289        Ok(())
1290    }
1291
1292    fn fmt_verbose_text(
1293        &self,
1294        f: &mut fmt::Formatter<'_>,
1295        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1296    ) -> fmt::Result {
1297        let mode = HumanizedExplain::new(ctx.config.redacted);
1298        let plan = self;
1299        if let Some(closure) = plan.final_closure.as_ref() {
1300            if !closure.is_identity() {
1301                writeln!(f, "{}final_closure", ctx.indent)?;
1302                ctx.indented(|ctx| closure.fmt_text(f, ctx))?;
1303            }
1304        }
1305        for (i, plan) in plan.stage_plans.iter().enumerate() {
1306            writeln!(f, "{}linear_stage[{}]", ctx.indent, i)?;
1307            ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
1308        }
1309        if let Some(closure) = plan.initial_closure.as_ref() {
1310            if !closure.is_identity() {
1311                writeln!(f, "{}initial_closure", ctx.indent)?;
1312                ctx.indented(|ctx| closure.fmt_text(f, ctx))?;
1313            }
1314        }
1315        match &plan.source_key {
1316            Some(source_key) => {
1317                let source_key = mode.seq(source_key, None);
1318                let source_key = CompactScalars(source_key);
1319                writeln!(
1320                    f,
1321                    "{}source={{ relation={}, key=[{}] }}",
1322                    ctx.indent, &plan.source_relation, source_key
1323                )?
1324            }
1325            None => writeln!(
1326                f,
1327                "{}source={{ relation={}, raw }}",
1328                ctx.indent, &plan.source_relation
1329            )?,
1330        };
1331        Ok(())
1332    }
1333}
1334
1335impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for LinearStagePlan {
1336    fn fmt_text(
1337        &self,
1338        f: &mut fmt::Formatter<'_>,
1339        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1340    ) -> fmt::Result {
1341        if ctx.config.verbose_syntax {
1342            self.fmt_verbose_text(f, ctx)
1343        } else {
1344            self.fmt_default_text(f, ctx)
1345        }
1346    }
1347}
1348impl LinearStagePlan {
1349    #[allow(clippy::needless_pass_by_ref_mut)]
1350    fn fmt_default_text(
1351        &self,
1352        f: &mut fmt::Formatter<'_>,
1353        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1354    ) -> fmt::Result {
1355        // NB this code path should not be live, as fmt_default_text for
1356        // `LinearJoinPlan` prints out each stage already
1357        let lookup_relation = &self.lookup_relation;
1358        if !self.lookup_key.is_empty() {
1359            let lookup_key = CompactScalarSeq(&self.lookup_key);
1360            writeln!(
1361                f,
1362                "{}Lookup key {lookup_key} in %{lookup_relation}",
1363                ctx.indent
1364            )
1365        } else {
1366            writeln!(f, "{}Lookup in %{lookup_relation}", ctx.indent)
1367        }
1368    }
1369
1370    fn fmt_verbose_text(
1371        &self,
1372        f: &mut fmt::Formatter<'_>,
1373        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1374    ) -> fmt::Result {
1375        let mode = HumanizedExplain::new(ctx.config.redacted);
1376
1377        let plan = self;
1378        if !plan.closure.is_identity() {
1379            writeln!(f, "{}closure", ctx.indent)?;
1380            ctx.indented(|ctx| plan.closure.fmt_text(f, ctx))?;
1381        }
1382        {
1383            let lookup_relation = &plan.lookup_relation;
1384            let lookup_key = CompactScalarSeq(&plan.lookup_key);
1385            writeln!(
1386                f,
1387                "{}lookup={{ relation={}, key=[{}] }}",
1388                ctx.indent, lookup_relation, lookup_key
1389            )?;
1390        }
1391        {
1392            let stream_key = mode.seq(&plan.stream_key, None);
1393            let stream_key = CompactScalars(stream_key);
1394            let stream_thinning = Indices(&plan.stream_thinning);
1395            writeln!(
1396                f,
1397                "{}stream={{ key=[{}], thinning=({}) }}",
1398                ctx.indent, stream_key, stream_thinning
1399            )?;
1400        }
1401        Ok(())
1402    }
1403}
1404
1405impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for DeltaJoinPlan {
1406    fn fmt_text(
1407        &self,
1408        f: &mut fmt::Formatter<'_>,
1409        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1410    ) -> fmt::Result {
1411        if ctx.config.verbose_syntax {
1412            self.fmt_verbose_text(f, ctx)
1413        } else {
1414            self.fmt_default_text(f, ctx)
1415        }
1416    }
1417}
1418impl DeltaJoinPlan {
1419    /// True iff any stage in any path is a cross product.
1420    fn has_cross_stage(&self) -> bool {
1421        self.path_plans
1422            .iter()
1423            .any(|p| p.stage_plans.iter().any(|s| s.lookup_key.is_empty()))
1424    }
1425
1426    fn fmt_default_text(
1427        &self,
1428        f: &mut fmt::Formatter<'_>,
1429        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1430    ) -> fmt::Result {
1431        // The header chain already shows each path's positions + keys, so we
1432        // only print a `path %src:` block when at least one of its stage
1433        // closures or its final closure is non-identity.
1434        for plan in self.path_plans.iter() {
1435            let has_stage_closure = plan.stage_plans.iter().any(|s| s.closure.maps_or_filters());
1436            let has_final_closure = plan
1437                .final_closure
1438                .as_ref()
1439                .is_some_and(|c| c.maps_or_filters());
1440            if !has_stage_closure && !has_final_closure {
1441                continue;
1442            }
1443            writeln!(f, "{}path %{}:", ctx.indent, plan.source_relation)?;
1444            ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
1445        }
1446        Ok(())
1447    }
1448
1449    fn fmt_verbose_text(
1450        &self,
1451        f: &mut fmt::Formatter<'_>,
1452        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1453    ) -> fmt::Result {
1454        for (i, plan) in self.path_plans.iter().enumerate() {
1455            writeln!(f, "{}plan_path[{}]", ctx.indent, i)?;
1456            ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
1457        }
1458        Ok(())
1459    }
1460}
1461
1462impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for DeltaPathPlan {
1463    fn fmt_text(
1464        &self,
1465        f: &mut fmt::Formatter<'_>,
1466        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1467    ) -> fmt::Result {
1468        if ctx.config.verbose_syntax {
1469            self.fmt_verbose_text(f, ctx)
1470        } else {
1471            self.fmt_default_text(f, ctx)
1472        }
1473    }
1474}
1475
1476impl DeltaPathPlan {
1477    #[allow(clippy::needless_pass_by_ref_mut)]
1478    fn fmt_default_text(
1479        &self,
1480        f: &mut fmt::Formatter<'_>,
1481        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1482    ) -> fmt::Result {
1483        for stage in self.stage_plans.iter() {
1484            if stage.closure.maps_or_filters() {
1485                writeln!(f, "{}after %{}:", ctx.indent, stage.lookup_relation)?;
1486                ctx.indented(|ctx| stage.closure.fmt_default_text(f, ctx))?;
1487            }
1488        }
1489        if let Some(final_closure) = &self.final_closure {
1490            if final_closure.maps_or_filters() {
1491                writeln!(f, "{}Final closure:", ctx.indent)?;
1492                ctx.indented(|ctx| final_closure.fmt_default_text(f, ctx))?;
1493            }
1494        }
1495        Ok(())
1496    }
1497
1498    fn fmt_verbose_text(
1499        &self,
1500        f: &mut fmt::Formatter<'_>,
1501        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1502    ) -> fmt::Result {
1503        let mode = HumanizedExplain::new(ctx.config.redacted);
1504        let plan = self;
1505        if let Some(closure) = plan.final_closure.as_ref() {
1506            if !closure.is_identity() {
1507                writeln!(f, "{}final_closure", ctx.indent)?;
1508                ctx.indented(|ctx| closure.fmt_text(f, ctx))?;
1509            }
1510        }
1511        for (i, plan) in plan.stage_plans.iter().enumerate().rev() {
1512            writeln!(f, "{}delta_stage[{}]", ctx.indent, i)?;
1513            ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
1514        }
1515        if !plan.initial_closure.is_identity() {
1516            writeln!(f, "{}initial_closure", ctx.indent)?;
1517            ctx.indented(|ctx| plan.initial_closure.fmt_text(f, ctx))?;
1518        }
1519        match &plan.source_key {
1520            Some(source_key) => {
1521                let source_key = mode.seq(source_key, None);
1522                let source_key = CompactScalars(source_key);
1523                writeln!(
1524                    f,
1525                    "{}source={{ relation={}, key=[{}] }}",
1526                    ctx.indent, &plan.source_relation, source_key
1527                )?
1528            }
1529            None => writeln!(
1530                f,
1531                "{}source={{ relation={}, raw }}",
1532                ctx.indent, &plan.source_relation
1533            )?,
1534        };
1535        Ok(())
1536    }
1537}
1538
1539impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for DeltaStagePlan {
1540    fn fmt_text(
1541        &self,
1542        f: &mut fmt::Formatter<'_>,
1543        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1544    ) -> fmt::Result {
1545        if ctx.config.verbose_syntax {
1546            self.fmt_verbose_text(f, ctx)
1547        } else {
1548            self.fmt_default_text(f, ctx)
1549        }
1550    }
1551}
1552impl DeltaStagePlan {
1553    #[allow(clippy::needless_pass_by_ref_mut)]
1554    fn fmt_default_text(
1555        &self,
1556        f: &mut fmt::Formatter<'_>,
1557        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1558    ) -> fmt::Result {
1559        // NB this code path should not be live, as fmt_default_text for
1560        // `DeltaPathPlan` prints out each stage already
1561        let lookup_relation = &self.lookup_relation;
1562        let lookup_key = CompactScalarSeq(&self.lookup_key);
1563        writeln!(
1564            f,
1565            "{}Lookup key {lookup_key} in %{lookup_relation}",
1566            ctx.indent
1567        )
1568    }
1569
1570    fn fmt_verbose_text(
1571        &self,
1572        f: &mut fmt::Formatter<'_>,
1573        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1574    ) -> fmt::Result {
1575        let mode = HumanizedExplain::new(ctx.config.redacted);
1576        let plan = self;
1577        if !plan.closure.is_identity() {
1578            writeln!(f, "{}closure", ctx.indent)?;
1579            ctx.indented(|ctx| plan.closure.fmt_text(f, ctx))?;
1580        }
1581        {
1582            let lookup_relation = &plan.lookup_relation;
1583            let lookup_key = mode.seq(&plan.lookup_key, None);
1584            let lookup_key = CompactScalars(lookup_key);
1585            writeln!(
1586                f,
1587                "{}lookup={{ relation={}, key=[{}] }}",
1588                ctx.indent, lookup_relation, lookup_key
1589            )?;
1590        }
1591        {
1592            let stream_key = mode.seq(&plan.stream_key, None);
1593            let stream_key = CompactScalars(stream_key);
1594            let stream_thinning = mode.seq(&plan.stream_thinning, None);
1595            let stream_thinning = CompactScalars(stream_thinning);
1596            writeln!(
1597                f,
1598                "{}stream={{ key=[{}], thinning=({}) }}",
1599                ctx.indent, stream_key, stream_thinning
1600            )?;
1601        }
1602        Ok(())
1603    }
1604}
1605
1606impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for JoinClosure {
1607    fn fmt_text(
1608        &self,
1609        f: &mut fmt::Formatter<'_>,
1610        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1611    ) -> fmt::Result {
1612        if ctx.config.verbose_syntax {
1613            self.fmt_verbose_text(f, ctx)
1614        } else {
1615            self.fmt_default_text(f, ctx)
1616        }
1617    }
1618}
1619impl JoinClosure {
1620    fn fmt_default_text(
1621        &self,
1622        f: &mut fmt::Formatter<'_>,
1623        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1624    ) -> fmt::Result {
1625        let mode = HumanizedExplain::new(ctx.config.redacted);
1626        if !self.before.is_identity() {
1627            mode.expr(self.before.deref(), None)
1628                .fmt_default_text(f, ctx)?;
1629        }
1630        if !self.ready_equivalences.is_empty() {
1631            let equivalences = separated(
1632                " AND ",
1633                self.ready_equivalences
1634                    .iter()
1635                    .map(|equivalence| separated(" = ", mode.seq(equivalence, None))),
1636            );
1637            writeln!(f, "{}Equivalences: {equivalences}", ctx.indent)?;
1638        }
1639        Ok(())
1640    }
1641
1642    fn fmt_verbose_text(
1643        &self,
1644        f: &mut fmt::Formatter<'_>,
1645        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1646    ) -> fmt::Result {
1647        let mode = HumanizedExplain::new(ctx.config.redacted);
1648        mode.expr(self.before.deref(), None).fmt_text(f, ctx)?;
1649        if !self.ready_equivalences.is_empty() {
1650            let equivalences = separated(
1651                " AND ",
1652                self.ready_equivalences
1653                    .iter()
1654                    .map(|equivalence| separated(" = ", mode.seq(equivalence, None))),
1655            );
1656            writeln!(f, "{}ready_equivalences={}", ctx.indent, equivalences)?;
1657        }
1658        Ok(())
1659    }
1660}
1661
1662impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for AccumulablePlan {
1663    fn fmt_text(
1664        &self,
1665        f: &mut fmt::Formatter<'_>,
1666        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1667    ) -> fmt::Result {
1668        if ctx.config.verbose_syntax {
1669            self.fmt_verbose_text(f, ctx)
1670        } else {
1671            self.fmt_default_text(f, ctx)
1672        }
1673    }
1674}
1675impl AccumulablePlan {
1676    #[allow(clippy::needless_pass_by_ref_mut)]
1677    fn fmt_default_text(
1678        &self,
1679        f: &mut fmt::Formatter<'_>,
1680        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1681    ) -> fmt::Result {
1682        let mode = HumanizedExplain::new(ctx.config.redacted);
1683
1684        if !self.simple_aggrs.is_empty() {
1685            let simple_aggrs = self
1686                .simple_aggrs
1687                .iter()
1688                .map(|(_i_datum, agg)| mode.expr(agg, None));
1689            let simple_aggrs = separated(", ", simple_aggrs);
1690            writeln!(f, "{}Simple aggregates: {simple_aggrs}", ctx.indent)?;
1691        }
1692
1693        if !self.distinct_aggrs.is_empty() {
1694            let distinct_aggrs = self
1695                .distinct_aggrs
1696                .iter()
1697                .map(|(_i_datum, agg)| mode.expr(agg, None));
1698            let distinct_aggrs = separated(", ", distinct_aggrs);
1699            writeln!(f, "{}Distinct aggregates: {distinct_aggrs}", ctx.indent)?;
1700        }
1701        Ok(())
1702    }
1703
1704    #[allow(clippy::needless_pass_by_ref_mut)]
1705    fn fmt_verbose_text(
1706        &self,
1707        f: &mut fmt::Formatter<'_>,
1708        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1709    ) -> fmt::Result {
1710        let mode = HumanizedExplain::new(ctx.config.redacted);
1711        // full_aggrs (skipped because they are repeated in simple_aggrs ∪ distinct_aggrs)
1712        // for (i, aggr) in self.full_aggrs.iter().enumerate() {
1713        //     write!(f, "{}full_aggrs[{}]=", ctx.indent, i)?;
1714        //     aggr.fmt_text(f, &mut ())?;
1715        //     writeln!(f)?;
1716        // }
1717        // simple_aggrs
1718        for (i, (i_datum, agg)) in self.simple_aggrs.iter().enumerate() {
1719            let agg = mode.expr(agg, None);
1720            write!(f, "{}simple_aggrs[{}]=", ctx.indent, i)?;
1721            writeln!(f, "({}, {})", i_datum, agg)?;
1722        }
1723        // distinct_aggrs
1724        for (i, (i_datum, agg)) in self.distinct_aggrs.iter().enumerate() {
1725            let agg = mode.expr(agg, None);
1726            write!(f, "{}distinct_aggrs[{}]=", ctx.indent, i)?;
1727            writeln!(f, "({}, {})", i_datum, agg)?;
1728        }
1729        Ok(())
1730    }
1731}
1732
1733impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for HierarchicalPlan {
1734    fn fmt_text(
1735        &self,
1736        f: &mut fmt::Formatter<'_>,
1737        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1738    ) -> fmt::Result {
1739        if ctx.config.verbose_syntax {
1740            self.fmt_verbose_text(f, ctx)
1741        } else {
1742            self.fmt_default_text(f, ctx)
1743        }
1744    }
1745}
1746impl HierarchicalPlan {
1747    #[allow(clippy::needless_pass_by_ref_mut)]
1748    fn fmt_default_text(
1749        &self,
1750        f: &mut fmt::Formatter<'_>,
1751        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1752    ) -> fmt::Result {
1753        let mode = HumanizedExplain::new(ctx.config.redacted);
1754        let aggr_funcs = mode.seq(self.aggr_funcs(), None);
1755        let aggr_funcs = separated(", ", aggr_funcs);
1756        writeln!(f, "{}Aggregations: {aggr_funcs}", ctx.indent)
1757    }
1758
1759    #[allow(clippy::needless_pass_by_ref_mut)]
1760    fn fmt_verbose_text(
1761        &self,
1762        f: &mut fmt::Formatter<'_>,
1763        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1764    ) -> fmt::Result {
1765        let mode = HumanizedExplain::new(ctx.config.redacted);
1766        match self {
1767            HierarchicalPlan::Monotonic(plan) => {
1768                let aggr_funcs = mode.seq(&plan.aggr_funcs, None);
1769                let aggr_funcs = separated(", ", aggr_funcs);
1770                writeln!(f, "{}aggr_funcs=[{}]", ctx.indent, aggr_funcs)?;
1771                writeln!(f, "{}monotonic", ctx.indent)?;
1772                if plan.must_consolidate {
1773                    writeln!(f, "{}must_consolidate", ctx.indent)?;
1774                }
1775            }
1776            HierarchicalPlan::Bucketed(plan) => {
1777                let aggr_funcs = mode.seq(&plan.aggr_funcs, None);
1778                let aggr_funcs = separated(", ", aggr_funcs);
1779                writeln!(f, "{}aggr_funcs=[{}]", ctx.indent, aggr_funcs)?;
1780                let buckets = separated(", ", &plan.buckets);
1781                writeln!(f, "{}buckets=[{}]", ctx.indent, buckets)?;
1782            }
1783        }
1784        Ok(())
1785    }
1786}
1787
1788impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for BasicPlan {
1789    fn fmt_text(
1790        &self,
1791        f: &mut fmt::Formatter<'_>,
1792        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1793    ) -> fmt::Result {
1794        if ctx.config.verbose_syntax {
1795            self.fmt_verbose_text(f, ctx)
1796        } else {
1797            self.fmt_default_text(f, ctx)
1798        }
1799    }
1800}
1801impl BasicPlan {
1802    #[allow(clippy::needless_pass_by_ref_mut)]
1803    fn fmt_default_text(
1804        &self,
1805        f: &mut fmt::Formatter<'_>,
1806        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1807    ) -> fmt::Result {
1808        let mode = HumanizedExplain::new(ctx.config.redacted);
1809        match self {
1810            BasicPlan::Single(SingleBasicPlan {
1811                expr,
1812                fused_unnest_list: _,
1813            }) => {
1814                let agg = mode.expr(expr, None);
1815                writeln!(f, "{}Aggregation: {agg}", ctx.indent)?;
1816            }
1817            BasicPlan::Multiple(aggs) => {
1818                let mode = HumanizedExplain::new(ctx.config.redacted);
1819                write!(f, "{}Aggregations:", ctx.indent)?;
1820
1821                for agg in aggs.iter() {
1822                    let agg = mode.expr(agg, None);
1823                    write!(f, " {agg}")?;
1824                }
1825                writeln!(f)?;
1826            }
1827        }
1828        Ok(())
1829    }
1830
1831    #[allow(clippy::needless_pass_by_ref_mut)]
1832    fn fmt_verbose_text(
1833        &self,
1834        f: &mut fmt::Formatter<'_>,
1835        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1836    ) -> fmt::Result {
1837        let mode = HumanizedExplain::new(ctx.config.redacted);
1838        match self {
1839            BasicPlan::Single(SingleBasicPlan {
1840                expr,
1841                fused_unnest_list,
1842            }) => {
1843                let agg = mode.expr(expr, None);
1844                let fused_unnest_list = if *fused_unnest_list {
1845                    ", fused_unnest_list=true"
1846                } else {
1847                    ""
1848                };
1849                writeln!(f, "{}aggr=({}{})", ctx.indent, agg, fused_unnest_list)?;
1850            }
1851            BasicPlan::Multiple(aggs) => {
1852                for (i, agg) in aggs.iter().enumerate() {
1853                    let agg = mode.expr(agg, None);
1854                    writeln!(f, "{}aggrs[{}]={}", ctx.indent, i, agg)?;
1855                }
1856            }
1857        }
1858        Ok(())
1859    }
1860}
1861
1862/// Helper struct for rendering an arrangement.
1863struct Arrangement<'a> {
1864    key: &'a Vec<LirScalarExpr>,
1865    permutation: Permutation<'a>,
1866    thinning: &'a Vec<usize>,
1867}
1868
1869impl<'a> From<&'a (Vec<LirScalarExpr>, Vec<usize>, Vec<usize>)> for Arrangement<'a> {
1870    fn from(
1871        (key, permutation, thinning): &'a (Vec<LirScalarExpr>, Vec<usize>, Vec<usize>),
1872    ) -> Self {
1873        Arrangement {
1874            key,
1875            permutation: Permutation(permutation),
1876            thinning,
1877        }
1878    }
1879}
1880
1881impl<'a> DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for Arrangement<'a> {
1882    fn fmt_text(
1883        &self,
1884        f: &mut fmt::Formatter<'_>,
1885        ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1886    ) -> fmt::Result {
1887        if ctx.config.verbose_syntax {
1888            self.fmt_verbose_text(f, ctx)
1889        } else {
1890            self.fmt_default_text(f, ctx)
1891        }
1892    }
1893}
1894
1895impl<'a> Arrangement<'a> {
1896    #[allow(clippy::needless_pass_by_ref_mut)]
1897    fn fmt_default_text(
1898        &self,
1899        f: &mut fmt::Formatter<'_>,
1900        ctx: &PlanRenderingContext<'_, LirRelationExpr>,
1901    ) -> fmt::Result {
1902        let mode = HumanizedExplain::new(ctx.config.redacted);
1903        if !self.key.is_empty() {
1904            let key = mode.seq(self.key, None);
1905            let key = CompactScalars(key);
1906            write!(f, "{key}")
1907        } else {
1908            write!(f, "(empty key)")
1909        }
1910    }
1911
1912    #[allow(clippy::needless_pass_by_ref_mut)]
1913    fn fmt_verbose_text(
1914        &self,
1915        f: &mut fmt::Formatter<'_>,
1916        ctx: &PlanRenderingContext<'_, LirRelationExpr>,
1917    ) -> fmt::Result {
1918        let mode = HumanizedExplain::new(ctx.config.redacted);
1919        // prepare key
1920        let key = mode.seq(self.key, None);
1921        let key = CompactScalars(key);
1922        // prepare perumation map
1923        let permutation = &self.permutation;
1924        // prepare thinning
1925        let thinning = Indices(self.thinning);
1926        // write the arrangement spec
1927        write!(
1928            f,
1929            "{{ key=[{}], permutation={}, thinning=({}) }}",
1930            key, permutation, thinning
1931        )
1932    }
1933}
1934
1935/// Helper struct for rendering a permutation.
1936struct Permutation<'a>(&'a Vec<usize>);
1937
1938impl<'a> fmt::Display for Permutation<'a> {
1939    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1940        let mut pairs = vec![];
1941        for (x, y) in self.0.iter().enumerate().filter(|(x, y)| x != *y) {
1942            pairs.push(format!("#{}: #{}", x, y));
1943        }
1944
1945        if pairs.len() > 0 {
1946            write!(f, "{{{}}}", separated(", ", pairs))
1947        } else {
1948            write!(f, "id")
1949        }
1950    }
1951}
1952
1953/// Annotations for physical plans.
1954struct PlanAnnotations {
1955    config: ExplainConfig,
1956    node_id: LirId,
1957}
1958
1959// The current implementation deviates from the `AnnotatedPlan` used in `Mir~`-based plans. This is
1960// fine, since at the moment the only attribute we are going to explain is the `node_id`, which at
1961// the moment is kept inline with the `LirRelationExpr` variants. If at some point in the future we want to
1962// start deriving and printing attributes that are derived ad-hoc, however, we might want to adopt
1963// `AnnotatedPlan` here as well.
1964impl PlanAnnotations {
1965    fn new(config: ExplainConfig, plan: &LirRelationExpr) -> Self {
1966        let node_id = plan.lir_id;
1967        Self { config, node_id }
1968    }
1969}
1970
1971impl fmt::Display for PlanAnnotations {
1972    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1973        if self.config.node_ids {
1974            f.debug_struct(" //")
1975                .field("node_id", &self.node_id)
1976                .finish()
1977        } else {
1978            // No physical plan annotations enabled.
1979            Ok(())
1980        }
1981    }
1982}