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