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