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