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