1use std::collections::BTreeMap;
13use std::fmt::{self, Display as _};
14use std::sync::Arc;
15
16use itertools::Itertools;
17use mz_ore::soft_assert_eq_or_log;
18use mz_ore::str::{Indent, IndentLike, StrExt, closure_to_display, separated};
19use mz_ore::treat_as_equal::TreatAsEqual;
20use mz_repr::explain::text::DisplayText;
21use mz_repr::explain::{
22 CompactScalars, ExprHumanizer, HumanizedAnalyses, IndexUsageType, Indices,
23 PlanRenderingContext, RenderingContext, ScalarOps,
24};
25use mz_repr::{Datum, Diff, GlobalId, Row, StableRow, UNKNOWN_COLUMN_NAME};
26use mz_sql_parser::ast::Ident;
27
28use crate::explain::{ExplainMultiPlan, ExplainSinglePlan, SourceOperators};
29use crate::{
30 AccessStrategy, AggregateExpr, EvalError, Id, JoinImplementation, JoinInputCharacteristics,
31 LocalId, MapFilterProject, MirRelationExpr, MirScalarExpr, OptimizableExpr, RowSetFinishing,
32};
33
34impl<'a, T: 'a> DisplayText for ExplainSinglePlan<'a, T>
35where
36 T: DisplayText<PlanRenderingContext<'a, T>> + Ord,
37{
38 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, _ctx: &mut ()) -> fmt::Result {
39 let mut ctx = PlanRenderingContext::new(
40 Indent::default(),
41 self.context.humanizer,
42 self.plan.annotations.clone(),
43 self.context.config,
44 self.context
45 .used_indexes
46 .ambiguous_ids(self.context.humanizer),
47 );
48
49 let mode = HumanizedExplain::new(self.context.config.redacted);
50
51 if let Some(finishing) = &self.context.finishing {
52 if ctx.config.humanized_exprs {
53 let analyses = ctx.annotations.get(&self.plan.plan);
54 let cols = analyses
55 .map(|analyses| analyses.column_names.clone())
56 .flatten();
57 mode.expr(finishing, cols.as_ref()).fmt_text(f, &mut ctx)?;
58 } else {
59 mode.expr(finishing, None).fmt_text(f, &mut ctx)?;
60 }
61 ctx.indented(|ctx| self.plan.plan.fmt_text(f, ctx))?;
62 } else {
63 self.plan.plan.fmt_text(f, &mut ctx)?;
64 }
65
66 if !self.context.used_indexes.is_empty() {
67 writeln!(f)?;
68 self.context.used_indexes.fmt_text(f, &mut ctx)?;
69 }
70
71 if let Some(target_cluster) = self.context.target_cluster {
72 writeln!(f)?;
73 writeln!(f, "Target cluster: {}", target_cluster)?;
74 }
75
76 if !self.context.optimizer_notices.is_empty() {
77 writeln!(f)?;
78 writeln!(f, "Notices:")?;
79 for notice in self.context.optimizer_notices.iter() {
80 writeln!(f, "{}", notice)?;
81 }
82 }
83
84 if self.context.config.timing {
85 writeln!(f)?;
86 writeln!(f, "Optimization time: {:?}", self.context.duration)?;
87 }
88
89 Ok(())
90 }
91}
92
93impl<'a, T: 'a, O> DisplayText for ExplainMultiPlan<'a, T, O>
94where
95 T: DisplayText<PlanRenderingContext<'a, T>> + Ord,
96 O: SourceOperators,
97{
98 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, _ctx: &mut ()) -> fmt::Result {
99 let mut ctx = RenderingContext::new(Indent::default(), self.context.humanizer);
100
101 let mode = HumanizedExplain::new(self.context.config.redacted);
102
103 for (no, (id, plan)) in self.plans.iter().enumerate() {
105 let mut ctx = PlanRenderingContext::new(
106 ctx.indent.clone(),
107 ctx.humanizer,
108 plan.annotations.clone(),
109 self.context.config,
110 self.context
111 .used_indexes
112 .ambiguous_ids(self.context.humanizer),
113 );
114
115 if no > 0 {
116 writeln!(f)?;
117 }
118
119 writeln!(f, "{}{}:", ctx.indent, id)?;
120 ctx.indented(|ctx| {
121 match &self.context.finishing {
122 Some(finishing) if no == 0 => {
124 if ctx.config.humanized_exprs {
125 let analyses = ctx.annotations.get(plan.plan);
126 let cols = analyses
127 .map(|analyses| analyses.column_names.clone())
128 .flatten();
129 mode.expr(finishing, cols.as_ref()).fmt_text(f, ctx)?;
130 } else {
131 mode.expr(finishing, None).fmt_text(f, ctx)?;
132 };
133 ctx.indented(|ctx| plan.plan.fmt_text(f, ctx))?;
134 }
135 _ => {
137 plan.plan.fmt_text(f, ctx)?;
138 }
139 }
140 Ok(())
141 })?;
142 }
143
144 if self.sources.iter().any(|src| !src.is_identity()) {
145 if !self.plans.is_empty() {
148 writeln!(f)?;
149 }
150 for src in self.sources.iter().filter(|src| !src.is_identity()) {
151 if self.context.config.humanized_exprs {
152 let mut cols = ctx.humanizer.column_names_for_id(src.id);
153 if let Some(cols) = cols.as_mut() {
157 let anonymous = std::iter::repeat(String::new());
158 cols.extend(
159 anonymous.take(src.op.as_ref().map_or(0, |op| op.appended_columns())),
160 )
161 };
162 mode.expr(src, cols.as_ref()).fmt_text(f, &mut ctx)?;
164 } else {
165 mode.expr(src, None).fmt_text(f, &mut ctx)?;
167 }
168 }
169 }
170
171 if !self.context.used_indexes.is_empty() {
172 writeln!(f)?;
173 self.context.used_indexes.fmt_text(f, &mut ctx)?;
174 }
175
176 if let Some(target_cluster) = self.context.target_cluster {
177 writeln!(f)?;
178 writeln!(f, "Target cluster: {}", target_cluster)?;
179 }
180
181 if !(self.context.config.no_notices || self.context.optimizer_notices.is_empty()) {
182 writeln!(f)?;
183 writeln!(f, "Notices:")?;
184 for notice in self.context.optimizer_notices.iter() {
185 writeln!(f, "{}", notice)?;
186 }
187 }
188
189 if self.context.config.timing {
190 writeln!(f)?;
191 writeln!(f, "Optimization time: {:?}", self.context.duration)?;
192 }
193
194 Ok(())
195 }
196}
197
198impl<'a, C, M> DisplayText<C> for HumanizedExpr<'a, RowSetFinishing, M>
199where
200 C: AsMut<Indent>,
201 M: HumanizerMode,
202{
203 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, ctx: &mut C) -> fmt::Result {
204 write!(f, "{}Finish", ctx.as_mut())?;
205 if !self.expr.order_by.is_empty() {
207 let order_by = self.expr.order_by.iter().map(|e| self.child(e));
208 write!(f, " order_by=[{}]", separated(", ", order_by))?;
209 }
210 if let Some(limit) = self.expr.limit {
212 write!(f, " limit={}", limit)?;
213 }
214 if self.expr.offset > 0 {
216 write!(f, " offset={}", self.expr.offset)?;
217 }
218 {
220 let project = Indices(&self.expr.project);
221 write!(f, " output=[{}]", project)?;
222 }
223 writeln!(f)
224 }
225}
226
227impl<'a, C, M, E> DisplayText<C> for HumanizedExpr<'a, MapFilterProject<E>, M>
228where
229 C: AsMut<Indent>,
230 M: HumanizerMode,
231 E: OptimizableExpr + HumanizeDisplay,
232{
233 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, ctx: &mut C) -> fmt::Result {
234 let (scalars, predicates, outputs, input_arity) = (
235 &self.expr.expressions,
236 &self.expr.predicates,
237 &self.expr.projection,
238 &self.expr.input_arity,
239 );
240
241 if &outputs.len() != input_arity || outputs.iter().enumerate().any(|(i, p)| i != *p) {
243 let outputs = Indices(outputs);
244 writeln!(f, "{}project=({})", ctx.as_mut(), outputs)?;
245 }
246 if !predicates.is_empty() {
248 let predicates = predicates.iter().map(|(_, p)| self.child(p));
249 let predicates = separated(" AND ", predicates);
250 writeln!(f, "{}filter=({})", ctx.as_mut(), predicates)?;
251 }
252 if !scalars.is_empty() {
254 let scalars = scalars.iter().map(|s| self.child(s));
255 let scalars = separated(", ", scalars);
256 writeln!(f, "{}map=({})", ctx.as_mut(), scalars)?;
257 }
258
259 Ok(())
260 }
261}
262
263impl<'a, M: HumanizerMode, E: OptimizableExpr + HumanizeDisplay>
264 HumanizedExpr<'a, MapFilterProject<E>, M>
265{
266 pub fn fmt_default_text<T>(
268 &self,
269 f: &mut fmt::Formatter<'_>,
270 ctx: &mut PlanRenderingContext<'_, T>,
271 ) -> fmt::Result {
272 if self.expr.projection.len() != self.expr.input_arity
273 || self
274 .expr
275 .projection
276 .iter()
277 .enumerate()
278 .any(|(i, p)| i != *p)
279 {
280 if self.expr.projection.len() == 0 {
281 writeln!(f, "{}Project: ()", ctx.indent)?;
282 } else {
283 let outputs = Indices(&self.expr.projection);
284 writeln!(f, "{}Project: {outputs}", ctx.indent)?;
285 }
286 }
287
288 if !self.expr.predicates.is_empty() {
290 let predicates = self.expr.predicates.iter().map(|(_, p)| self.child(p));
291 let predicates = separated(" AND ", predicates);
292 writeln!(f, "{}Filter: {predicates}", ctx.indent)?;
293 }
294 if !self.expr.expressions.is_empty() {
296 let scalars = self.expr.expressions.iter().map(|s| self.child(s));
297 let scalars = separated(", ", scalars);
298 writeln!(f, "{}Map: {scalars}", ctx.indent)?;
299 }
300
301 Ok(())
302 }
303}
304
305impl DisplayText<PlanRenderingContext<'_, MirRelationExpr>> for MirRelationExpr {
318 fn fmt_text(
319 &self,
320 f: &mut fmt::Formatter<'_>,
321 ctx: &mut PlanRenderingContext<'_, MirRelationExpr>,
322 ) -> fmt::Result {
323 if ctx.config.verbose_syntax {
324 self.fmt_verbose_syntax(f, ctx)
325 } else {
326 self.fmt_default_syntax(f, ctx)
327 }
328 }
329}
330
331impl MirRelationExpr {
332 fn fmt_default_syntax(
333 &self,
334 f: &mut fmt::Formatter<'_>,
335 ctx: &mut PlanRenderingContext<'_, MirRelationExpr>,
336 ) -> fmt::Result {
337 self.fmt_verbose_syntax(f, ctx)
339 }
340
341 fn fmt_verbose_syntax(
342 &self,
343 f: &mut fmt::Formatter<'_>,
344 ctx: &mut PlanRenderingContext<'_, MirRelationExpr>,
345 ) -> fmt::Result {
346 use MirRelationExpr::*;
347
348 let mode = HumanizedExplain::new(ctx.config.redacted);
349
350 match &self {
351 Constant { rows, typ: _ } => match rows {
352 Ok(rows) => {
353 if !rows.is_empty() {
354 write!(f, "{}Constant", ctx.indent)?;
355 self.fmt_analyses(f, ctx)?;
356 ctx.indented(|ctx| {
357 fmt_text_constant_rows(
358 f,
359 rows.iter().map(|(x, y)| (x, y)),
360 &mut ctx.indent,
361 mode.redacted(),
362 )
363 })?;
364 } else {
365 write!(f, "{}Constant <empty>", ctx.indent)?;
366 self.fmt_analyses(f, ctx)?;
367 }
368 }
369 Err(err) => {
370 if mode.redacted() {
371 writeln!(f, "{}Error █", ctx.indent)?;
372 } else {
373 writeln!(f, "{}Error {}", ctx.indent, err.to_string().escaped())?;
374 }
375 }
376 },
377 Let { id, value, body } => {
378 let mut bindings = vec![(id, value.as_ref())];
379 let mut head = body.as_ref();
380
381 while let Let { id, value, body } = head {
384 bindings.push((id, value.as_ref()));
385 head = body.as_ref();
386 }
387
388 if ctx.config.linear_chains {
389 writeln!(f, "{}With", ctx.indent)?;
390 ctx.indented(|ctx| {
391 for (id, value) in bindings.iter() {
392 writeln!(f, "{}cte {} =", ctx.indent, *id)?;
393 ctx.indented(|ctx| value.fmt_text(f, ctx))?;
394 }
395 Ok(())
396 })?;
397 write!(f, "{}Return", ctx.indent)?;
398 self.fmt_analyses(f, ctx)?;
399 ctx.indented(|ctx| head.fmt_text(f, ctx))?;
400 } else {
401 writeln!(f, "{}With", ctx.indent)?;
402 ctx.indented(|ctx| {
403 for (id, value) in bindings.iter() {
404 writeln!(f, "{}cte {} =", ctx.indent, *id)?;
405 ctx.indented(|ctx| value.fmt_text(f, ctx))?;
406 }
407 Ok(())
408 })?;
409 write!(f, "{}Return", ctx.indent)?;
410 self.fmt_analyses(f, ctx)?;
411 ctx.indented(|ctx| head.fmt_text(f, ctx))?;
412 }
413 }
414 LetRec {
415 ids,
416 values,
417 limits,
418 body,
419 } => {
420 assert_eq!(ids.len(), values.len());
421 assert_eq!(ids.len(), limits.len());
422 let head = body.as_ref();
423
424 let all_limits_same = limits
429 .iter()
430 .reduce(|first, i| if i == first { first } else { &None })
431 .unwrap_or(&None);
432
433 if ctx.config.linear_chains {
434 unreachable!(); } else {
436 write!(f, "{}With Mutually Recursive", ctx.indent)?;
437 if let Some(limit) = all_limits_same {
438 write!(f, " {}", limit)?;
439 }
440 writeln!(f)?;
441 ctx.indented(|ctx| {
442 let bindings = ids.iter().zip_eq(values).zip_eq(limits);
443 for ((id, value), limit) in bindings {
444 write!(f, "{}cte", ctx.indent)?;
445 if all_limits_same.is_none() {
446 if let Some(limit) = limit {
447 write!(f, " {}", limit)?;
448 }
449 }
450 writeln!(f, " {} =", id)?;
451 ctx.indented(|ctx| value.fmt_text(f, ctx))?;
452 }
453 Ok(())
454 })?;
455 write!(f, "{}Return", ctx.indent)?;
456 self.fmt_analyses(f, ctx)?;
457 ctx.indented(|ctx| head.fmt_text(f, ctx))?;
458 }
459 }
460 Get {
461 id,
462 access_strategy: persist_or_index,
463 ..
464 } => {
465 match id {
466 Id::Local(id) => {
467 assert!(matches!(persist_or_index, AccessStrategy::UnknownOrLocal));
468 write!(f, "{}Get {}", ctx.indent, id)?;
469 }
470 Id::Global(id) => {
471 let humanize = |id: &GlobalId| {
472 ctx.humanizer
473 .humanize_id(*id)
474 .unwrap_or_else(|| id.to_string())
475 };
476 let humanize_unqualified = |id: &GlobalId| {
477 ctx.humanize_id_maybe_unqualified(*id)
478 .unwrap_or_else(|| id.to_string())
479 };
480 let humanize_unqualified_maybe_deleted = |id: &GlobalId| {
481 ctx.humanize_id_maybe_unqualified(*id)
482 .unwrap_or_else(|| "[DELETED INDEX]".to_owned())
483 };
484 match persist_or_index {
485 AccessStrategy::UnknownOrLocal => {
486 write!(f, "{}Get {}", ctx.indent, humanize(id))?;
487 }
488 AccessStrategy::Persist => {
489 write!(f, "{}ReadStorage {}", ctx.indent, humanize(id))?;
490 }
491 AccessStrategy::SameDataflow => {
492 write!(
493 f,
494 "{}ReadGlobalFromSameDataflow {}",
495 ctx.indent,
496 humanize(id)
497 )?;
498 }
499 AccessStrategy::Index(index_accesses) => {
500 let mut grouped_index_accesses = BTreeMap::new();
501 for (idx_id, usage_type) in index_accesses {
502 grouped_index_accesses
503 .entry(idx_id)
504 .or_insert(Vec::new())
505 .push(usage_type.clone());
506 }
507 write!(
508 f,
509 "{}ReadIndex on={} {}",
510 ctx.indent,
511 humanize_unqualified(id),
512 separated(
513 " ",
514 grouped_index_accesses.iter().map(
515 |(idx_id, usage_types)| {
516 closure_to_display(move |f| {
517 write!(
518 f,
519 "{}=[{}]",
520 humanize_unqualified_maybe_deleted(idx_id),
521 IndexUsageType::display_vec(usage_types)
522 )
523 })
524 }
525 )
526 ),
527 )?;
528 }
529 }
530 }
531 }
532 self.fmt_analyses(f, ctx)?;
533 }
534 Project { outputs, input } => {
535 FmtNode {
536 fmt_root: |f, ctx| {
537 let outputs = mode.seq(outputs, input.column_names(ctx));
538 let outputs = CompactScalars(outputs);
539 write!(f, "{}Project ({})", ctx.indent, outputs)?;
540 self.fmt_analyses(f, ctx)
541 },
542 fmt_children: |f, ctx| input.fmt_text(f, ctx),
543 }
544 .render(f, ctx)?;
545 }
546 Map { scalars, input } => {
547 FmtNode {
548 fmt_root: |f, ctx: &mut PlanRenderingContext<'_, MirRelationExpr>| {
549 let scalars = mode.seq(scalars, self.column_names(ctx));
553 let scalars = CompactScalars(scalars);
554 write!(f, "{}Map ({})", ctx.indent, scalars)?;
555 self.fmt_analyses(f, ctx)
556 },
557 fmt_children: |f, ctx| input.fmt_text(f, ctx),
558 }
559 .render(f, ctx)?;
560 }
561 FlatMap { input, func, exprs } => {
562 FmtNode {
563 fmt_root: |f, ctx: &mut PlanRenderingContext<'_, MirRelationExpr>| {
564 let exprs = mode.seq(exprs, input.column_names(ctx));
565 let exprs = CompactScalars(exprs);
566 write!(f, "{}FlatMap {}({})", ctx.indent, func, exprs)?;
567 self.fmt_analyses(f, ctx)
568 },
569 fmt_children: |f, ctx| input.fmt_text(f, ctx),
570 }
571 .render(f, ctx)?;
572 }
573 Filter { predicates, input } => {
574 FmtNode {
575 fmt_root: |f, ctx: &mut PlanRenderingContext<'_, MirRelationExpr>| {
576 if predicates.is_empty() {
577 write!(f, "{}Filter", ctx.indent)?;
578 } else {
579 let cols = input.column_names(ctx);
580 let predicates = mode.seq(predicates, cols);
581 let predicates = separated(" AND ", predicates);
582 write!(f, "{}Filter {}", ctx.indent, predicates)?;
583 }
584 self.fmt_analyses(f, ctx)
585 },
586 fmt_children: |f, ctx| input.fmt_text(f, ctx),
587 }
588 .render(f, ctx)?;
589 }
590 Join {
591 inputs,
592 equivalences,
593 implementation:
594 implementation @ (JoinImplementation::Differential(..)
595 | JoinImplementation::DeltaQuery(..)
596 | JoinImplementation::Unimplemented),
597 } => {
598 let has_equivalences = !equivalences.is_empty();
599
600 if has_equivalences {
601 let cols = self.column_names(ctx);
602 let equivalences = separated(
603 " AND ",
604 equivalences.iter().map(|equivalence| {
605 let equivalence = mode.seq(equivalence, cols);
606 separated(" = ", equivalence)
607 }),
608 );
609 write!(f, "{}Join on=({})", ctx.indent, equivalences)?;
610 } else {
611 write!(f, "{}CrossJoin", ctx.indent)?;
612 }
613 if let Some(name) = implementation.name() {
614 write!(f, " type={}", name)?;
615 }
616
617 self.fmt_analyses(f, ctx)?;
618
619 if ctx.config.join_impls {
620 let input_name = &|pos: usize| -> String {
621 fn dig_name_from_expr(
623 h: &dyn ExprHumanizer,
624 e: &MirRelationExpr,
625 ) -> Option<String> {
626 let global_id_name = |id: &GlobalId| -> String {
627 h.humanize_id_unqualified(*id)
628 .unwrap_or_else(|| id.to_string())
629 };
630 let (_mfp, e) = MapFilterProject::extract_from_expression(e);
631 match e {
632 Get { id, .. } => match id {
633 Id::Local(lid) => Some(lid.to_string()),
634 Id::Global(gid) => Some(global_id_name(gid)),
635 },
636 ArrangeBy { input, .. } => dig_name_from_expr(h, input),
637 Join {
638 implementation: JoinImplementation::IndexedFilter(gid, ..),
639 ..
640 } => Some(global_id_name(gid)),
641 _ => None,
642 }
643 }
644 match dig_name_from_expr(ctx.humanizer, &inputs[pos]) {
645 Some(str) => format!("%{}:{}", pos, str),
646 None => format!("%{}", pos),
647 }
648 };
649 let join_key_to_string = |key: &Vec<MirScalarExpr>| -> String {
650 if key.is_empty() {
651 "×".to_owned()
652 } else {
653 CompactScalars(mode.seq(key, None)).to_string()
654 }
655 };
656 let join_order = |start_idx: usize,
657 start_key: &Option<Vec<MirScalarExpr>>,
658 start_characteristics: &Option<JoinInputCharacteristics>,
659 tail: &Vec<(
660 usize,
661 Vec<MirScalarExpr>,
662 Option<JoinInputCharacteristics>,
663 )>|
664 -> String {
665 format!(
666 "{}{}{} » {}",
667 input_name(start_idx),
668 match start_key {
669 None => "".to_owned(),
670 Some(key) => format!("[{}]", join_key_to_string(key)),
671 },
672 start_characteristics
673 .as_ref()
674 .map(|c| c.explain())
675 .unwrap_or_else(|| "".to_string()),
676 separated(
677 " » ",
678 tail.iter().map(|(pos, key, characteristics)| {
679 format!(
680 "{}[{}]{}",
681 input_name(*pos),
682 join_key_to_string(key),
683 characteristics
684 .as_ref()
685 .map(|c| c.explain())
686 .unwrap_or_else(|| "".to_string())
687 )
688 })
689 ),
690 )
691 };
692 ctx.indented(|ctx| {
693 match implementation {
694 JoinImplementation::Differential(
695 (start_idx, start_key, start_characteristics),
696 tail,
697 ) => {
698 soft_assert_eq_or_log!(inputs.len(), tail.len() + 1);
699
700 writeln!(f, "{}implementation", ctx.indent)?;
701 ctx.indented(|ctx| {
702 writeln!(
703 f,
704 "{}{}",
705 ctx.indent,
706 join_order(
707 *start_idx,
708 start_key,
709 start_characteristics,
710 tail
711 )
712 )
713 })?;
714 }
715 JoinImplementation::DeltaQuery(half_join_chains) => {
716 soft_assert_eq_or_log!(inputs.len(), half_join_chains.len());
717
718 writeln!(f, "{}implementation", ctx.indent)?;
719 ctx.indented(|ctx| {
720 for (pos, chain) in half_join_chains.iter().enumerate() {
721 writeln!(
722 f,
723 "{}{}",
724 ctx.indent,
725 join_order(pos, &None, &None, chain)
726 )?;
727 }
728 Ok(())
729 })?;
730 }
731 JoinImplementation::IndexedFilter(_, _, _, _) => {
732 unreachable!() }
734 JoinImplementation::Unimplemented => {}
735 }
736 Ok(())
737 })?;
738 }
739
740 ctx.indented(|ctx| {
741 for input in inputs {
742 input.fmt_text(f, ctx)?;
743 }
744 Ok(())
745 })?;
746 }
747 Join {
748 implementation:
749 JoinImplementation::IndexedFilter(coll_id, idx_id, _key, literal_constraints),
750 inputs,
751 ..
752 } => {
753 let cse_id = match inputs.get(1).unwrap() {
754 Get { id, .. } => {
756 if let Id::Local(local_id) = id {
757 Some(local_id)
758 } else {
759 unreachable!()
760 }
761 }
762 _ => None,
763 };
764 Self::fmt_indexed_filter(
765 f,
766 ctx,
767 coll_id,
768 idx_id,
769 Some(literal_constraints.clone()),
770 cse_id,
771 )?;
772 self.fmt_analyses(f, ctx)?;
773 }
774 Reduce {
775 group_key,
776 aggregates,
777 expected_group_size,
778 monotonic,
779 input,
780 } => {
781 FmtNode {
782 fmt_root: |f, ctx: &mut PlanRenderingContext<'_, MirRelationExpr>| {
783 if aggregates.len() == 0 && !ctx.config.raw_syntax {
784 write!(f, "{}Distinct", ctx.indent)?;
785
786 let group_key = mode.seq(group_key, input.column_names(ctx));
787 let group_key = CompactScalars(group_key);
788 write!(f, " project=[{}]", group_key)?;
789 } else {
790 write!(f, "{}Reduce", ctx.indent)?;
791
792 if group_key.len() > 0 {
793 let group_key = mode.seq(group_key, input.column_names(ctx));
794 let group_key = CompactScalars(group_key);
795 write!(f, " group_by=[{}]", group_key)?;
796 }
797 }
798 if aggregates.len() > 0 {
799 let cols = input.column_names(ctx);
800 let aggregates = mode.seq(aggregates, cols);
801 write!(f, " aggregates=[{}]", separated(", ", aggregates))?;
802 }
803 if *monotonic {
804 write!(f, " monotonic")?;
805 }
806 if let Some(expected_group_size) = expected_group_size {
807 write!(f, " exp_group_size={}", expected_group_size)?;
808 }
809 self.fmt_analyses(f, ctx)
810 },
811 fmt_children: |f, ctx| input.fmt_text(f, ctx),
812 }
813 .render(f, ctx)?;
814 }
815 TopK {
816 group_key,
817 order_key,
818 limit,
819 offset,
820 monotonic,
821 input,
822 expected_group_size,
823 } => {
824 FmtNode {
825 fmt_root: |f, ctx: &mut PlanRenderingContext<'_, MirRelationExpr>| {
826 write!(f, "{}TopK", ctx.indent)?;
827 let cols = input.column_names(ctx);
828 if group_key.len() > 0 {
829 if cols.is_some() {
830 let group_by = mode.seq(group_key, cols);
831 write!(f, " group_by=[{}]", separated(", ", group_by))?;
832 } else {
833 let group_by = Indices(group_key);
834 write!(f, " group_by=[{}]", group_by)?;
835 }
836 }
837 if order_key.len() > 0 {
838 let order_by = mode.seq(order_key, cols);
839 write!(f, " order_by=[{}]", separated(", ", order_by))?;
840 }
841 if let Some(limit) = limit {
842 let limit = mode.expr(limit, cols);
843 write!(f, " limit={}", limit)?;
844 }
845 if offset > &0 {
846 write!(f, " offset={}", offset)?
847 }
848 if *monotonic {
849 write!(f, " monotonic")?;
850 }
851 if let Some(expected_group_size) = expected_group_size {
852 write!(f, " exp_group_size={}", expected_group_size)?;
853 }
854 self.fmt_analyses(f, ctx)
855 },
856 fmt_children: |f, ctx| input.fmt_text(f, ctx),
857 }
858 .render(f, ctx)?;
859 }
860 Negate { input } => {
861 FmtNode {
862 fmt_root: |f, ctx| {
863 write!(f, "{}Negate", ctx.indent)?;
864 self.fmt_analyses(f, ctx)
865 },
866 fmt_children: |f, ctx| input.fmt_text(f, ctx),
867 }
868 .render(f, ctx)?;
869 }
870 Threshold { input } => {
871 FmtNode {
872 fmt_root: |f, ctx| {
873 write!(f, "{}Threshold", ctx.indent)?;
874 self.fmt_analyses(f, ctx)
875 },
876 fmt_children: |f, ctx| input.fmt_text(f, ctx),
877 }
878 .render(f, ctx)?;
879 }
880 Union { base, inputs } => {
881 write!(f, "{}Union", ctx.indent)?;
882 self.fmt_analyses(f, ctx)?;
883 ctx.indented(|ctx| {
884 base.fmt_text(f, ctx)?;
885 for input in inputs.iter() {
886 input.fmt_text(f, ctx)?;
887 }
888 Ok(())
889 })?;
890 }
891 ArrangeBy { input, keys } => {
892 FmtNode {
893 fmt_root: |f, ctx: &mut PlanRenderingContext<'_, MirRelationExpr>| {
894 write!(f, "{}ArrangeBy", ctx.indent)?;
895
896 let keys = keys.iter().map(|key| {
897 let key = mode.seq(key, input.column_names(ctx));
898 CompactScalars(key)
899 });
900 let keys = separated("], [", keys);
901 write!(f, " keys=[[{}]]", keys)?;
902
903 self.fmt_analyses(f, ctx)
904 },
905 fmt_children: |f, ctx| input.fmt_text(f, ctx),
906 }
907 .render(f, ctx)?;
908 }
909 }
910
911 Ok(())
912 }
913
914 fn fmt_analyses(
915 &self,
916 f: &mut fmt::Formatter<'_>,
917 ctx: &PlanRenderingContext<'_, MirRelationExpr>,
918 ) -> fmt::Result {
919 if ctx.config.requires_analyses() {
920 if let Some(analyses) = ctx.annotations.get(self) {
921 writeln!(f, " {}", HumanizedAnalyses::new(analyses, ctx))
922 } else {
923 writeln!(f, " // error: no analyses for subtree in map")
924 }
925 } else {
926 writeln!(f)
927 }
928 }
929
930 fn column_names<'a>(
931 &'a self,
932 ctx: &'a PlanRenderingContext<'_, MirRelationExpr>,
933 ) -> Option<&'a Vec<String>> {
934 if !ctx.config.humanized_exprs {
935 None
936 } else if let Some(analyses) = ctx.annotations.get(self) {
937 analyses.column_names.as_ref()
938 } else {
939 None
940 }
941 }
942
943 pub fn fmt_indexed_filter<'a, T>(
944 f: &mut fmt::Formatter<'_>,
945 ctx: &mut PlanRenderingContext<'a, T>,
946 coll_id: &GlobalId, idx_id: &GlobalId, constants: Option<Vec<Row>>, cse_id: Option<&LocalId>, ) -> fmt::Result {
951 let mode = HumanizedExplain::new(ctx.config.redacted);
952
953 let humanized_coll = ctx
954 .humanizer
955 .humanize_id(*coll_id)
956 .unwrap_or_else(|| coll_id.to_string());
957 let humanized_index = ctx
958 .humanize_id_maybe_unqualified(*idx_id)
959 .unwrap_or_else(|| "[DELETED INDEX]".to_owned());
960 if let Some(constants) = constants {
961 write!(
962 f,
963 "{}ReadIndex on={} {}=[{} ",
964 ctx.as_mut(),
965 humanized_coll,
966 humanized_index,
967 IndexUsageType::Lookup(*idx_id),
968 )?;
969 if let Some(cse_id) = cse_id {
970 write!(f, "values=<Get {}>]", cse_id)?;
974 } else {
975 if constants.len() == 1 {
976 let value = mode.expr(&constants[0], None);
977 write!(f, "value={}]", value)?;
978 } else {
979 let values = mode.seq(&constants, None);
980 write!(f, "values=[{}]]", separated("; ", values))?;
981 }
982 }
983 } else {
984 write!(
986 f,
987 "{}ReadIndex on={} {}=[{}]",
988 ctx.indent,
989 humanized_coll,
990 humanized_index,
991 IndexUsageType::FullScan
992 )?;
993 }
994 Ok(())
995 }
996}
997
998struct FmtNode<F, G>
1005where
1006 F: FnOnce(
1007 &mut fmt::Formatter<'_>,
1008 &mut PlanRenderingContext<'_, MirRelationExpr>,
1009 ) -> fmt::Result,
1010 G: FnOnce(
1011 &mut fmt::Formatter<'_>,
1012 &mut PlanRenderingContext<'_, MirRelationExpr>,
1013 ) -> fmt::Result,
1014{
1015 fmt_root: F,
1016 fmt_children: G,
1017}
1018
1019impl<F, G> FmtNode<F, G>
1020where
1021 F: FnOnce(
1022 &mut fmt::Formatter<'_>,
1023 &mut PlanRenderingContext<'_, MirRelationExpr>,
1024 ) -> fmt::Result,
1025 G: FnOnce(
1026 &mut fmt::Formatter<'_>,
1027 &mut PlanRenderingContext<'_, MirRelationExpr>,
1028 ) -> fmt::Result,
1029{
1030 fn render(
1031 self,
1032 f: &mut fmt::Formatter<'_>,
1033 ctx: &mut PlanRenderingContext<'_, MirRelationExpr>,
1034 ) -> fmt::Result {
1035 let FmtNode {
1036 fmt_root,
1037 fmt_children,
1038 } = self;
1039 if ctx.config.linear_chains {
1040 fmt_children(f, ctx)?;
1042 fmt_root(f, ctx)?;
1043 } else {
1044 fmt_root(f, ctx)?;
1046 *ctx.as_mut() += 1;
1048 fmt_children(f, ctx)?;
1049 *ctx.as_mut() -= 1;
1050 }
1051 Ok(())
1052 }
1053}
1054
1055impl MirScalarExpr {
1056 pub fn format(&self, f: &mut fmt::Formatter<'_>, cols: Option<&Vec<String>>) -> fmt::Result {
1057 let mode = HumanizedExplain::default();
1058 fmt::Display::fmt(&mode.expr(self, cols), f)
1059 }
1060}
1061
1062impl fmt::Display for MirScalarExpr {
1063 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1064 self.format(f, None)
1065 }
1066}
1067
1068#[derive(Debug, Clone)]
1072pub struct HumanizedExpr<'a, T, M = HumanizedExplain> {
1073 pub expr: &'a T,
1075 pub cols: Option<&'a Vec<String>>,
1078 pub mode: M,
1080}
1081
1082impl<'a, T, M: HumanizerMode> HumanizedExpr<'a, T, M> {
1083 pub fn child<U>(&self, expr: &'a U) -> HumanizedExpr<'a, U, M> {
1086 HumanizedExpr {
1087 expr,
1088 cols: self.cols,
1089 mode: self.mode.clone(),
1090 }
1091 }
1092}
1093
1094pub trait HumanizerMode: Sized + Clone {
1105 fn default() -> Self {
1110 let redacted = !mz_ore::assert::soft_assertions_enabled();
1111 Self::new(redacted)
1112 }
1113
1114 fn new(redacted: bool) -> Self;
1117
1118 fn expr<'a, T>(
1121 &self,
1122 expr: &'a T,
1123 cols: Option<&'a Vec<String>>,
1124 ) -> HumanizedExpr<'a, T, Self> {
1125 HumanizedExpr {
1126 expr,
1127 cols,
1128 mode: self.clone(),
1129 }
1130 }
1131
1132 fn redacted(&self) -> bool;
1134
1135 fn humanize_ident(col: usize, ident: Ident, f: &mut fmt::Formatter<'_>) -> fmt::Result;
1137
1138 fn humanize_datum(&self, datum: Datum<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1143 if self.redacted() {
1144 write!(f, "█")
1145 } else {
1146 write!(f, "{}", datum)
1147 }
1148 }
1149
1150 fn seq<'i, T>(
1152 &self,
1153 exprs: &'i [T],
1154 cols: Option<&'i Vec<String>>,
1155 ) -> impl Iterator<Item = HumanizedExpr<'i, T, Self>> + Clone {
1156 exprs.iter().map(move |expr| self.expr(expr, cols))
1157 }
1158}
1159
1160#[derive(Debug, Clone)]
1165pub struct HumanizedNotice(bool);
1166
1167impl HumanizerMode for HumanizedNotice {
1168 fn new(redacted: bool) -> Self {
1169 Self(redacted)
1170 }
1171
1172 fn redacted(&self) -> bool {
1173 self.0
1174 }
1175
1176 fn humanize_ident(_col: usize, ident: Ident, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1178 write!(f, "{ident}")
1179 }
1180}
1181
1182#[derive(Debug, Clone)]
1186pub struct HumanizedExplain(bool);
1187
1188impl HumanizerMode for HumanizedExplain {
1189 fn new(redacted: bool) -> Self {
1190 Self(redacted)
1191 }
1192
1193 fn redacted(&self) -> bool {
1194 self.0
1195 }
1196
1197 fn humanize_ident(col: usize, ident: Ident, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1199 if ident.as_str() == UNKNOWN_COLUMN_NAME {
1200 write!(f, "#{col}")
1201 } else if ident.has_only_bare_chars() {
1202 write!(f, "#{col}{{{}}}", ident.as_str())
1209 } else {
1210 write!(f, "#{col}{{{ident}}}")
1211 }
1212 }
1213}
1214
1215impl<'a, M> fmt::Display for HumanizedExpr<'a, usize, M>
1217where
1218 M: HumanizerMode,
1219{
1220 #[inline]
1221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1222 match self.cols {
1223 Some(cols) if cols.len() > *self.expr && !cols[*self.expr].is_empty() => {
1225 let ident = Ident::new_unchecked(cols[*self.expr].clone()); M::humanize_ident(*self.expr, ident, f)
1229 }
1230 _ => {
1232 write!(f, "#{}", self.expr)
1234 }
1235 }
1236 }
1237}
1238
1239impl<'a, M> fmt::Display for HumanizedExpr<'a, (&usize, &Arc<str>), M>
1242where
1243 M: HumanizerMode,
1244{
1245 #[inline]
1246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1247 match self.cols {
1248 Some(cols) if cols.len() > *self.expr.0 && !cols[*self.expr.0].is_empty() => {
1250 let ident = Ident::new_unchecked(cols[*self.expr.0].clone()); M::humanize_ident(*self.expr.0, ident, f)
1254 }
1255 _ => M::humanize_ident(*self.expr.0, Ident::new_unchecked(self.expr.1.as_ref()), f),
1257 }
1258 }
1259}
1260
1261impl<'a, T, M> ScalarOps for HumanizedExpr<'a, T, M>
1262where
1263 T: ScalarOps,
1264{
1265 fn match_col_ref(&self) -> Option<usize> {
1266 self.expr.match_col_ref()
1267 }
1268
1269 fn references(&self, col_ref: usize) -> bool {
1270 self.expr.references(col_ref)
1271 }
1272}
1273
1274pub trait HumanizeDisplay: Sized {
1275 fn humanize<'a, M: HumanizerMode>(
1276 e: &HumanizedExpr<'a, Self, M>,
1277 f: &mut fmt::Formatter<'_>,
1278 ) -> fmt::Result;
1279}
1280
1281impl HumanizeDisplay for MirScalarExpr {
1282 fn humanize<'a, M: HumanizerMode>(
1283 e: &HumanizedExpr<'a, MirScalarExpr, M>,
1284 f: &mut fmt::Formatter<'_>,
1285 ) -> fmt::Result {
1286 use MirScalarExpr::*;
1287
1288 match e.expr {
1289 Column(i, TreatAsEqual(None)) => {
1290 e.child(i).fmt(f)
1292 }
1293 Column(i, TreatAsEqual(Some(name))) => {
1294 e.child(&(i, name)).fmt(f)
1296 }
1297 Literal(row, _) => {
1298 e.child(row).fmt(f)
1300 }
1301 CallUnmaterializable(func) => write!(f, "{}()", func),
1302 CallUnary { func, expr } => {
1303 if let crate::UnaryFunc::Not(_) = *func {
1304 if let CallUnary { func, expr } = expr.as_ref() {
1305 if let Some(is) = func.is() {
1306 let expr = e.child::<MirScalarExpr>(&*expr);
1307 return write!(f, "({}) IS NOT {}", expr, is);
1308 }
1309 }
1310 }
1311 if let Some(is) = func.is() {
1312 let expr = e.child::<MirScalarExpr>(&*expr);
1313 write!(f, "({}) IS {}", expr, is)
1314 } else {
1315 let expr = e.child::<MirScalarExpr>(&*expr);
1316 write!(f, "{}({})", func, expr)
1317 }
1318 }
1319 CallBinary { func, expr1, expr2 } => {
1320 let expr1 = e.child::<MirScalarExpr>(&*expr1);
1321 let expr2 = e.child::<MirScalarExpr>(&*expr2);
1322 if func.is_infix_op() {
1323 write!(f, "({} {} {})", expr1, func, expr2)
1324 } else {
1325 write!(f, "{}({}, {})", func, expr1, expr2)
1326 }
1327 }
1328 CallVariadic { func, exprs } => {
1329 use crate::VariadicFunc::*;
1330 match func {
1331 CaseLiteral(cl) => {
1332 let input = e.child::<MirScalarExpr>(&exprs[0]);
1333 write!(f, "case_lookup {}", input)?;
1334 for entry in &cl.lookup {
1335 let result = e.child::<MirScalarExpr>(&exprs[entry.expr_index]);
1336 write!(f, " when ")?;
1337 e.mode.humanize_datum(entry.literal.unpack_first(), f)?;
1338 write!(f, " then {}", result)?;
1339 }
1340 let els = e.child::<MirScalarExpr>(exprs.last().unwrap());
1341 write!(f, " else {} end", els)
1342 }
1343 ArrayCreate(..) => {
1344 let exprs = exprs.iter().map(|expr| e.child(expr));
1345 let exprs = separated(", ", exprs);
1346 write!(f, "array[{}]", exprs)
1347 }
1348 ListCreate(..) => {
1349 let exprs = exprs.iter().map(|expr| e.child(expr));
1350 let exprs = separated(", ", exprs);
1351 write!(f, "list[{}]", exprs)
1352 }
1353 RecordCreate(..) => {
1354 let exprs = exprs.iter().map(|expr| e.child(expr));
1355 let exprs = separated(", ", exprs);
1356 write!(f, "row({})", exprs)
1357 }
1358 func if func.is_infix_op() && exprs.len() > 1 => {
1359 let exprs = exprs.iter().map(|expr| e.child(expr));
1360 let func = format!(" {} ", func);
1361 let exprs = separated(&func, exprs);
1362 write!(f, "({})", exprs)
1363 }
1364 func => {
1365 let exprs = exprs.iter().map(|expr| e.child(expr));
1366 let exprs = separated(", ", exprs);
1367 write!(f, "{}({})", func, exprs)
1368 }
1369 }
1370 }
1371 If { cond, then, els } => {
1372 let cond = e.child::<MirScalarExpr>(&*cond);
1373 let then = e.child::<MirScalarExpr>(&*then);
1374 let els = e.child::<MirScalarExpr>(&*els);
1375 write!(f, "case when {} then {} else {} end", cond, then, els)
1376 }
1377 }
1378 }
1379}
1380
1381impl<'a, T, M> fmt::Display for HumanizedExpr<'a, T, M>
1382where
1383 T: HumanizeDisplay,
1384 M: HumanizerMode,
1385{
1386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1387 T::humanize(self, f)
1388 }
1389}
1390
1391impl<'a, M> fmt::Display for HumanizedExpr<'a, AggregateExpr, M>
1392where
1393 M: HumanizerMode,
1394{
1395 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1396 if self.expr.is_count_asterisk() {
1397 return write!(f, "count(*)");
1398 }
1399
1400 write!(
1401 f,
1402 "{}({}",
1403 self.child(&self.expr.func),
1404 if self.expr.distinct { "distinct " } else { "" }
1405 )?;
1406
1407 self.child(&self.expr.expr).fmt(f)?;
1408 write!(f, ")")
1409 }
1410}
1411
1412impl<'a, M> fmt::Display for HumanizedExpr<'a, Result<Row, EvalError>, M>
1419where
1420 M: HumanizerMode,
1421{
1422 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1423 match self.expr {
1424 Ok(row) => self.mode.humanize_datum(row.unpack_first(), f),
1425 Err(err) => {
1426 if self.mode.redacted() {
1427 write!(f, "error(█)")
1428 } else {
1429 write!(f, "error({})", err.to_string().escaped())
1430 }
1431 }
1432 }
1433 }
1434}
1435
1436impl HumanizeDisplay for StableRow {
1438 fn humanize<'a, M: HumanizerMode>(
1439 e: &HumanizedExpr<'a, Self, M>,
1440 f: &mut fmt::Formatter<'_>,
1441 ) -> fmt::Result {
1442 e.child::<Row>(&e.expr.0).fmt(f)
1443 }
1444}
1445
1446impl<'a, M> fmt::Display for HumanizedExpr<'a, Result<StableRow, EvalError>, M>
1449where
1450 M: HumanizerMode,
1451{
1452 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1453 match self.expr {
1454 Ok(row) => self.mode.humanize_datum(row.unpack_first(), f),
1455 Err(err) => {
1456 if self.mode.redacted() {
1457 write!(f, "error(█)")
1458 } else {
1459 write!(f, "error({})", err.to_string().escaped())
1460 }
1461 }
1462 }
1463 }
1464}
1465
1466impl<'a, M> fmt::Display for HumanizedExpr<'a, Datum<'a>, M>
1467where
1468 M: HumanizerMode,
1469{
1470 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1471 self.mode.humanize_datum(*self.expr, f)
1472 }
1473}
1474
1475impl<'a, M> fmt::Display for HumanizedExpr<'a, Row, M>
1476where
1477 M: HumanizerMode,
1478{
1479 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1480 f.write_str("(")?;
1481 for (i, d) in self.expr.iter().enumerate() {
1482 if i > 0 {
1483 write!(f, ", {}", self.child(&d))?;
1484 } else {
1485 write!(f, "{}", self.child(&d))?;
1486 }
1487 }
1488 f.write_str(")")?;
1489 Ok(())
1490 }
1491}
1492
1493pub fn fmt_text_constant_rows<'a, I>(
1494 f: &mut fmt::Formatter<'_>,
1495 mut rows: I,
1496 ctx: &mut Indent,
1497 redacted: bool,
1498) -> fmt::Result
1499where
1500 I: Iterator<Item = (&'a Row, &'a Diff)>,
1501{
1502 let mut row_count = Diff::ZERO;
1503 let mut first_rows = Vec::with_capacity(20);
1504 for _ in 0..20 {
1505 if let Some((row, diff)) = rows.next() {
1506 row_count += diff.abs();
1507 first_rows.push((row, diff));
1508 }
1509 }
1510 let rest_of_row_count = rows.map(|(_, diff)| diff.abs()).sum::<Diff>();
1511 if !rest_of_row_count.is_zero() {
1512 writeln!(
1513 f,
1514 "{}total_rows (diffs absed): {}",
1515 ctx,
1516 row_count + rest_of_row_count
1517 )?;
1518 writeln!(f, "{}first_rows:", ctx)?;
1519 ctx.indented(move |ctx| write_first_rows(f, &first_rows, ctx, redacted))?;
1520 } else {
1521 write_first_rows(f, &first_rows, ctx, redacted)?;
1522 }
1523 Ok(())
1524}
1525
1526fn write_first_rows(
1527 f: &mut fmt::Formatter<'_>,
1528 first_rows: &Vec<(&Row, &Diff)>,
1529 ctx: &Indent,
1530 redacted: bool,
1531) -> fmt::Result {
1532 let mode = HumanizedExplain::new(redacted);
1533 for (row, diff) in first_rows {
1534 let row = mode.expr(*row, None);
1535 if **diff == Diff::ONE {
1536 writeln!(f, "{}- {}", ctx, row)?;
1537 } else {
1538 writeln!(f, "{}- ({} x {})", ctx, row, diff)?;
1539 }
1540 }
1541 Ok(())
1542}