1use 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 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 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 _ => {
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 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 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 mode.expr(src, cols.as_ref()).fmt_text(f, &mut ctx)?;
153 } else {
154 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 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 if let Some(limit) = self.expr.limit {
201 write!(f, " limit={}", limit)?;
202 }
203 if self.expr.offset > 0 {
205 write!(f, " offset={}", self.expr.offset)?;
206 }
207 {
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 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 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 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 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 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 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
291impl 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 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 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 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!(); } 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 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 write!(f, "#{col}{{{ident}}}")
1190 }
1191}
1192
1193impl<'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 Some(cols) if cols.len() > *self.expr && !cols[*self.expr].is_empty() => {
1203 let ident = Ident::new_unchecked(cols[*self.expr].clone()); M::humanize_ident(*self.expr, ident, f)
1207 }
1208 _ => {
1210 write!(f, "#{}", self.expr)
1212 }
1213 }
1214 }
1215}
1216
1217impl<'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 Some(cols) if cols.len() > *self.expr.0 && !cols[*self.expr.0].is_empty() => {
1228 let ident = Ident::new_unchecked(cols[*self.expr.0].clone()); M::humanize_ident(*self.expr.0, ident, f)
1232 }
1233 _ => {
1235 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 self.child(i).fmt(f)
1277 }
1278 Column(i, TreatAsEqual(Some(name))) => {
1279 self.child(&(i, name)).fmt(f)
1281 }
1282 Literal(row, _) => {
1283 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
1371impl<'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}