1use std::collections::BTreeMap;
13use std::fmt;
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, UNKNOWN_COLUMN_NAME};
26use mz_sql_parser::ast::Ident;
27
28use crate::explain::{ExplainMultiPlan, ExplainSinglePlan};
29use crate::{
30 AccessStrategy, AggregateExpr, EvalError, Id, JoinImplementation, JoinInputCharacteristics,
31 LocalId, MapFilterProject, MirRelationExpr, MirScalarExpr, 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 );
45
46 let mode = HumanizedExplain::new(self.context.config.redacted);
47
48 if let Some(finishing) = &self.context.finishing {
49 if ctx.config.humanized_exprs {
50 let analyses = ctx.annotations.get(&self.plan.plan);
51 let cols = analyses
52 .map(|analyses| analyses.column_names.clone())
53 .flatten();
54 mode.expr(finishing, cols.as_ref()).fmt_text(f, &mut ctx)?;
55 } else {
56 mode.expr(finishing, None).fmt_text(f, &mut ctx)?;
57 }
58 ctx.indented(|ctx| self.plan.plan.fmt_text(f, ctx))?;
59 } else {
60 self.plan.plan.fmt_text(f, &mut ctx)?;
61 }
62
63 if !self.context.used_indexes.is_empty() {
64 writeln!(f)?;
65 self.context.used_indexes.fmt_text(f, &mut ctx)?;
66 }
67
68 if let Some(target_cluster) = self.context.target_cluster {
69 writeln!(f)?;
70 writeln!(f, "Target cluster: {}", target_cluster)?;
71 }
72
73 if !self.context.optimizer_notices.is_empty() {
74 writeln!(f)?;
75 writeln!(f, "Notices:")?;
76 for notice in self.context.optimizer_notices.iter() {
77 writeln!(f, "{}", notice)?;
78 }
79 }
80
81 if self.context.config.timing {
82 writeln!(f)?;
83 writeln!(f, "Optimization time: {:?}", self.context.duration)?;
84 }
85
86 Ok(())
87 }
88}
89
90impl<'a, T: 'a> DisplayText for ExplainMultiPlan<'a, T>
91where
92 T: DisplayText<PlanRenderingContext<'a, T>> + Ord,
93{
94 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, _ctx: &mut ()) -> fmt::Result {
95 let mut ctx = RenderingContext::new(Indent::default(), self.context.humanizer);
96
97 let mode = HumanizedExplain::new(self.context.config.redacted);
98
99 for (no, (id, plan)) in self.plans.iter().enumerate() {
101 let mut ctx = PlanRenderingContext::new(
102 ctx.indent.clone(),
103 ctx.humanizer,
104 plan.annotations.clone(),
105 self.context.config,
106 );
107
108 if no > 0 {
109 writeln!(f)?;
110 }
111
112 writeln!(f, "{}{}:", ctx.indent, id)?;
113 ctx.indented(|ctx| {
114 match &self.context.finishing {
115 Some(finishing) if no == 0 => {
117 if ctx.config.humanized_exprs {
118 let analyses = ctx.annotations.get(plan.plan);
119 let cols = analyses
120 .map(|analyses| analyses.column_names.clone())
121 .flatten();
122 mode.expr(finishing, cols.as_ref()).fmt_text(f, ctx)?;
123 } else {
124 mode.expr(finishing, None).fmt_text(f, ctx)?;
125 };
126 ctx.indented(|ctx| plan.plan.fmt_text(f, ctx))?;
127 }
128 _ => {
130 plan.plan.fmt_text(f, ctx)?;
131 }
132 }
133 Ok(())
134 })?;
135 }
136
137 if self.sources.iter().any(|src| !src.is_identity()) {
138 writeln!(f)?;
140 for src in self.sources.iter().filter(|src| !src.is_identity()) {
141 if self.context.config.humanized_exprs {
142 let mut cols = ctx.humanizer.column_names_for_id(src.id);
143 if let Some(cols) = cols.as_mut() {
147 let anonymous = std::iter::repeat(String::new());
148 cols.extend(
149 anonymous.take(src.op.map(|op| op.expressions.len()).unwrap_or(0)),
150 )
151 };
152 mode.expr(src, cols.as_ref()).fmt_text(f, &mut ctx)?;
154 } else {
155 mode.expr(src, None).fmt_text(f, &mut ctx)?;
157 }
158 }
159 }
160
161 if !self.context.used_indexes.is_empty() {
162 writeln!(f)?;
163 self.context.used_indexes.fmt_text(f, &mut ctx)?;
164 }
165
166 if let Some(target_cluster) = self.context.target_cluster {
167 writeln!(f)?;
168 writeln!(f, "Target cluster: {}", target_cluster)?;
169 }
170
171 if !(self.context.config.no_notices || self.context.optimizer_notices.is_empty()) {
172 writeln!(f)?;
173 writeln!(f, "Notices:")?;
174 for notice in self.context.optimizer_notices.iter() {
175 writeln!(f, "{}", notice)?;
176 }
177 }
178
179 if self.context.config.timing {
180 writeln!(f)?;
181 writeln!(f, "Optimization time: {:?}", self.context.duration)?;
182 }
183
184 Ok(())
185 }
186}
187
188impl<'a, C, M> DisplayText<C> for HumanizedExpr<'a, RowSetFinishing, M>
189where
190 C: AsMut<Indent>,
191 M: HumanizerMode,
192{
193 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, ctx: &mut C) -> fmt::Result {
194 write!(f, "{}Finish", ctx.as_mut())?;
195 if !self.expr.order_by.is_empty() {
197 let order_by = self.expr.order_by.iter().map(|e| self.child(e));
198 write!(f, " order_by=[{}]", separated(", ", order_by))?;
199 }
200 if let Some(limit) = self.expr.limit {
202 write!(f, " limit={}", limit)?;
203 }
204 if self.expr.offset > 0 {
206 write!(f, " offset={}", self.expr.offset)?;
207 }
208 {
210 let project = Indices(&self.expr.project);
211 write!(f, " output=[{}]", project)?;
212 }
213 writeln!(f)
214 }
215}
216
217impl<'a, C, M> DisplayText<C> for HumanizedExpr<'a, MapFilterProject, M>
218where
219 C: AsMut<Indent>,
220 M: HumanizerMode,
221{
222 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, ctx: &mut C) -> fmt::Result {
223 let (scalars, predicates, outputs, input_arity) = (
224 &self.expr.expressions,
225 &self.expr.predicates,
226 &self.expr.projection,
227 &self.expr.input_arity,
228 );
229
230 if &outputs.len() != input_arity || outputs.iter().enumerate().any(|(i, p)| i != *p) {
232 let outputs = Indices(outputs);
233 writeln!(f, "{}project=({})", ctx.as_mut(), outputs)?;
234 }
235 if !predicates.is_empty() {
237 let predicates = predicates.iter().map(|(_, p)| self.child(p));
238 let predicates = separated(" AND ", predicates);
239 writeln!(f, "{}filter=({})", ctx.as_mut(), predicates)?;
240 }
241 if !scalars.is_empty() {
243 let scalars = scalars.iter().map(|s| self.child(s));
244 let scalars = separated(", ", scalars);
245 writeln!(f, "{}map=({})", ctx.as_mut(), scalars)?;
246 }
247
248 Ok(())
249 }
250}
251
252impl<'a, M: HumanizerMode> HumanizedExpr<'a, MapFilterProject, M> {
253 pub fn fmt_default_text<T>(
255 &self,
256 f: &mut fmt::Formatter<'_>,
257 ctx: &mut PlanRenderingContext<'_, T>,
258 ) -> fmt::Result {
259 if self.expr.projection.len() != self.expr.input_arity
260 || self
261 .expr
262 .projection
263 .iter()
264 .enumerate()
265 .any(|(i, p)| i != *p)
266 {
267 if self.expr.projection.len() == 0 {
268 writeln!(f, "{}Project: ()", ctx.indent)?;
269 } else {
270 let outputs = Indices(&self.expr.projection);
271 writeln!(f, "{}Project: {outputs}", ctx.indent)?;
272 }
273 }
274
275 if !self.expr.predicates.is_empty() {
277 let predicates = self.expr.predicates.iter().map(|(_, p)| self.child(p));
278 let predicates = separated(" AND ", predicates);
279 writeln!(f, "{}Filter: {predicates}", ctx.indent)?;
280 }
281 if !self.expr.expressions.is_empty() {
283 let scalars = self.expr.expressions.iter().map(|s| self.child(s));
284 let scalars = separated(", ", scalars);
285 writeln!(f, "{}Map: {scalars}", ctx.indent)?;
286 }
287
288 Ok(())
289 }
290}
291
292impl DisplayText<PlanRenderingContext<'_, MirRelationExpr>> for MirRelationExpr {
305 fn fmt_text(
306 &self,
307 f: &mut fmt::Formatter<'_>,
308 ctx: &mut PlanRenderingContext<'_, MirRelationExpr>,
309 ) -> fmt::Result {
310 if ctx.config.verbose_syntax {
311 self.fmt_verbose_syntax(f, ctx)
312 } else {
313 self.fmt_default_syntax(f, ctx)
314 }
315 }
316}
317
318impl MirRelationExpr {
319 fn fmt_default_syntax(
320 &self,
321 f: &mut fmt::Formatter<'_>,
322 ctx: &mut PlanRenderingContext<'_, MirRelationExpr>,
323 ) -> fmt::Result {
324 self.fmt_verbose_syntax(f, ctx)
326 }
327
328 fn fmt_verbose_syntax(
329 &self,
330 f: &mut fmt::Formatter<'_>,
331 ctx: &mut PlanRenderingContext<'_, MirRelationExpr>,
332 ) -> fmt::Result {
333 use MirRelationExpr::*;
334
335 let mode = HumanizedExplain::new(ctx.config.redacted);
336
337 match &self {
338 Constant { rows, typ: _ } => match rows {
339 Ok(rows) => {
340 if !rows.is_empty() {
341 write!(f, "{}Constant", ctx.indent)?;
342 self.fmt_analyses(f, ctx)?;
343 ctx.indented(|ctx| {
344 fmt_text_constant_rows(
345 f,
346 rows.iter().map(|(x, y)| (x, y)),
347 &mut ctx.indent,
348 mode.redacted(),
349 )
350 })?;
351 } else {
352 write!(f, "{}Constant <empty>", ctx.indent)?;
353 self.fmt_analyses(f, ctx)?;
354 }
355 }
356 Err(err) => {
357 if mode.redacted() {
358 writeln!(f, "{}Error █", ctx.indent)?;
359 } else {
360 writeln!(f, "{}Error {}", ctx.indent, err.to_string().escaped())?;
361 }
362 }
363 },
364 Let { id, value, body } => {
365 let mut bindings = vec![(id, value.as_ref())];
366 let mut head = body.as_ref();
367
368 while let Let { id, value, body } = head {
371 bindings.push((id, value.as_ref()));
372 head = body.as_ref();
373 }
374
375 if ctx.config.linear_chains {
376 writeln!(f, "{}With", ctx.indent)?;
377 ctx.indented(|ctx| {
378 for (id, value) in bindings.iter() {
379 writeln!(f, "{}cte {} =", ctx.indent, *id)?;
380 ctx.indented(|ctx| value.fmt_text(f, ctx))?;
381 }
382 Ok(())
383 })?;
384 write!(f, "{}Return", ctx.indent)?;
385 self.fmt_analyses(f, ctx)?;
386 ctx.indented(|ctx| head.fmt_text(f, ctx))?;
387 } else {
388 writeln!(f, "{}With", ctx.indent)?;
389 ctx.indented(|ctx| {
390 for (id, value) in bindings.iter() {
391 writeln!(f, "{}cte {} =", ctx.indent, *id)?;
392 ctx.indented(|ctx| value.fmt_text(f, ctx))?;
393 }
394 Ok(())
395 })?;
396 write!(f, "{}Return", ctx.indent)?;
397 self.fmt_analyses(f, ctx)?;
398 ctx.indented(|ctx| head.fmt_text(f, ctx))?;
399 }
400 }
401 LetRec {
402 ids,
403 values,
404 limits,
405 body,
406 } => {
407 assert_eq!(ids.len(), values.len());
408 assert_eq!(ids.len(), limits.len());
409 let head = body.as_ref();
410
411 let all_limits_same = limits
416 .iter()
417 .reduce(|first, i| if i == first { first } else { &None })
418 .unwrap_or(&None);
419
420 if ctx.config.linear_chains {
421 unreachable!(); } else {
423 write!(f, "{}With Mutually Recursive", ctx.indent)?;
424 if let Some(limit) = all_limits_same {
425 write!(f, " {}", limit)?;
426 }
427 writeln!(f)?;
428 ctx.indented(|ctx| {
429 let bindings = ids.iter().zip_eq(values).zip_eq(limits);
430 for ((id, value), limit) in bindings {
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 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 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!() }
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 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, idx_id: &GlobalId, constants: Option<Vec<Row>>, cse_id: Option<&LocalId>, ) -> 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 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 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
988struct 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 fmt_children(f, ctx)?;
1032 fmt_root(f, ctx)?;
1033 } else {
1034 fmt_root(f, ctx)?;
1036 *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#[derive(Debug, Clone)]
1062pub struct HumanizedExpr<'a, T, M = HumanizedExplain> {
1063 pub expr: &'a T,
1065 pub cols: Option<&'a Vec<String>>,
1068 pub mode: M,
1070}
1071
1072impl<'a, T, M: HumanizerMode> HumanizedExpr<'a, T, M> {
1073 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
1084pub trait HumanizerMode: Sized + Clone {
1095 fn default() -> Self {
1100 let redacted = !mz_ore::assert::soft_assertions_enabled();
1101 Self::new(redacted)
1102 }
1103
1104 fn new(redacted: bool) -> Self;
1107
1108 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 fn redacted(&self) -> bool;
1124
1125 fn humanize_ident(col: usize, ident: Ident, f: &mut fmt::Formatter<'_>) -> fmt::Result;
1127
1128 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 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#[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 fn humanize_ident(_col: usize, ident: Ident, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1168 write!(f, "{ident}")
1169 }
1170}
1171
1172#[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 fn humanize_ident(col: usize, ident: Ident, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1189 if ident.as_str() == UNKNOWN_COLUMN_NAME {
1190 write!(f, "#{col}")
1191 } else {
1192 write!(f, "#{col}{{{ident}}}")
1193 }
1194 }
1195}
1196
1197impl<'a, M> fmt::Display for HumanizedExpr<'a, usize, M>
1199where
1200 M: HumanizerMode,
1201{
1202 #[inline]
1203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1204 match self.cols {
1205 Some(cols) if cols.len() > *self.expr && !cols[*self.expr].is_empty() => {
1207 let ident = Ident::new_unchecked(cols[*self.expr].clone()); M::humanize_ident(*self.expr, ident, f)
1211 }
1212 _ => {
1214 write!(f, "#{}", self.expr)
1216 }
1217 }
1218 }
1219}
1220
1221impl<'a, M> fmt::Display for HumanizedExpr<'a, (&usize, &Arc<str>), M>
1224where
1225 M: HumanizerMode,
1226{
1227 #[inline]
1228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1229 match self.cols {
1230 Some(cols) if cols.len() > *self.expr.0 && !cols[*self.expr.0].is_empty() => {
1232 let ident = Ident::new_unchecked(cols[*self.expr.0].clone()); M::humanize_ident(*self.expr.0, ident, f)
1236 }
1237 _ => M::humanize_ident(*self.expr.0, Ident::new_unchecked(self.expr.1.as_ref()), f),
1239 }
1240 }
1241}
1242
1243impl<'a, M> ScalarOps for HumanizedExpr<'a, MirScalarExpr, M> {
1244 fn match_col_ref(&self) -> Option<usize> {
1245 self.expr.match_col_ref()
1246 }
1247
1248 fn references(&self, col_ref: usize) -> bool {
1249 self.expr.references(col_ref)
1250 }
1251}
1252
1253impl<'a, M> ScalarOps for HumanizedExpr<'a, usize, M> {
1254 fn match_col_ref(&self) -> Option<usize> {
1255 Some(*self.expr)
1256 }
1257
1258 fn references(&self, col_ref: usize) -> bool {
1259 col_ref == *self.expr
1260 }
1261}
1262
1263impl<'a, M> fmt::Display for HumanizedExpr<'a, MirScalarExpr, M>
1264where
1265 M: HumanizerMode,
1266{
1267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1268 use MirScalarExpr::*;
1269
1270 match self.expr {
1271 Column(i, TreatAsEqual(None)) => {
1272 self.child(i).fmt(f)
1274 }
1275 Column(i, TreatAsEqual(Some(name))) => {
1276 self.child(&(i, name)).fmt(f)
1278 }
1279 Literal(row, _) => {
1280 self.child(row).fmt(f)
1282 }
1283 CallUnmaterializable(func) => write!(f, "{}()", func),
1284 CallUnary { func, expr } => {
1285 if let crate::UnaryFunc::Not(_) = *func {
1286 if let CallUnary { func, expr } = expr.as_ref() {
1287 if let Some(is) = func.is() {
1288 let expr = self.child::<MirScalarExpr>(&*expr);
1289 return write!(f, "({}) IS NOT {}", expr, is);
1290 }
1291 }
1292 }
1293 if let Some(is) = func.is() {
1294 let expr = self.child::<MirScalarExpr>(&*expr);
1295 write!(f, "({}) IS {}", expr, is)
1296 } else {
1297 let expr = self.child::<MirScalarExpr>(&*expr);
1298 write!(f, "{}({})", func, expr)
1299 }
1300 }
1301 CallBinary { func, expr1, expr2 } => {
1302 let expr1 = self.child::<MirScalarExpr>(&*expr1);
1303 let expr2 = self.child::<MirScalarExpr>(&*expr2);
1304 if func.is_infix_op() {
1305 write!(f, "({} {} {})", expr1, func, expr2)
1306 } else {
1307 write!(f, "{}({}, {})", func, expr1, expr2)
1308 }
1309 }
1310 CallVariadic { func, exprs } => {
1311 use crate::VariadicFunc::*;
1312 let exprs = exprs.iter().map(|expr| self.child(expr));
1313 match func {
1314 ArrayCreate { .. } => {
1315 let exprs = separated(", ", exprs);
1316 write!(f, "array[{}]", exprs)
1317 }
1318 ListCreate { .. } => {
1319 let exprs = separated(", ", exprs);
1320 write!(f, "list[{}]", exprs)
1321 }
1322 RecordCreate { .. } => {
1323 let exprs = separated(", ", exprs);
1324 write!(f, "row({})", exprs)
1325 }
1326 func if func.is_infix_op() && exprs.len() > 1 => {
1327 let func = format!(" {} ", func);
1328 let exprs = separated(&func, exprs);
1329 write!(f, "({})", exprs)
1330 }
1331 func => {
1332 let exprs = separated(", ", exprs);
1333 write!(f, "{}({})", func, exprs)
1334 }
1335 }
1336 }
1337 If { cond, then, els } => {
1338 let cond = self.child::<MirScalarExpr>(&*cond);
1339 let then = self.child::<MirScalarExpr>(&*then);
1340 let els = self.child::<MirScalarExpr>(&*els);
1341 write!(f, "case when {} then {} else {} end", cond, then, els)
1342 }
1343 }
1344 }
1345}
1346
1347impl<'a, M> fmt::Display for HumanizedExpr<'a, AggregateExpr, M>
1348where
1349 M: HumanizerMode,
1350{
1351 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1352 if self.expr.is_count_asterisk() {
1353 return write!(f, "count(*)");
1354 }
1355
1356 write!(
1357 f,
1358 "{}({}",
1359 self.child(&self.expr.func),
1360 if self.expr.distinct { "distinct " } else { "" }
1361 )?;
1362
1363 self.child(&self.expr.expr).fmt(f)?;
1364 write!(f, ")")
1365 }
1366}
1367
1368impl<'a, M> fmt::Display for HumanizedExpr<'a, Result<Row, EvalError>, M>
1375where
1376 M: HumanizerMode,
1377{
1378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1379 match self.expr {
1380 Ok(row) => self.mode.humanize_datum(row.unpack_first(), f),
1381 Err(err) => {
1382 if self.mode.redacted() {
1383 write!(f, "error(█)")
1384 } else {
1385 write!(f, "error({})", err.to_string().escaped())
1386 }
1387 }
1388 }
1389 }
1390}
1391
1392impl<'a, M> fmt::Display for HumanizedExpr<'a, Datum<'a>, M>
1393where
1394 M: HumanizerMode,
1395{
1396 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1397 self.mode.humanize_datum(*self.expr, f)
1398 }
1399}
1400
1401impl<'a, M> fmt::Display for HumanizedExpr<'a, Row, M>
1402where
1403 M: HumanizerMode,
1404{
1405 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1406 f.write_str("(")?;
1407 for (i, d) in self.expr.iter().enumerate() {
1408 if i > 0 {
1409 write!(f, ", {}", self.child(&d))?;
1410 } else {
1411 write!(f, "{}", self.child(&d))?;
1412 }
1413 }
1414 f.write_str(")")?;
1415 Ok(())
1416 }
1417}
1418
1419pub fn fmt_text_constant_rows<'a, I>(
1420 f: &mut fmt::Formatter<'_>,
1421 mut rows: I,
1422 ctx: &mut Indent,
1423 redacted: bool,
1424) -> fmt::Result
1425where
1426 I: Iterator<Item = (&'a Row, &'a Diff)>,
1427{
1428 let mut row_count = Diff::ZERO;
1429 let mut first_rows = Vec::with_capacity(20);
1430 for _ in 0..20 {
1431 if let Some((row, diff)) = rows.next() {
1432 row_count += diff.abs();
1433 first_rows.push((row, diff));
1434 }
1435 }
1436 let rest_of_row_count = rows.map(|(_, diff)| diff.abs()).sum::<Diff>();
1437 if !rest_of_row_count.is_zero() {
1438 writeln!(
1439 f,
1440 "{}total_rows (diffs absed): {}",
1441 ctx,
1442 row_count + rest_of_row_count
1443 )?;
1444 writeln!(f, "{}first_rows:", ctx)?;
1445 ctx.indented(move |ctx| write_first_rows(f, &first_rows, ctx, redacted))?;
1446 } else {
1447 write_first_rows(f, &first_rows, ctx, redacted)?;
1448 }
1449 Ok(())
1450}
1451
1452fn write_first_rows(
1453 f: &mut fmt::Formatter<'_>,
1454 first_rows: &Vec<(&Row, &Diff)>,
1455 ctx: &Indent,
1456 redacted: bool,
1457) -> fmt::Result {
1458 let mode = HumanizedExplain::new(redacted);
1459 for (row, diff) in first_rows {
1460 let row = mode.expr(*row, None);
1461 if **diff == Diff::ONE {
1462 writeln!(f, "{}- {}", ctx, row)?;
1463 } else {
1464 writeln!(f, "{}- ({} x {})", ctx, row, diff)?;
1465 }
1466 }
1467 Ok(())
1468}