mz_expr/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 structures defined in this crate.
11
12use std::collections::BTreeMap;
13use std::fmt;
14use std::sync::Arc;
15
16use mz_ore::soft_assert_eq_or_log;
17use mz_ore::str::{Indent, IndentLike, StrExt, closure_to_display, separated};
18use mz_ore::treat_as_equal::TreatAsEqual;
19use mz_repr::explain::text::DisplayText;
20use mz_repr::explain::{
21    CompactScalars, ExprHumanizer, HumanizedAnalyses, IndexUsageType, Indices,
22    PlanRenderingContext, RenderingContext, ScalarOps,
23};
24use mz_repr::{Datum, Diff, GlobalId, Row};
25use mz_sql_parser::ast::Ident;
26
27use crate::explain::{ExplainMultiPlan, ExplainSinglePlan};
28use crate::{
29    AccessStrategy, AggregateExpr, EvalError, Id, JoinImplementation, JoinInputCharacteristics,
30    LocalId, MapFilterProject, MirRelationExpr, MirScalarExpr, RowSetFinishing,
31};
32
33impl<'a, T: 'a> DisplayText for ExplainSinglePlan<'a, T>
34where
35    T: DisplayText<PlanRenderingContext<'a, T>> + Ord,
36{
37    fn fmt_text(&self, f: &mut fmt::Formatter<'_>, _ctx: &mut ()) -> fmt::Result {
38        let mut ctx = PlanRenderingContext::new(
39            Indent::default(),
40            self.context.humanizer,
41            self.plan.annotations.clone(),
42            self.context.config,
43        );
44
45        let mode = HumanizedExplain::new(self.context.config.redacted);
46
47        if let Some(finishing) = &self.context.finishing {
48            if ctx.config.humanized_exprs {
49                let analyses = ctx.annotations.get(&self.plan.plan);
50                let cols = analyses
51                    .map(|analyses| analyses.column_names.clone())
52                    .flatten();
53                mode.expr(finishing, cols.as_ref()).fmt_text(f, &mut ctx)?;
54            } else {
55                mode.expr(finishing, None).fmt_text(f, &mut ctx)?;
56            }
57            ctx.indented(|ctx| self.plan.plan.fmt_text(f, ctx))?;
58        } else {
59            self.plan.plan.fmt_text(f, &mut ctx)?;
60        }
61
62        if !self.context.used_indexes.is_empty() {
63            writeln!(f)?;
64            self.context.used_indexes.fmt_text(f, &mut ctx)?;
65        }
66
67        if let Some(target_cluster) = self.context.target_cluster {
68            writeln!(f)?;
69            writeln!(f, "Target cluster: {}", target_cluster)?;
70        }
71
72        if !self.context.optimizer_notices.is_empty() {
73            writeln!(f)?;
74            writeln!(f, "Notices:")?;
75            for notice in self.context.optimizer_notices.iter() {
76                writeln!(f, "{}", notice)?;
77            }
78        }
79
80        if self.context.config.timing {
81            writeln!(f)?;
82            writeln!(f, "Optimization time: {:?}", self.context.duration)?;
83        }
84
85        Ok(())
86    }
87}
88
89impl<'a, T: 'a> DisplayText for ExplainMultiPlan<'a, T>
90where
91    T: DisplayText<PlanRenderingContext<'a, T>> + Ord,
92{
93    fn fmt_text(&self, f: &mut fmt::Formatter<'_>, _ctx: &mut ()) -> fmt::Result {
94        let mut ctx = RenderingContext::new(Indent::default(), self.context.humanizer);
95
96        let mode = HumanizedExplain::new(self.context.config.redacted);
97
98        // Render plans.
99        for (no, (id, plan)) in self.plans.iter().enumerate() {
100            let mut ctx = PlanRenderingContext::new(
101                ctx.indent.clone(),
102                ctx.humanizer,
103                plan.annotations.clone(),
104                self.context.config,
105            );
106
107            if no > 0 {
108                writeln!(f)?;
109            }
110
111            writeln!(f, "{}{}:", ctx.indent, id)?;
112            ctx.indented(|ctx| {
113                match &self.context.finishing {
114                    // If present, a RowSetFinishing always applies to the first rendered plan.
115                    Some(finishing) if no == 0 => {
116                        if ctx.config.humanized_exprs {
117                            let analyses = ctx.annotations.get(plan.plan);
118                            let cols = analyses
119                                .map(|analyses| analyses.column_names.clone())
120                                .flatten();
121                            mode.expr(finishing, cols.as_ref()).fmt_text(f, ctx)?;
122                        } else {
123                            mode.expr(finishing, None).fmt_text(f, ctx)?;
124                        };
125                        ctx.indented(|ctx| plan.plan.fmt_text(f, ctx))?;
126                    }
127                    // All other plans are rendered without a RowSetFinishing.
128                    _ => {
129                        plan.plan.fmt_text(f, ctx)?;
130                    }
131                }
132                Ok(())
133            })?;
134        }
135
136        if self.sources.iter().any(|src| !src.is_identity()) {
137            // Render one blank line between the plans and sources.
138            writeln!(f)?;
139            for src in self.sources.iter().filter(|src| !src.is_identity()) {
140                if self.context.config.humanized_exprs {
141                    let mut cols = ctx.humanizer.column_names_for_id(src.id);
142                    // The column names of the source needs to be extended with
143                    // anonymous columns for each source expression before we can
144                    // pass it to the ExplainSource rendering code.
145                    if let Some(cols) = cols.as_mut() {
146                        let anonymous = std::iter::repeat(String::new());
147                        cols.extend(
148                            anonymous.take(src.op.map(|op| op.expressions.len()).unwrap_or(0)),
149                        )
150                    };
151                    // Render source with humanized expressions.
152                    mode.expr(src, cols.as_ref()).fmt_text(f, &mut ctx)?;
153                } else {
154                    // Render source without humanized expressions.
155                    mode.expr(src, None).fmt_text(f, &mut ctx)?;
156                }
157            }
158        }
159
160        if !self.context.used_indexes.is_empty() {
161            writeln!(f)?;
162            self.context.used_indexes.fmt_text(f, &mut ctx)?;
163        }
164
165        if let Some(target_cluster) = self.context.target_cluster {
166            writeln!(f)?;
167            writeln!(f, "Target cluster: {}", target_cluster)?;
168        }
169
170        if !(self.context.config.no_notices || self.context.optimizer_notices.is_empty()) {
171            writeln!(f)?;
172            writeln!(f, "Notices:")?;
173            for notice in self.context.optimizer_notices.iter() {
174                writeln!(f, "{}", notice)?;
175            }
176        }
177
178        if self.context.config.timing {
179            writeln!(f)?;
180            writeln!(f, "Optimization time: {:?}", self.context.duration)?;
181        }
182
183        Ok(())
184    }
185}
186
187impl<'a, C, M> DisplayText<C> for HumanizedExpr<'a, RowSetFinishing, M>
188where
189    C: AsMut<Indent>,
190    M: HumanizerMode,
191{
192    fn fmt_text(&self, f: &mut fmt::Formatter<'_>, ctx: &mut C) -> fmt::Result {
193        write!(f, "{}Finish", ctx.as_mut())?;
194        // order by
195        if !self.expr.order_by.is_empty() {
196            let order_by = self.expr.order_by.iter().map(|e| self.child(e));
197            write!(f, " order_by=[{}]", separated(", ", order_by))?;
198        }
199        // limit
200        if let Some(limit) = self.expr.limit {
201            write!(f, " limit={}", limit)?;
202        }
203        // offset
204        if self.expr.offset > 0 {
205            write!(f, " offset={}", self.expr.offset)?;
206        }
207        // project
208        {
209            let project = Indices(&self.expr.project);
210            write!(f, " output=[{}]", project)?;
211        }
212        writeln!(f)
213    }
214}
215
216impl<'a, C, M> DisplayText<C> for HumanizedExpr<'a, MapFilterProject, M>
217where
218    C: AsMut<Indent>,
219    M: HumanizerMode,
220{
221    fn fmt_text(&self, f: &mut fmt::Formatter<'_>, ctx: &mut C) -> fmt::Result {
222        let (scalars, predicates, outputs, input_arity) = (
223            &self.expr.expressions,
224            &self.expr.predicates,
225            &self.expr.projection,
226            &self.expr.input_arity,
227        );
228
229        // render `project` field iff not the identity projection
230        if &outputs.len() != input_arity || outputs.iter().enumerate().any(|(i, p)| i != *p) {
231            let outputs = Indices(outputs);
232            writeln!(f, "{}project=({})", ctx.as_mut(), outputs)?;
233        }
234        // render `filter` field iff predicates are present
235        if !predicates.is_empty() {
236            let predicates = predicates.iter().map(|(_, p)| self.child(p));
237            let predicates = separated(" AND ", predicates);
238            writeln!(f, "{}filter=({})", ctx.as_mut(), predicates)?;
239        }
240        // render `map` field iff scalars are present
241        if !scalars.is_empty() {
242            let scalars = scalars.iter().map(|s| self.child(s));
243            let scalars = separated(", ", scalars);
244            writeln!(f, "{}map=({})", ctx.as_mut(), scalars)?;
245        }
246
247        Ok(())
248    }
249}
250
251impl<'a, M: HumanizerMode> HumanizedExpr<'a, MapFilterProject, M> {
252    /// Render an MFP using the the default (concise) syntax.
253    pub fn fmt_default_text<T>(
254        &self,
255        f: &mut fmt::Formatter<'_>,
256        ctx: &mut PlanRenderingContext<'_, T>,
257    ) -> fmt::Result {
258        if self.expr.projection.len() != self.expr.input_arity
259            || self
260                .expr
261                .projection
262                .iter()
263                .enumerate()
264                .any(|(i, p)| i != *p)
265        {
266            if self.expr.projection.len() == 0 {
267                writeln!(f, "{}Project: ()", ctx.indent)?;
268            } else {
269                let outputs = Indices(&self.expr.projection);
270                writeln!(f, "{}Project: {outputs}", ctx.indent)?;
271            }
272        }
273
274        // render `filter` field iff predicates are present
275        if !self.expr.predicates.is_empty() {
276            let predicates = self.expr.predicates.iter().map(|(_, p)| self.child(p));
277            let predicates = separated(" AND ", predicates);
278            writeln!(f, "{}Filter: {predicates}", ctx.indent)?;
279        }
280        // render `map` field iff scalars are present
281        if !self.expr.expressions.is_empty() {
282            let scalars = self.expr.expressions.iter().map(|s| self.child(s));
283            let scalars = separated(", ", scalars);
284            writeln!(f, "{}Map: {scalars}", ctx.indent)?;
285        }
286
287        Ok(())
288    }
289}
290
291/// `EXPLAIN ... AS TEXT` support for [`MirRelationExpr`].
292///
293/// The format adheres to the following conventions:
294/// 1. In general, every line corresponds to an [`MirRelationExpr`] node in the
295///    plan.
296/// 2. Non-recursive parameters of each sub-plan are written as `$key=$val`
297///    pairs on the same line.
298/// 3. A single non-recursive parameter can be written just as `$val`.
299/// 4. Exceptions in (1) can be made when virtual syntax is requested (done by
300///    default, can be turned off with `WITH(raw_syntax)`).
301/// 5. Exceptions in (2) can be made when join implementations are rendered
302///    explicitly `WITH(join_impls)`.
303impl DisplayText<PlanRenderingContext<'_, MirRelationExpr>> for MirRelationExpr {
304    fn fmt_text(
305        &self,
306        f: &mut fmt::Formatter<'_>,
307        ctx: &mut PlanRenderingContext<'_, MirRelationExpr>,
308    ) -> fmt::Result {
309        if ctx.config.verbose_syntax {
310            self.fmt_verbose_syntax(f, ctx)
311        } else {
312            self.fmt_default_syntax(f, ctx)
313        }
314    }
315}
316
317impl MirRelationExpr {
318    fn fmt_default_syntax(
319        &self,
320        f: &mut fmt::Formatter<'_>,
321        ctx: &mut PlanRenderingContext<'_, MirRelationExpr>,
322    ) -> fmt::Result {
323        // TODO(mgree) MIR does not support a different default syntax (yet!)
324        self.fmt_verbose_syntax(f, ctx)
325    }
326
327    fn fmt_verbose_syntax(
328        &self,
329        f: &mut fmt::Formatter<'_>,
330        ctx: &mut PlanRenderingContext<'_, MirRelationExpr>,
331    ) -> fmt::Result {
332        use MirRelationExpr::*;
333
334        let mode = HumanizedExplain::new(ctx.config.redacted);
335
336        match &self {
337            Constant { rows, typ: _ } => match rows {
338                Ok(rows) => {
339                    if !rows.is_empty() {
340                        write!(f, "{}Constant", ctx.indent)?;
341                        self.fmt_analyses(f, ctx)?;
342                        ctx.indented(|ctx| {
343                            fmt_text_constant_rows(
344                                f,
345                                rows.iter().map(|(x, y)| (x, y)),
346                                &mut ctx.indent,
347                                mode.redacted(),
348                            )
349                        })?;
350                    } else {
351                        write!(f, "{}Constant <empty>", ctx.indent)?;
352                        self.fmt_analyses(f, ctx)?;
353                    }
354                }
355                Err(err) => {
356                    if mode.redacted() {
357                        writeln!(f, "{}Error █", ctx.indent)?;
358                    } else {
359                        writeln!(f, "{}Error {}", ctx.indent, err.to_string().escaped())?;
360                    }
361                }
362            },
363            Let { id, value, body } => {
364                let mut bindings = vec![(id, value.as_ref())];
365                let mut head = body.as_ref();
366
367                // Render Let-blocks nested in the body an outer Let-block in one step
368                // with a flattened list of bindings
369                while let Let { id, value, body } = head {
370                    bindings.push((id, value.as_ref()));
371                    head = body.as_ref();
372                }
373
374                if ctx.config.linear_chains {
375                    writeln!(f, "{}With", ctx.indent)?;
376                    ctx.indented(|ctx| {
377                        for (id, value) in bindings.iter() {
378                            writeln!(f, "{}cte {} =", ctx.indent, *id)?;
379                            ctx.indented(|ctx| value.fmt_text(f, ctx))?;
380                        }
381                        Ok(())
382                    })?;
383                    write!(f, "{}Return", ctx.indent)?;
384                    self.fmt_analyses(f, ctx)?;
385                    ctx.indented(|ctx| head.fmt_text(f, ctx))?;
386                } else {
387                    writeln!(f, "{}With", ctx.indent)?;
388                    ctx.indented(|ctx| {
389                        for (id, value) in bindings.iter() {
390                            writeln!(f, "{}cte {} =", ctx.indent, *id)?;
391                            ctx.indented(|ctx| value.fmt_text(f, ctx))?;
392                        }
393                        Ok(())
394                    })?;
395                    write!(f, "{}Return", ctx.indent)?;
396                    self.fmt_analyses(f, ctx)?;
397                    ctx.indented(|ctx| head.fmt_text(f, ctx))?;
398                }
399            }
400            LetRec {
401                ids,
402                values,
403                limits,
404                body,
405            } => {
406                assert_eq!(ids.len(), values.len());
407                assert_eq!(ids.len(), limits.len());
408                let bindings =
409                    itertools::izip!(ids.iter(), values.iter(), limits.iter()).collect::<Vec<_>>();
410                let head = body.as_ref();
411
412                // Determine whether all `limits` are the same.
413                // If all of them are the same, then we print it on top of the block (or not print
414                // it at all if it's None). If there are differences, then we print them on the
415                // ctes.
416                let all_limits_same = limits
417                    .iter()
418                    .reduce(|first, i| if i == first { first } else { &None })
419                    .unwrap_or(&None);
420
421                if ctx.config.linear_chains {
422                    unreachable!(); // We exclude this case in `as_explain_single_plan`.
423                } else {
424                    write!(f, "{}With Mutually Recursive", ctx.indent)?;
425                    if let Some(limit) = all_limits_same {
426                        write!(f, " {}", limit)?;
427                    }
428                    writeln!(f)?;
429                    ctx.indented(|ctx| {
430                        for (id, value, limit) in bindings.iter() {
431                            write!(f, "{}cte", ctx.indent)?;
432                            if all_limits_same.is_none() {
433                                if let Some(limit) = limit {
434                                    write!(f, " {}", limit)?;
435                                }
436                            }
437                            writeln!(f, " {} =", id)?;
438                            ctx.indented(|ctx| value.fmt_text(f, ctx))?;
439                        }
440                        Ok(())
441                    })?;
442                    write!(f, "{}Return", ctx.indent)?;
443                    self.fmt_analyses(f, ctx)?;
444                    ctx.indented(|ctx| head.fmt_text(f, ctx))?;
445                }
446            }
447            Get {
448                id,
449                access_strategy: persist_or_index,
450                ..
451            } => {
452                match id {
453                    Id::Local(id) => {
454                        assert!(matches!(persist_or_index, AccessStrategy::UnknownOrLocal));
455                        write!(f, "{}Get {}", ctx.indent, id)?;
456                    }
457                    Id::Global(id) => {
458                        let humanize = |id: &GlobalId| {
459                            ctx.humanizer
460                                .humanize_id(*id)
461                                .unwrap_or_else(|| id.to_string())
462                        };
463                        let humanize_unqualified = |id: &GlobalId| {
464                            ctx.humanizer
465                                .humanize_id_unqualified(*id)
466                                .unwrap_or_else(|| id.to_string())
467                        };
468                        let humanize_unqualified_maybe_deleted = |id: &GlobalId| {
469                            ctx.humanizer
470                                .humanize_id_unqualified(*id)
471                                .unwrap_or_else(|| "[DELETED INDEX]".to_owned())
472                        };
473                        match persist_or_index {
474                            AccessStrategy::UnknownOrLocal => {
475                                write!(f, "{}Get {}", ctx.indent, humanize(id))?;
476                            }
477                            AccessStrategy::Persist => {
478                                write!(f, "{}ReadStorage {}", ctx.indent, humanize(id))?;
479                            }
480                            AccessStrategy::SameDataflow => {
481                                write!(
482                                    f,
483                                    "{}ReadGlobalFromSameDataflow {}",
484                                    ctx.indent,
485                                    humanize(id)
486                                )?;
487                            }
488                            AccessStrategy::Index(index_accesses) => {
489                                let mut grouped_index_accesses = BTreeMap::new();
490                                for (idx_id, usage_type) in index_accesses {
491                                    grouped_index_accesses
492                                        .entry(idx_id)
493                                        .or_insert(Vec::new())
494                                        .push(usage_type.clone());
495                                }
496                                write!(
497                                    f,
498                                    "{}ReadIndex on={} {}",
499                                    ctx.indent,
500                                    humanize_unqualified(id),
501                                    separated(
502                                        " ",
503                                        grouped_index_accesses.iter().map(
504                                            |(idx_id, usage_types)| {
505                                                closure_to_display(move |f| {
506                                                    write!(
507                                                        f,
508                                                        "{}=[{}]",
509                                                        humanize_unqualified_maybe_deleted(idx_id),
510                                                        IndexUsageType::display_vec(usage_types)
511                                                    )
512                                                })
513                                            }
514                                        )
515                                    ),
516                                )?;
517                            }
518                        }
519                    }
520                }
521                self.fmt_analyses(f, ctx)?;
522            }
523            Project { outputs, input } => {
524                FmtNode {
525                    fmt_root: |f, ctx| {
526                        let outputs = mode.seq(outputs, input.column_names(ctx));
527                        let outputs = CompactScalars(outputs);
528                        write!(f, "{}Project ({})", ctx.indent, outputs)?;
529                        self.fmt_analyses(f, ctx)
530                    },
531                    fmt_children: |f, ctx| input.fmt_text(f, ctx),
532                }
533                .render(f, ctx)?;
534            }
535            Map { scalars, input } => {
536                FmtNode {
537                    fmt_root: |f, ctx: &mut PlanRenderingContext<'_, MirRelationExpr>| {
538                        // Here, it's better to refer to `self.column_names(ctx)` rather than
539                        // `input.column_names(ctx)`, because then we also get humanization for refs
540                        // to cols introduced earlier by the same `Map`.
541                        let scalars = mode.seq(scalars, self.column_names(ctx));
542                        let scalars = CompactScalars(scalars);
543                        write!(f, "{}Map ({})", ctx.indent, scalars)?;
544                        self.fmt_analyses(f, ctx)
545                    },
546                    fmt_children: |f, ctx| input.fmt_text(f, ctx),
547                }
548                .render(f, ctx)?;
549            }
550            FlatMap { input, func, exprs } => {
551                FmtNode {
552                    fmt_root: |f, ctx: &mut PlanRenderingContext<'_, MirRelationExpr>| {
553                        let exprs = mode.seq(exprs, input.column_names(ctx));
554                        let exprs = CompactScalars(exprs);
555                        write!(f, "{}FlatMap {}({})", ctx.indent, func, exprs)?;
556                        self.fmt_analyses(f, ctx)
557                    },
558                    fmt_children: |f, ctx| input.fmt_text(f, ctx),
559                }
560                .render(f, ctx)?;
561            }
562            Filter { predicates, input } => {
563                FmtNode {
564                    fmt_root: |f, ctx: &mut PlanRenderingContext<'_, MirRelationExpr>| {
565                        if predicates.is_empty() {
566                            write!(f, "{}Filter", ctx.indent)?;
567                        } else {
568                            let cols = input.column_names(ctx);
569                            let predicates = mode.seq(predicates, cols);
570                            let predicates = separated(" AND ", predicates);
571                            write!(f, "{}Filter {}", ctx.indent, predicates)?;
572                        }
573                        self.fmt_analyses(f, ctx)
574                    },
575                    fmt_children: |f, ctx| input.fmt_text(f, ctx),
576                }
577                .render(f, ctx)?;
578            }
579            Join {
580                inputs,
581                equivalences,
582                implementation:
583                    implementation @ (JoinImplementation::Differential(..)
584                    | JoinImplementation::DeltaQuery(..)
585                    | JoinImplementation::Unimplemented),
586            } => {
587                let has_equivalences = !equivalences.is_empty();
588
589                if has_equivalences {
590                    let cols = self.column_names(ctx);
591                    let equivalences = separated(
592                        " AND ",
593                        equivalences.iter().map(|equivalence| {
594                            let equivalence = mode.seq(equivalence, cols);
595                            separated(" = ", equivalence)
596                        }),
597                    );
598                    write!(f, "{}Join on=({})", ctx.indent, equivalences)?;
599                } else {
600                    write!(f, "{}CrossJoin", ctx.indent)?;
601                }
602                if let Some(name) = implementation.name() {
603                    write!(f, " type={}", name)?;
604                }
605
606                self.fmt_analyses(f, ctx)?;
607
608                if ctx.config.join_impls {
609                    let input_name = &|pos: usize| -> String {
610                        // Dig out a Get (or IndexedFilter), and return the name of its Id.
611                        fn dig_name_from_expr(
612                            h: &dyn ExprHumanizer,
613                            e: &MirRelationExpr,
614                        ) -> Option<String> {
615                            let global_id_name = |id: &GlobalId| -> String {
616                                h.humanize_id_unqualified(*id)
617                                    .unwrap_or_else(|| id.to_string())
618                            };
619                            let (_mfp, e) = MapFilterProject::extract_from_expression(e);
620                            match e {
621                                Get { id, .. } => match id {
622                                    Id::Local(lid) => Some(lid.to_string()),
623                                    Id::Global(gid) => Some(global_id_name(gid)),
624                                },
625                                ArrangeBy { input, .. } => dig_name_from_expr(h, input),
626                                Join {
627                                    implementation: JoinImplementation::IndexedFilter(gid, ..),
628                                    ..
629                                } => Some(global_id_name(gid)),
630                                _ => None,
631                            }
632                        }
633                        match dig_name_from_expr(ctx.humanizer, &inputs[pos]) {
634                            Some(str) => format!("%{}:{}", pos, str),
635                            None => format!("%{}", pos),
636                        }
637                    };
638                    let join_key_to_string = |key: &Vec<MirScalarExpr>| -> String {
639                        if key.is_empty() {
640                            "×".to_owned()
641                        } else {
642                            CompactScalars(mode.seq(key, None)).to_string()
643                        }
644                    };
645                    let join_order = |start_idx: usize,
646                                      start_key: &Option<Vec<MirScalarExpr>>,
647                                      start_characteristics: &Option<JoinInputCharacteristics>,
648                                      tail: &Vec<(
649                        usize,
650                        Vec<MirScalarExpr>,
651                        Option<JoinInputCharacteristics>,
652                    )>|
653                     -> String {
654                        format!(
655                            "{}{}{} » {}",
656                            input_name(start_idx),
657                            match start_key {
658                                None => "".to_owned(),
659                                Some(key) => format!("[{}]", join_key_to_string(key)),
660                            },
661                            start_characteristics
662                                .as_ref()
663                                .map(|c| c.explain())
664                                .unwrap_or_else(|| "".to_string()),
665                            separated(
666                                " » ",
667                                tail.iter().map(|(pos, key, characteristics)| {
668                                    format!(
669                                        "{}[{}]{}",
670                                        input_name(*pos),
671                                        join_key_to_string(key),
672                                        characteristics
673                                            .as_ref()
674                                            .map(|c| c.explain())
675                                            .unwrap_or_else(|| "".to_string())
676                                    )
677                                })
678                            ),
679                        )
680                    };
681                    ctx.indented(|ctx| {
682                        match implementation {
683                            JoinImplementation::Differential(
684                                (start_idx, start_key, start_characteristics),
685                                tail,
686                            ) => {
687                                soft_assert_eq_or_log!(inputs.len(), tail.len() + 1);
688
689                                writeln!(f, "{}implementation", ctx.indent)?;
690                                ctx.indented(|ctx| {
691                                    writeln!(
692                                        f,
693                                        "{}{}",
694                                        ctx.indent,
695                                        join_order(
696                                            *start_idx,
697                                            start_key,
698                                            start_characteristics,
699                                            tail
700                                        )
701                                    )
702                                })?;
703                            }
704                            JoinImplementation::DeltaQuery(half_join_chains) => {
705                                soft_assert_eq_or_log!(inputs.len(), half_join_chains.len());
706
707                                writeln!(f, "{}implementation", ctx.indent)?;
708                                ctx.indented(|ctx| {
709                                    for (pos, chain) in half_join_chains.iter().enumerate() {
710                                        writeln!(
711                                            f,
712                                            "{}{}",
713                                            ctx.indent,
714                                            join_order(pos, &None, &None, chain)
715                                        )?;
716                                    }
717                                    Ok(())
718                                })?;
719                            }
720                            JoinImplementation::IndexedFilter(_, _, _, _) => {
721                                unreachable!() // because above we matched the other implementations
722                            }
723                            JoinImplementation::Unimplemented => {}
724                        }
725                        Ok(())
726                    })?;
727                }
728
729                ctx.indented(|ctx| {
730                    for input in inputs {
731                        input.fmt_text(f, ctx)?;
732                    }
733                    Ok(())
734                })?;
735            }
736            Join {
737                implementation:
738                    JoinImplementation::IndexedFilter(coll_id, idx_id, _key, literal_constraints),
739                inputs,
740                ..
741            } => {
742                let cse_id = match inputs.get(1).unwrap() {
743                    // If the constant input is actually a Get, then let `fmt_indexed_filter` know.
744                    Get { id, .. } => {
745                        if let Id::Local(local_id) = id {
746                            Some(local_id)
747                        } else {
748                            unreachable!()
749                        }
750                    }
751                    _ => None,
752                };
753                Self::fmt_indexed_filter(
754                    f,
755                    ctx,
756                    coll_id,
757                    idx_id,
758                    Some(literal_constraints.clone()),
759                    cse_id,
760                )?;
761                self.fmt_analyses(f, ctx)?;
762            }
763            Reduce {
764                group_key,
765                aggregates,
766                expected_group_size,
767                monotonic,
768                input,
769            } => {
770                FmtNode {
771                    fmt_root: |f, ctx: &mut PlanRenderingContext<'_, MirRelationExpr>| {
772                        if aggregates.len() == 0 && !ctx.config.raw_syntax {
773                            write!(f, "{}Distinct", ctx.indent)?;
774
775                            let group_key = mode.seq(group_key, input.column_names(ctx));
776                            let group_key = CompactScalars(group_key);
777                            write!(f, " project=[{}]", group_key)?;
778                        } else {
779                            write!(f, "{}Reduce", ctx.indent)?;
780
781                            if group_key.len() > 0 {
782                                let group_key = mode.seq(group_key, input.column_names(ctx));
783                                let group_key = CompactScalars(group_key);
784                                write!(f, " group_by=[{}]", group_key)?;
785                            }
786                        }
787                        if aggregates.len() > 0 {
788                            let cols = input.column_names(ctx);
789                            let aggregates = mode.seq(aggregates, cols);
790                            write!(f, " aggregates=[{}]", separated(", ", aggregates))?;
791                        }
792                        if *monotonic {
793                            write!(f, " monotonic")?;
794                        }
795                        if let Some(expected_group_size) = expected_group_size {
796                            write!(f, " exp_group_size={}", expected_group_size)?;
797                        }
798                        self.fmt_analyses(f, ctx)
799                    },
800                    fmt_children: |f, ctx| input.fmt_text(f, ctx),
801                }
802                .render(f, ctx)?;
803            }
804            TopK {
805                group_key,
806                order_key,
807                limit,
808                offset,
809                monotonic,
810                input,
811                expected_group_size,
812            } => {
813                FmtNode {
814                    fmt_root: |f, ctx: &mut PlanRenderingContext<'_, MirRelationExpr>| {
815                        write!(f, "{}TopK", ctx.indent)?;
816                        let cols = input.column_names(ctx);
817                        if group_key.len() > 0 {
818                            if cols.is_some() {
819                                let group_by = mode.seq(group_key, cols);
820                                write!(f, " group_by=[{}]", separated(", ", group_by))?;
821                            } else {
822                                let group_by = Indices(group_key);
823                                write!(f, " group_by=[{}]", group_by)?;
824                            }
825                        }
826                        if order_key.len() > 0 {
827                            let order_by = mode.seq(order_key, cols);
828                            write!(f, " order_by=[{}]", separated(", ", order_by))?;
829                        }
830                        if let Some(limit) = limit {
831                            let limit = mode.expr(limit, cols);
832                            write!(f, " limit={}", limit)?;
833                        }
834                        if offset > &0 {
835                            write!(f, " offset={}", offset)?
836                        }
837                        if *monotonic {
838                            write!(f, " monotonic")?;
839                        }
840                        if let Some(expected_group_size) = expected_group_size {
841                            write!(f, " exp_group_size={}", expected_group_size)?;
842                        }
843                        self.fmt_analyses(f, ctx)
844                    },
845                    fmt_children: |f, ctx| input.fmt_text(f, ctx),
846                }
847                .render(f, ctx)?;
848            }
849            Negate { input } => {
850                FmtNode {
851                    fmt_root: |f, ctx| {
852                        write!(f, "{}Negate", ctx.indent)?;
853                        self.fmt_analyses(f, ctx)
854                    },
855                    fmt_children: |f, ctx| input.fmt_text(f, ctx),
856                }
857                .render(f, ctx)?;
858            }
859            Threshold { input } => {
860                FmtNode {
861                    fmt_root: |f, ctx| {
862                        write!(f, "{}Threshold", ctx.indent)?;
863                        self.fmt_analyses(f, ctx)
864                    },
865                    fmt_children: |f, ctx| input.fmt_text(f, ctx),
866                }
867                .render(f, ctx)?;
868            }
869            Union { base, inputs } => {
870                write!(f, "{}Union", ctx.indent)?;
871                self.fmt_analyses(f, ctx)?;
872                ctx.indented(|ctx| {
873                    base.fmt_text(f, ctx)?;
874                    for input in inputs.iter() {
875                        input.fmt_text(f, ctx)?;
876                    }
877                    Ok(())
878                })?;
879            }
880            ArrangeBy { input, keys } => {
881                FmtNode {
882                    fmt_root: |f, ctx: &mut PlanRenderingContext<'_, MirRelationExpr>| {
883                        write!(f, "{}ArrangeBy", ctx.indent)?;
884
885                        let keys = keys.iter().map(|key| {
886                            let key = mode.seq(key, input.column_names(ctx));
887                            CompactScalars(key)
888                        });
889                        let keys = separated("], [", keys);
890                        write!(f, " keys=[[{}]]", keys)?;
891
892                        self.fmt_analyses(f, ctx)
893                    },
894                    fmt_children: |f, ctx| input.fmt_text(f, ctx),
895                }
896                .render(f, ctx)?;
897            }
898        }
899
900        Ok(())
901    }
902
903    fn fmt_analyses(
904        &self,
905        f: &mut fmt::Formatter<'_>,
906        ctx: &PlanRenderingContext<'_, MirRelationExpr>,
907    ) -> fmt::Result {
908        if ctx.config.requires_analyses() {
909            if let Some(analyses) = ctx.annotations.get(self) {
910                writeln!(f, " {}", HumanizedAnalyses::new(analyses, ctx))
911            } else {
912                writeln!(f, " // error: no analyses for subtree in map")
913            }
914        } else {
915            writeln!(f)
916        }
917    }
918
919    fn column_names<'a>(
920        &'a self,
921        ctx: &'a PlanRenderingContext<'_, MirRelationExpr>,
922    ) -> Option<&'a Vec<String>> {
923        if !ctx.config.humanized_exprs {
924            None
925        } else if let Some(analyses) = ctx.annotations.get(self) {
926            analyses.column_names.as_ref()
927        } else {
928            None
929        }
930    }
931
932    pub fn fmt_indexed_filter<'a, T>(
933        f: &mut fmt::Formatter<'_>,
934        ctx: &mut PlanRenderingContext<'a, T>,
935        coll_id: &GlobalId, // The id of the collection that the index is on
936        idx_id: &GlobalId,  // The id of the index
937        constants: Option<Vec<Row>>, // The values that we are looking up
938        cse_id: Option<&LocalId>, // Sometimes, RelationCSE pulls out the const input
939    ) -> fmt::Result {
940        let mode = HumanizedExplain::new(ctx.config.redacted);
941
942        let humanized_coll = ctx
943            .humanizer
944            .humanize_id(*coll_id)
945            .unwrap_or_else(|| coll_id.to_string());
946        let humanized_index = ctx
947            .humanizer
948            .humanize_id_unqualified(*idx_id)
949            .unwrap_or_else(|| "[DELETED INDEX]".to_owned());
950        if let Some(constants) = constants {
951            write!(
952                f,
953                "{}ReadIndex on={} {}=[{} ",
954                ctx.as_mut(),
955                humanized_coll,
956                humanized_index,
957                IndexUsageType::Lookup(*idx_id),
958            )?;
959            if let Some(cse_id) = cse_id {
960                // If we were to simply print `constants` here, then the EXPLAIN output would look
961                // weird: It would look like as if there was a dangling cte, because we (probably)
962                // wouldn't be printing any Get that refers to that cte.
963                write!(f, "values=<Get {}>]", cse_id)?;
964            } else {
965                if constants.len() == 1 {
966                    let value = mode.expr(&constants[0], None);
967                    write!(f, "value={}]", value)?;
968                } else {
969                    let values = mode.seq(&constants, None);
970                    write!(f, "values=[{}]]", separated("; ", values))?;
971                }
972            }
973        } else {
974            // Can't happen in dataflow, only in fast path.
975            write!(
976                f,
977                "{}ReadIndex on={} {}=[{}]",
978                ctx.indent,
979                humanized_coll,
980                humanized_index,
981                IndexUsageType::FullScan
982            )?;
983        }
984        Ok(())
985    }
986}
987
988/// A helper struct that abstracts over the formatting behavior of a
989/// single-input node.
990///
991/// If [`mz_repr::explain::ExplainConfig::linear_chains`] is set, this will
992/// render children before parents using the same indentation level, and if not
993/// the children will be rendered indented after their parent.
994struct FmtNode<F, G>
995where
996    F: FnOnce(
997        &mut fmt::Formatter<'_>,
998        &mut PlanRenderingContext<'_, MirRelationExpr>,
999    ) -> fmt::Result,
1000    G: FnOnce(
1001        &mut fmt::Formatter<'_>,
1002        &mut PlanRenderingContext<'_, MirRelationExpr>,
1003    ) -> fmt::Result,
1004{
1005    fmt_root: F,
1006    fmt_children: G,
1007}
1008
1009impl<F, G> FmtNode<F, G>
1010where
1011    F: FnOnce(
1012        &mut fmt::Formatter<'_>,
1013        &mut PlanRenderingContext<'_, MirRelationExpr>,
1014    ) -> fmt::Result,
1015    G: FnOnce(
1016        &mut fmt::Formatter<'_>,
1017        &mut PlanRenderingContext<'_, MirRelationExpr>,
1018    ) -> fmt::Result,
1019{
1020    fn render(
1021        self,
1022        f: &mut fmt::Formatter<'_>,
1023        ctx: &mut PlanRenderingContext<'_, MirRelationExpr>,
1024    ) -> fmt::Result {
1025        let FmtNode {
1026            fmt_root,
1027            fmt_children,
1028        } = self;
1029        if ctx.config.linear_chains {
1030            // Render children before parents
1031            fmt_children(f, ctx)?;
1032            fmt_root(f, ctx)?;
1033        } else {
1034            // Render children indented after parent
1035            fmt_root(f, ctx)?;
1036            // cannot use ctx.indented() here
1037            *ctx.as_mut() += 1;
1038            fmt_children(f, ctx)?;
1039            *ctx.as_mut() -= 1;
1040        }
1041        Ok(())
1042    }
1043}
1044
1045impl MirScalarExpr {
1046    pub fn format(&self, f: &mut fmt::Formatter<'_>, cols: Option<&Vec<String>>) -> fmt::Result {
1047        let mode = HumanizedExplain::default();
1048        fmt::Display::fmt(&mode.expr(self, cols), f)
1049    }
1050}
1051
1052impl fmt::Display for MirScalarExpr {
1053    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1054        self.format(f, None)
1055    }
1056}
1057
1058/// A helper struct for wrapping expressions whose text output is modulated by
1059/// the presence of some local (inferred schema) or global (required redaction)
1060/// context.
1061#[derive(Debug, Clone)]
1062pub struct HumanizedExpr<'a, T, M = HumanizedExplain> {
1063    /// The expression to be humanized.
1064    pub expr: &'a T,
1065    /// An optional vector of inferred column names to be used when rendering
1066    /// column references in `expr`.
1067    pub cols: Option<&'a Vec<String>>,
1068    /// The rendering mode to use. See [`HumanizerMode`] for details.
1069    pub mode: M,
1070}
1071
1072impl<'a, T, M: HumanizerMode> HumanizedExpr<'a, T, M> {
1073    /// Wrap the given child `expr` into a [`HumanizedExpr`] using the same
1074    /// `cols` and `mode` as `self`.
1075    pub fn child<U>(&self, expr: &'a U) -> HumanizedExpr<'a, U, M> {
1076        HumanizedExpr {
1077            expr,
1078            cols: self.cols,
1079            mode: self.mode.clone(),
1080        }
1081    }
1082}
1083
1084/// A trait that abstracts the various ways in which we can humanize
1085/// expressions.
1086///
1087/// Currently, the degrees of freedom are:
1088/// - Humanizing for an `EXPLAIN` output vs humanizing for a notice output. This
1089///   is currently handled by the two different implementations of this trait -
1090///   [`HumanizedExplain`] vs [`HumanizedNotice`].
1091/// - Humanizing with redacted or non-redacted literals. This is currently
1092///   covered by the [`HumanizerMode::redacted`] method which is used by the
1093///   default implementation of [`HumanizerMode::humanize_datum`].
1094pub trait HumanizerMode: Sized + Clone {
1095    /// Default implementation of a default constructor.
1096    ///
1097    /// This will produce a [`HumanizerMode`] instance that redacts output in
1098    /// production deployments, but not in debug builds and in CI.
1099    fn default() -> Self {
1100        let redacted = !mz_ore::assert::soft_assertions_enabled();
1101        Self::new(redacted)
1102    }
1103
1104    /// Create a new instance of the optimizer mode with literal redaction
1105    /// determined by the `redacted` parameter value.
1106    fn new(redacted: bool) -> Self;
1107
1108    /// Factory method that wraps the given `expr` and `cols` into a
1109    /// [`HumanizedExpr`] with the current `mode`.
1110    fn expr<'a, T>(
1111        &self,
1112        expr: &'a T,
1113        cols: Option<&'a Vec<String>>,
1114    ) -> HumanizedExpr<'a, T, Self> {
1115        HumanizedExpr {
1116            expr,
1117            cols,
1118            mode: self.clone(),
1119        }
1120    }
1121
1122    /// Return `true` iff literal redaction is enabled for this mode.
1123    fn redacted(&self) -> bool;
1124
1125    /// Render reference to column `col` which resolves to the given `ident`.
1126    fn humanize_ident(col: usize, ident: Ident, f: &mut fmt::Formatter<'_>) -> fmt::Result;
1127
1128    /// Render a literal datum.
1129    ///
1130    /// The default implementation prints a redacted symbol (█) if redaction is
1131    /// enabled.
1132    fn humanize_datum(&self, datum: Datum<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1133        if self.redacted() {
1134            write!(f, "█")
1135        } else {
1136            write!(f, "{}", datum)
1137        }
1138    }
1139
1140    /// Wrap the given `exprs` (with `cols`) into [`HumanizedExpr`] with the current `mode`.
1141    fn seq<'i, T>(
1142        &self,
1143        exprs: &'i [T],
1144        cols: Option<&'i Vec<String>>,
1145    ) -> impl Iterator<Item = HumanizedExpr<'i, T, Self>> + Clone {
1146        exprs.iter().map(move |expr| self.expr(expr, cols))
1147    }
1148}
1149
1150/// A [`HumanizerMode`] that is ambiguous but allows us to print valid SQL
1151/// statements, so it should be used in optimizer notices.
1152///
1153/// The inner parameter is `true` iff literals should be redacted.
1154#[derive(Debug, Clone)]
1155pub struct HumanizedNotice(bool);
1156
1157impl HumanizerMode for HumanizedNotice {
1158    fn new(redacted: bool) -> Self {
1159        Self(redacted)
1160    }
1161
1162    fn redacted(&self) -> bool {
1163        self.0
1164    }
1165
1166    /// Write `ident`.
1167    fn humanize_ident(_col: usize, ident: Ident, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1168        write!(f, "{ident}")
1169    }
1170}
1171
1172/// A [`HumanizerMode`] that is unambiguous and should be used in `EXPLAIN` output.
1173///
1174/// The inner parameter is `true` iff literals should be redacted.
1175#[derive(Debug, Clone)]
1176pub struct HumanizedExplain(bool);
1177
1178impl HumanizerMode for HumanizedExplain {
1179    fn new(redacted: bool) -> Self {
1180        Self(redacted)
1181    }
1182
1183    fn redacted(&self) -> bool {
1184        self.0
1185    }
1186
1187    /// Write `#c{ident}`.
1188    fn humanize_ident(col: usize, ident: Ident, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1189        write!(f, "#{col}{{{ident}}}")
1190    }
1191}
1192
1193// A usize that directly represents a column reference.
1194impl<'a, M> fmt::Display for HumanizedExpr<'a, usize, M>
1195where
1196    M: HumanizerMode,
1197{
1198    #[inline]
1199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1200        match self.cols {
1201            // We have a name inferred for the column indexed by `self.expr`. Write `ident`.
1202            Some(cols) if cols.len() > *self.expr && !cols[*self.expr].is_empty() => {
1203                // Note: using unchecked here is okay since we're directly
1204                // converting to a string afterwards.
1205                let ident = Ident::new_unchecked(cols[*self.expr].clone()); // TODO: try to avoid the `.clone()` here.
1206                M::humanize_ident(*self.expr, ident, f)
1207            }
1208            // We don't have name inferred for this column.
1209            _ => {
1210                // Write `#c`.
1211                write!(f, "#{}", self.expr)
1212            }
1213        }
1214    }
1215}
1216
1217// A column reference with a stored name.
1218// We defer to the inferred name, if available.
1219impl<'a, M> fmt::Display for HumanizedExpr<'a, (&usize, &Arc<str>), M>
1220where
1221    M: HumanizerMode,
1222{
1223    #[inline]
1224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1225        match self.cols {
1226            // We have a name inferred for the column indexed by `self.expr`. Write `ident`.
1227            Some(cols) if cols.len() > *self.expr.0 && !cols[*self.expr.0].is_empty() => {
1228                // Note: using unchecked here is okay since we're directly
1229                // converting to a string afterwards.
1230                let ident = Ident::new_unchecked(cols[*self.expr.0].clone()); // TODO: try to avoid the `.clone()` here.
1231                M::humanize_ident(*self.expr.0, ident, f)
1232            }
1233            // We don't have name inferred for this column.
1234            _ => {
1235                // Don't show dummy column names.
1236                if self.expr.1.as_ref() == "\"?column?\"" {
1237                    write!(f, "#{}", self.expr.0)
1238                } else {
1239                    M::humanize_ident(*self.expr.0, Ident::new_unchecked(self.expr.1.as_ref()), f)
1240                }
1241            }
1242        }
1243    }
1244}
1245
1246impl<'a, M> ScalarOps for HumanizedExpr<'a, MirScalarExpr, M> {
1247    fn match_col_ref(&self) -> Option<usize> {
1248        self.expr.match_col_ref()
1249    }
1250
1251    fn references(&self, col_ref: usize) -> bool {
1252        self.expr.references(col_ref)
1253    }
1254}
1255
1256impl<'a, M> ScalarOps for HumanizedExpr<'a, usize, M> {
1257    fn match_col_ref(&self) -> Option<usize> {
1258        Some(*self.expr)
1259    }
1260
1261    fn references(&self, col_ref: usize) -> bool {
1262        col_ref == *self.expr
1263    }
1264}
1265
1266impl<'a, M> fmt::Display for HumanizedExpr<'a, MirScalarExpr, M>
1267where
1268    M: HumanizerMode,
1269{
1270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1271        use MirScalarExpr::*;
1272
1273        match self.expr {
1274            Column(i, TreatAsEqual(None)) => {
1275                // Delegate to the `HumanizedExpr<'a, _>` implementation (plain column reference).
1276                self.child(i).fmt(f)
1277            }
1278            Column(i, TreatAsEqual(Some(name))) => {
1279                // Delegate to the `HumanizedExpr<'a, _>` implementation (with stored name information)
1280                self.child(&(i, name)).fmt(f)
1281            }
1282            Literal(row, _) => {
1283                // Delegate to the `HumanizedExpr<'a, _>` implementation.
1284                self.child(row).fmt(f)
1285            }
1286            CallUnmaterializable(func) => write!(f, "{}()", func),
1287            CallUnary { func, expr } => {
1288                if let crate::UnaryFunc::Not(_) = *func {
1289                    if let CallUnary { func, expr } = expr.as_ref() {
1290                        if let Some(is) = func.is() {
1291                            let expr = self.child::<MirScalarExpr>(&*expr);
1292                            return write!(f, "({}) IS NOT {}", expr, is);
1293                        }
1294                    }
1295                }
1296                if let Some(is) = func.is() {
1297                    let expr = self.child::<MirScalarExpr>(&*expr);
1298                    write!(f, "({}) IS {}", expr, is)
1299                } else {
1300                    let expr = self.child::<MirScalarExpr>(&*expr);
1301                    write!(f, "{}({})", func, expr)
1302                }
1303            }
1304            CallBinary { func, expr1, expr2 } => {
1305                let expr1 = self.child::<MirScalarExpr>(&*expr1);
1306                let expr2 = self.child::<MirScalarExpr>(&*expr2);
1307                if func.is_infix_op() {
1308                    write!(f, "({} {} {})", expr1, func, expr2)
1309                } else {
1310                    write!(f, "{}({}, {})", func, expr1, expr2)
1311                }
1312            }
1313            CallVariadic { func, exprs } => {
1314                use crate::VariadicFunc::*;
1315                let exprs = exprs.iter().map(|expr| self.child(expr));
1316                match func {
1317                    ArrayCreate { .. } => {
1318                        let exprs = separated(", ", exprs);
1319                        write!(f, "array[{}]", exprs)
1320                    }
1321                    ListCreate { .. } => {
1322                        let exprs = separated(", ", exprs);
1323                        write!(f, "list[{}]", exprs)
1324                    }
1325                    RecordCreate { .. } => {
1326                        let exprs = separated(", ", exprs);
1327                        write!(f, "row({})", exprs)
1328                    }
1329                    func if func.is_infix_op() && exprs.len() > 1 => {
1330                        let func = format!(" {} ", func);
1331                        let exprs = separated(&func, exprs);
1332                        write!(f, "({})", exprs)
1333                    }
1334                    func => {
1335                        let exprs = separated(", ", exprs);
1336                        write!(f, "{}({})", func, exprs)
1337                    }
1338                }
1339            }
1340            If { cond, then, els } => {
1341                let cond = self.child::<MirScalarExpr>(&*cond);
1342                let then = self.child::<MirScalarExpr>(&*then);
1343                let els = self.child::<MirScalarExpr>(&*els);
1344                write!(f, "case when {} then {} else {} end", cond, then, els)
1345            }
1346        }
1347    }
1348}
1349
1350impl<'a, M> fmt::Display for HumanizedExpr<'a, AggregateExpr, M>
1351where
1352    M: HumanizerMode,
1353{
1354    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1355        if self.expr.is_count_asterisk() {
1356            return write!(f, "count(*)");
1357        }
1358
1359        write!(
1360            f,
1361            "{}({}",
1362            self.child(&self.expr.func),
1363            if self.expr.distinct { "distinct " } else { "" }
1364        )?;
1365
1366        self.child(&self.expr.expr).fmt(f)?;
1367        write!(f, ")")
1368    }
1369}
1370
1371/// Render a literal value represented as a single-element [`Row`] or an
1372/// [`EvalError`].
1373///
1374/// The default implementation calls [`HumanizerMode::humanize_datum`] for
1375/// the former and handles the error case (including redaction) directly for
1376/// the latter.
1377impl<'a, M> fmt::Display for HumanizedExpr<'a, Result<Row, EvalError>, M>
1378where
1379    M: HumanizerMode,
1380{
1381    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1382        match self.expr {
1383            Ok(row) => self.mode.humanize_datum(row.unpack_first(), f),
1384            Err(err) => {
1385                if self.mode.redacted() {
1386                    write!(f, "error(█)")
1387                } else {
1388                    write!(f, "error({})", err.to_string().escaped())
1389                }
1390            }
1391        }
1392    }
1393}
1394
1395impl<'a, M> fmt::Display for HumanizedExpr<'a, Datum<'a>, M>
1396where
1397    M: HumanizerMode,
1398{
1399    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1400        self.mode.humanize_datum(*self.expr, f)
1401    }
1402}
1403
1404impl<'a, M> fmt::Display for HumanizedExpr<'a, Row, M>
1405where
1406    M: HumanizerMode,
1407{
1408    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1409        f.write_str("(")?;
1410        for (i, d) in self.expr.iter().enumerate() {
1411            if i > 0 {
1412                write!(f, ", {}", self.child(&d))?;
1413            } else {
1414                write!(f, "{}", self.child(&d))?;
1415            }
1416        }
1417        f.write_str(")")?;
1418        Ok(())
1419    }
1420}
1421
1422pub fn fmt_text_constant_rows<'a, I>(
1423    f: &mut fmt::Formatter<'_>,
1424    mut rows: I,
1425    ctx: &mut Indent,
1426    redacted: bool,
1427) -> fmt::Result
1428where
1429    I: Iterator<Item = (&'a Row, &'a Diff)>,
1430{
1431    let mut row_count = Diff::ZERO;
1432    let mut first_rows = Vec::with_capacity(20);
1433    for _ in 0..20 {
1434        if let Some((row, diff)) = rows.next() {
1435            row_count += diff.abs();
1436            first_rows.push((row, diff));
1437        }
1438    }
1439    let rest_of_row_count = rows.map(|(_, diff)| diff.abs()).sum::<Diff>();
1440    if !rest_of_row_count.is_zero() {
1441        writeln!(
1442            f,
1443            "{}total_rows (diffs absed): {}",
1444            ctx,
1445            row_count + rest_of_row_count
1446        )?;
1447        writeln!(f, "{}first_rows:", ctx)?;
1448        ctx.indented(move |ctx| write_first_rows(f, &first_rows, ctx, redacted))?;
1449    } else {
1450        write_first_rows(f, &first_rows, ctx, redacted)?;
1451    }
1452    Ok(())
1453}
1454
1455fn write_first_rows(
1456    f: &mut fmt::Formatter<'_>,
1457    first_rows: &Vec<(&Row, &Diff)>,
1458    ctx: &Indent,
1459    redacted: bool,
1460) -> fmt::Result {
1461    let mode = HumanizedExplain::new(redacted);
1462    for (row, diff) in first_rows {
1463        let row = mode.expr(*row, None);
1464        if **diff == Diff::ONE {
1465            writeln!(f, "{}- {}", ctx, row)?;
1466        } else {
1467            writeln!(f, "{}- ({} x {})", ctx, row, diff)?;
1468        }
1469    }
1470    Ok(())
1471}