1use std::fmt;
24use std::ops::Deref;
25
26use itertools::Itertools;
27use mz_expr::Id;
28use mz_expr::MfpPlan;
29use mz_expr::explain::{HumanizedExplain, HumanizerMode, fmt_text_constant_rows};
30use mz_ore::soft_assert_or_log;
31use mz_ore::str::{IndentLike, StrExt, separated};
32use mz_repr::explain::text::DisplayText;
33use mz_repr::explain::{
34 CompactScalarSeq, CompactScalars, ExplainConfig, ExprHumanizer, Indices, PlanRenderingContext,
35};
36
37use crate::plan::join::delta_join::{DeltaPathPlan, DeltaStagePlan};
38use crate::plan::join::linear_join::LinearStagePlan;
39use crate::plan::join::{DeltaJoinPlan, JoinClosure, LinearJoinPlan};
40use crate::plan::reduce::{
41 AccumulablePlan, BasicPlan, BucketedPlan, HierarchicalPlan, MonotonicPlan, SingleBasicPlan,
42};
43use crate::plan::scalar::LirScalarExpr;
44use crate::plan::threshold::ThresholdPlan;
45use crate::plan::{
46 ArrangementStrategy, AvailableCollections, LirId, LirRelationExpr, LirRelationNode,
47};
48
49impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for LirRelationExpr {
50 fn fmt_text(
51 &self,
52 f: &mut fmt::Formatter<'_>,
53 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
54 ) -> fmt::Result {
55 if ctx.config.verbose_syntax {
56 self.fmt_verbose_text(f, ctx)
57 } else {
58 self.fmt_default_text(f, ctx)
59 }
60 }
61}
62
63impl LirRelationExpr {
64 fn fmt_default_text(
70 &self,
71 f: &mut fmt::Formatter<'_>,
72 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
73 ) -> fmt::Result {
74 use LirRelationNode::*;
75
76 let mode = HumanizedExplain::new(ctx.config.redacted);
77 let annotations = PlanAnnotations::new(ctx.config.clone(), self);
78
79 match &self.node {
80 Constant { rows } => {
81 write!(f, "{}→Constant ", ctx.indent)?;
82
83 match rows {
84 Ok(rows) => write!(
85 f,
86 "({} row{})",
87 rows.len(),
88 if rows.len() == 1 { "" } else { "s" }
89 )?,
90 Err(err) => {
91 if mode.redacted() {
92 write!(f, "(error: █)")?;
93 } else {
94 write!(f, "(error: {})", err.to_string().quoted(),)?;
95 }
96 }
97 }
98
99 writeln!(f, "{annotations}")?;
100 }
101 Get { id, keys, plan } => {
102 ctx.indent.set(); let id = match id {
106 Id::Local(id) => id.to_string(),
107 Id::Global(id) => ctx
108 .humanizer
109 .humanize_id(*id)
110 .unwrap_or_else(|| id.to_string()),
111 };
112 use crate::plan::GetPlan;
114 match plan {
115 GetPlan::PassArrangements => {
116 if keys.raw && keys.arranged.is_empty() {
117 writeln!(f, "{}→Stream {id}{annotations}", ctx.indent)?;
118 } else {
119 writeln!(f, "{}→Arranged {id}{annotations}", ctx.indent)?;
122 }
123 }
124 GetPlan::Arrangement(key, Some(val), mfp) => {
125 if !mfp.is_identity() {
126 writeln!(f, "{}→Fused with Child Map/Filter/Project", ctx.indent)?;
127 ctx.indent += 1;
128 fmt_mfp_default_text(mfp, &mode, f, ctx)?;
129 ctx.indent += 1;
130 }
131
132 writeln!(f, "{}→Index Lookup on {id}{annotations}", ctx.indent)?;
133 ctx.indent += 1;
134 let key = CompactScalars(mode.seq(key, None));
135 write!(f, "{}Key: ({key}) ", ctx.indent)?;
136 let val = mode.expr(val, None);
137 writeln!(f, "Value: {val}")?;
138 }
139 GetPlan::Arrangement(key, None, mfp) => {
140 if !mfp.is_identity() {
141 writeln!(f, "{}→Fused with Child Map/Filter/Project", ctx.indent)?;
142 ctx.indent += 1;
143 fmt_mfp_default_text(mfp, &mode, f, ctx)?;
144 ctx.indent += 1;
145 }
146
147 writeln!(f, "{}→Arranged {id}{annotations}", ctx.indent)?;
148 ctx.indent += 1;
149 let key = CompactScalars(mode.seq(key, None));
150 writeln!(f, "{}Key: ({key})", ctx.indent)?;
151 }
152 GetPlan::Collection(mfp) => {
153 if !mfp.is_identity() {
154 writeln!(f, "{}→Fused with Child Map/Filter/Project", ctx.indent)?;
155 ctx.indent += 1;
156 fmt_mfp_default_text(mfp, &mode, f, ctx)?;
157 ctx.indent += 1;
158 }
159
160 writeln!(f, "{}→Read {id}{annotations}", ctx.indent)?;
161 }
162 }
163 ctx.indent.reset(); }
165 Let { id, value, body } => {
166 let mut bindings = vec![(id, value.as_ref())];
167 let mut head = body.as_ref();
168
169 while let Let { id, value, body } = &head.node {
172 bindings.push((id, value.as_ref()));
173 head = body.as_ref();
174 }
175
176 writeln!(f, "{}→With", ctx.indent)?;
177 ctx.indented(|ctx| {
178 for (id, value) in bindings.iter() {
179 writeln!(f, "{}cte {} =", ctx.indent, *id)?;
180 ctx.indented(|ctx| value.fmt_text(f, ctx))?;
181 }
182 Ok(())
183 })?;
184 writeln!(f, "{}→Return{annotations}", ctx.indent)?;
185 ctx.indented(|ctx| head.fmt_text(f, ctx))?;
186 }
187 LetRec {
188 ids,
189 values,
190 limits,
191 body,
192 } => {
193 let head = body.as_ref();
194
195 writeln!(f, "{}→With Mutually Recursive", ctx.indent)?;
196 ctx.indented(|ctx| {
197 let bindings = ids.iter().zip_eq(values).zip_eq(limits);
198 for ((id, value), limit) in bindings {
199 if let Some(limit) = limit {
200 writeln!(f, "{}cte {} {} =", ctx.indent, limit, *id)?;
201 } else {
202 writeln!(f, "{}cte {} =", ctx.indent, *id)?;
203 }
204 ctx.indented(|ctx| value.fmt_text(f, ctx))?;
205 }
206 Ok(())
207 })?;
208 writeln!(f, "{}→Return{annotations}", ctx.indent)?;
209 ctx.indented(|ctx| head.fmt_text(f, ctx))?;
210 }
211 Mfp {
212 input,
213 mfp,
214 input_key_val: _,
215 } => {
216 writeln!(f, "{}→Map/Filter/Project{annotations}", ctx.indent)?;
217 ctx.indent.set();
218
219 ctx.indent += 1;
220 fmt_mfp_default_text(mfp, &mode, f, ctx)?;
221
222 if !mfp.is_identity() {
224 ctx.indent += 1;
225 }
226 input.fmt_text(f, ctx)?;
227 ctx.indent.reset();
228 }
229 FlatMap {
230 input_key: _,
231 input,
232 exprs,
233 func,
234 mfp_after,
235 } => {
236 ctx.indent.set();
237 if !mfp_after.is_identity() {
238 writeln!(f, "{}→Fused with Child Map/Filter/Project", ctx.indent)?;
239 ctx.indent += 1;
240 fmt_mfp_default_text(mfp_after, &mode, f, ctx)?;
241 ctx.indent += 1;
242 }
243
244 let exprs = mode.seq(exprs, None);
245 let exprs = CompactScalars(exprs);
246 writeln!(
247 f,
248 "{}→Table Function {func}({exprs}){annotations}",
249 ctx.indent
250 )?;
251 ctx.indent += 1;
252
253 input.fmt_text(f, ctx)?;
254
255 ctx.indent.reset();
256 }
257 Join { inputs, plan } => {
258 use crate::plan::join::JoinPlan;
259 match plan {
260 JoinPlan::Linear(plan) => {
261 let label = if plan.has_cross_stage() {
262 "→Differential Cross Join"
263 } else {
264 "→Differential Join"
265 };
266 write!(f, "{}{label} ", ctx.indent)?;
267 fmt_join_chain(
268 f,
269 ctx.humanizer,
270 &mode,
271 inputs,
272 plan.source_relation,
273 plan.source_key.as_ref(),
274 plan.stage_plans
275 .iter()
276 .map(|s| (s.lookup_relation, &s.lookup_key)),
277 )?;
278 writeln!(f, "{annotations}")?;
279 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
280 }
281 JoinPlan::Delta(plan) => {
282 let one_shot = plan.path_plans.len() == 1;
286 let label = match (one_shot, plan.has_cross_stage()) {
287 (true, true) => "→One-Shot Delta Cross Join",
288 (true, false) => "→One-Shot Delta Join",
289 (false, true) => "→Delta Cross Join",
290 (false, false) => "→Delta Join",
291 };
292 write!(f, "{}{label}", ctx.indent)?;
293 for dpp in &plan.path_plans {
294 write!(f, " [")?;
295 fmt_join_chain(
296 f,
297 ctx.humanizer,
298 &mode,
299 inputs,
300 dpp.source_relation,
301 dpp.source_key.as_ref(),
302 dpp.stage_plans
303 .iter()
304 .map(|s| (s.lookup_relation, &s.lookup_key)),
305 )?;
306 write!(f, "]")?;
307 }
308 writeln!(f, "{annotations}")?;
309 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
310 }
311 }
312
313 ctx.indented(|ctx| {
314 for input in inputs {
315 input.fmt_text(f, ctx)?;
316 }
317 Ok(())
318 })?;
319 }
320 Reduce {
321 input_key: _,
322 input,
323 key_val_plan,
324 plan,
325 mfp_after,
326 temporal_bucketing_strategy,
327 } => {
328 ctx.indent.set();
329 if !mfp_after.is_identity() {
330 writeln!(f, "{}→Fused with Child Map/Filter/Project", ctx.indent)?;
331 ctx.indent += 1;
332 mode.expr(mfp_after.deref(), None)
333 .fmt_default_text(f, ctx)?;
334 ctx.indent += 1;
335 }
336
337 let temporally_bucketed = matches!(
338 temporal_bucketing_strategy,
339 ArrangementStrategy::TemporalBucketing
340 );
341
342 use crate::plan::reduce::ReducePlan;
343 match plan {
344 ReducePlan::Distinct => {
345 write!(f, "{}→", ctx.indent)?;
346 if temporally_bucketed {
347 write!(f, "Temporally-Bucketed ")?;
348 }
349 writeln!(f, "Distinct GroupAggregate{annotations}")?;
350 }
351 ReducePlan::Accumulable(plan) => {
352 write!(f, "{}→", ctx.indent)?;
353 if temporally_bucketed {
354 write!(f, "Temporally-Bucketed ")?;
355 }
356 writeln!(f, "Accumulable GroupAggregate{annotations}")?;
357 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
358 }
359 ReducePlan::Hierarchical(
360 plan @ HierarchicalPlan::Bucketed(BucketedPlan { buckets, .. }),
361 ) => {
362 write!(f, "{}→", ctx.indent)?;
363 if temporally_bucketed {
364 write!(f, "Temporally-Bucketed ")?;
365 }
366 write!(f, "Bucketed Hierarchical GroupAggregate (buckets:")?;
367 for bucket in buckets {
368 write!(f, " {bucket}")?;
369 }
370 writeln!(f, "){annotations}")?;
371 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
372 }
373 ReducePlan::Hierarchical(
374 plan @ HierarchicalPlan::Monotonic(MonotonicPlan {
375 must_consolidate, ..
376 }),
377 ) => {
378 write!(f, "{}→", ctx.indent)?;
379 if temporally_bucketed {
380 write!(f, "Temporally-Bucketed ")?;
381 }
382 if *must_consolidate {
383 write!(f, "Consolidating ")?;
384 }
385 writeln!(f, "Monotonic GroupAggregate{annotations}",)?;
386 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
387 }
388 ReducePlan::Basic(plan) => {
389 ctx.indent.set();
390 if let BasicPlan::Single(SingleBasicPlan {
391 fused_unnest_list, ..
392 }) = &plan
393 {
394 if *fused_unnest_list {
395 writeln!(
396 f,
397 "{}→Fused with Child Table Function unnest_list",
398 ctx.indent
399 )?;
400 ctx.indent += 1;
401 }
402 }
403 write!(f, "{}→", ctx.indent)?;
404 if temporally_bucketed {
405 write!(f, "Temporally-Bucketed ")?;
406 }
407 writeln!(f, "Non-incremental GroupAggregate{annotations}")?;
408 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
409 ctx.indent.reset();
410 }
411 }
412
413 ctx.indented(|ctx| {
414 let kvp = key_val_plan.key_plan.deref();
415 if !kvp.is_identity() {
416 writeln!(f, "{}Key:", ctx.indent)?;
417 ctx.indented(|ctx| {
418 let key_plan = mode.expr(kvp, None);
419 key_plan.fmt_default_text(f, ctx)
420 })?;
421 }
422
423 input.fmt_text(f, ctx)
424 })?;
425
426 ctx.indent.reset();
427 }
428 TopK {
429 input,
430 top_k_plan,
431 temporal_bucketing_strategy,
432 } => {
433 let temporally_bucketed = matches!(
434 temporal_bucketing_strategy,
435 ArrangementStrategy::TemporalBucketing
436 );
437 use crate::plan::top_k::TopKPlan;
438 match top_k_plan {
439 TopKPlan::MonotonicTop1(plan) => {
440 write!(f, "{}→", ctx.indent)?;
441 if temporally_bucketed {
442 write!(f, "Temporally-Bucketed ")?;
443 }
444 if plan.must_consolidate {
445 write!(f, "Consolidating ")?;
446 }
447 writeln!(f, "Monotonic Top1{annotations}")?;
448
449 ctx.indented(|ctx| {
450 if plan.group_key.len() > 0 {
451 let group_by = CompactScalars(mode.seq(&plan.group_key, None));
452 writeln!(f, "{}Group By {group_by}", ctx.indent)?;
453 }
454 if plan.order_key.len() > 0 {
455 let order_by = separated(", ", mode.seq(&plan.order_key, None));
456 writeln!(f, "{}Order By {order_by}", ctx.indent)?;
457 }
458 Ok(())
459 })?;
460 }
461 TopKPlan::MonotonicTopK(plan) => {
462 write!(f, "{}→", ctx.indent)?;
463 if temporally_bucketed {
464 write!(f, "Temporally-Bucketed ")?;
465 }
466 if plan.must_consolidate {
467 write!(f, "Consolidating ")?;
468 }
469 writeln!(f, "Monotonic TopK{annotations}")?;
470
471 ctx.indented(|ctx| {
472 if plan.group_key.len() > 0 {
473 let group_by = CompactScalars(mode.seq(&plan.group_key, None));
474 writeln!(f, "{}Group By {group_by}", ctx.indent)?;
475 }
476 if plan.order_key.len() > 0 {
477 let order_by = separated(", ", mode.seq(&plan.order_key, None));
478 writeln!(f, "{}Order By {order_by}", ctx.indent)?;
479 }
480 if let Some(limit) = &plan.limit {
481 let limit = mode.expr(limit, None);
482 writeln!(f, "{}Limit {limit}", ctx.indent)?;
483 }
484 Ok(())
485 })?;
486 }
487 TopKPlan::Basic(plan) => {
488 write!(f, "{}→", ctx.indent)?;
489 if temporally_bucketed {
490 write!(f, "Temporally-Bucketed ")?;
491 }
492 writeln!(f, "Non-monotonic TopK{annotations}")?;
493
494 ctx.indented(|ctx| {
495 if plan.group_key.len() > 0 {
496 let group_by = CompactScalars(mode.seq(&plan.group_key, None));
497 writeln!(f, "{}Group By {group_by}", ctx.indent)?;
498 }
499 if plan.order_key.len() > 0 {
500 let order_by = separated(", ", mode.seq(&plan.order_key, None));
501 writeln!(f, "{}Order By {order_by}", ctx.indent)?;
502 }
503 if let Some(limit) = &plan.limit {
504 let limit = mode.expr(limit, None);
505 writeln!(f, "{}Limit {limit}", ctx.indent)?;
506 }
507 if plan.offset != 0 {
508 let offset = plan.offset;
509 writeln!(f, "{}Offset {offset}", ctx.indent)?;
510 }
511 Ok(())
512 })?;
513 }
514 }
515
516 ctx.indented(|ctx| input.fmt_text(f, ctx))?;
517 }
518 Negate { input } => {
519 writeln!(f, "{}→Negate Diffs{annotations}", ctx.indent)?;
520
521 ctx.indented(|ctx| input.fmt_text(f, ctx))?;
522 }
523 Threshold {
524 input,
525 threshold_plan,
526 } => {
527 match threshold_plan {
528 ThresholdPlan::Basic(plan) => {
529 write!(f, "{}→Threshold Diffs ", ctx.indent)?;
530 let ensure_arrangement = Arrangement::from(&plan.ensure_arrangement);
531 ensure_arrangement.fmt_text(f, ctx)?;
532 writeln!(f, "{annotations}")?;
533 }
534 };
535
536 ctx.indented(|ctx| input.fmt_text(f, ctx))?;
537 }
538 Union {
539 inputs,
540 consolidate_output,
541 temporal_bucketing_strategies,
542 } => {
543 let any_temporally_bucketed = temporal_bucketing_strategies
544 .iter()
545 .any(|s| matches!(s, ArrangementStrategy::TemporalBucketing));
546 write!(f, "{}→", ctx.indent)?;
547 if any_temporally_bucketed {
548 write!(f, "Temporally-Bucketed ")?;
549 }
550 if *consolidate_output {
551 write!(f, "Consolidating ")?;
552 }
553 writeln!(f, "Union{annotations}")?;
554
555 ctx.indented(|ctx| {
556 for input in inputs.iter() {
557 input.fmt_text(f, ctx)?;
558 }
559 Ok(())
560 })?;
561 }
562 ArrangeBy {
563 input_key: _,
564 input,
565 input_mfp,
566 forms,
567 strategy: _,
568 } => {
569 ctx.indent.set();
570 if forms.raw && forms.arranged.is_empty() {
571 soft_assert_or_log!(forms.raw, "raw stream with no arrangements");
572 writeln!(f, "{}→Unarranged Raw Stream{annotations}", ctx.indent)?;
573 } else {
574 write!(f, "{}→Arrange", ctx.indent)?;
575
576 if !forms.arranged.is_empty() {
577 let mode = HumanizedExplain::new(ctx.config.redacted);
578 for (key, _, _) in &forms.arranged {
579 if !key.is_empty() {
580 let key = mode.seq(key, None);
581 let key = CompactScalars(key);
582 write!(f, " ({key})")?;
583 } else {
584 write!(f, " (empty key)")?;
585 }
586 }
587 }
588 writeln!(f, "{annotations}")?;
589 }
590
591 if !input_mfp.is_identity() {
592 ctx.indent += 1;
593 writeln!(f, "{}→Fused with Parent Map/Filter/Project", ctx.indent)?;
594 ctx.indented(|ctx| fmt_mfp_default_text(input_mfp, &mode, f, ctx))?;
595 }
596
597 ctx.indent += 1;
598 input.fmt_text(f, ctx)?;
599 ctx.indent.reset();
600 }
601 }
602
603 Ok(())
604 }
605
606 fn fmt_verbose_text(
607 &self,
608 f: &mut fmt::Formatter<'_>,
609 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
610 ) -> fmt::Result {
611 use LirRelationNode::*;
612
613 let mode = HumanizedExplain::new(ctx.config.redacted);
614 let annotations = PlanAnnotations::new(ctx.config.clone(), self);
615
616 match &self.node {
617 Constant { rows } => match rows {
618 Ok(rows) => {
619 if !rows.is_empty() {
620 writeln!(f, "{}Constant{}", ctx.indent, annotations)?;
621 ctx.indented(|ctx| {
622 fmt_text_constant_rows(
623 f,
624 rows.iter().map(|(data, _, diff)| (data, diff)),
625 &mut ctx.indent,
626 ctx.config.redacted,
627 )
628 })?;
629 } else {
630 writeln!(f, "{}Constant <empty>{}", ctx.indent, annotations)?;
631 }
632 }
633 Err(err) => {
634 if mode.redacted() {
635 writeln!(f, "{}Error █{}", ctx.indent, annotations)?;
636 } else {
637 {
638 writeln!(
639 f,
640 "{}Error {}{}",
641 ctx.indent,
642 err.to_string().quoted(),
643 annotations
644 )?;
645 }
646 }
647 }
648 },
649
650 Get { id, keys, plan } => {
651 ctx.indent.set(); let id = match id {
655 Id::Local(id) => id.to_string(),
656 Id::Global(id) => ctx
657 .humanizer
658 .humanize_id(*id)
659 .unwrap_or_else(|| id.to_string()),
660 };
661 use crate::plan::GetPlan;
663 match plan {
664 GetPlan::PassArrangements => {
665 writeln!(
666 f,
667 "{}Get::PassArrangements {}{}",
668 ctx.indent, id, annotations
669 )?;
670 ctx.indent += 1;
671 }
672 GetPlan::Arrangement(key, val, mfp) => {
673 writeln!(f, "{}Get::Arrangement {}{}", ctx.indent, id, annotations)?;
674 ctx.indent += 1;
675 fmt_mfp_verbose_text(mfp, &mode, f, ctx)?;
676 {
677 let key = mode.seq(key, None);
678 let key = CompactScalars(key);
679 writeln!(f, "{}key={}", ctx.indent, key)?;
680 }
681 if let Some(val) = val {
682 let val = mode.expr(val, None);
683 writeln!(f, "{}val={}", ctx.indent, val)?;
684 }
685 }
686 GetPlan::Collection(mfp) => {
687 writeln!(f, "{}Get::Collection {}{}", ctx.indent, id, annotations)?;
688 ctx.indent += 1;
689 fmt_mfp_verbose_text(mfp, &mode, f, ctx)?;
690 }
691 }
692
693 keys.fmt_text(f, ctx)?;
695
696 ctx.indent.reset(); }
698 Let { id, value, body } => {
699 let mut bindings = vec![(id, value.as_ref())];
700 let mut head = body.as_ref();
701
702 while let Let { id, value, body } = &head.node {
705 bindings.push((id, value.as_ref()));
706 head = body.as_ref();
707 }
708
709 writeln!(f, "{}With", ctx.indent)?;
710 ctx.indented(|ctx| {
711 for (id, value) in bindings.iter() {
712 writeln!(f, "{}cte {} =", ctx.indent, *id)?;
713 ctx.indented(|ctx| value.fmt_text(f, ctx))?;
714 }
715 Ok(())
716 })?;
717 writeln!(f, "{}Return{}", ctx.indent, annotations)?;
718 ctx.indented(|ctx| head.fmt_text(f, ctx))?;
719 }
720 LetRec {
721 ids,
722 values,
723 limits,
724 body,
725 } => {
726 let head = body.as_ref();
727
728 writeln!(f, "{}With Mutually Recursive", ctx.indent)?;
729 ctx.indented(|ctx| {
730 let bindings = ids.iter().zip_eq(values).zip_eq(limits);
731 for ((id, value), limit) in bindings {
732 if let Some(limit) = limit {
733 writeln!(f, "{}cte {} {} =", ctx.indent, limit, *id)?;
734 } else {
735 writeln!(f, "{}cte {} =", ctx.indent, *id)?;
736 }
737 ctx.indented(|ctx| value.fmt_text(f, ctx))?;
738 }
739 Ok(())
740 })?;
741 writeln!(f, "{}Return{}", ctx.indent, annotations)?;
742 ctx.indented(|ctx| head.fmt_text(f, ctx))?;
743 }
744 Mfp {
745 input,
746 mfp,
747 input_key_val,
748 } => {
749 writeln!(f, "{}Mfp{}", ctx.indent, annotations)?;
750 ctx.indented(|ctx| {
751 fmt_mfp_verbose_text(mfp, &mode, f, ctx)?;
752 if let Some((key, val)) = input_key_val {
753 {
754 let key = mode.seq(key, None);
755 let key = CompactScalars(key);
756 writeln!(f, "{}input_key={}", ctx.indent, key)?;
757 }
758 if let Some(val) = val {
759 let val = mode.expr(val, None);
760 writeln!(f, "{}input_val={}", ctx.indent, val)?;
761 }
762 }
763 input.fmt_text(f, ctx)
764 })?;
765 }
766 FlatMap {
767 input_key,
768 input,
769 exprs,
770 func,
771 mfp_after,
772 } => {
773 let exprs = mode.seq(exprs, None);
774 let exprs = CompactScalars(exprs);
775 writeln!(
776 f,
777 "{}FlatMap {}({}){}",
778 ctx.indent, func, exprs, annotations
779 )?;
780 ctx.indented(|ctx| {
781 if let Some(key) = input_key {
782 let key = mode.seq(key, None);
783 let key = CompactScalars(key);
784 writeln!(f, "{}input_key={}", ctx.indent, key)?;
785 }
786 if !mfp_after.is_identity() {
787 writeln!(f, "{}mfp_after", ctx.indent)?;
788 ctx.indented(|ctx| fmt_mfp_verbose_text(mfp_after, &mode, f, ctx))?;
789 }
790 input.fmt_text(f, ctx)
791 })?;
792 }
793 Join { inputs, plan } => {
794 use crate::plan::join::JoinPlan;
795 match plan {
796 JoinPlan::Linear(plan) => {
797 writeln!(f, "{}Join::Linear{}", ctx.indent, annotations)?;
798 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
799 }
800 JoinPlan::Delta(plan) => {
801 writeln!(f, "{}Join::Delta{}", ctx.indent, annotations)?;
802 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
803 }
804 }
805 ctx.indented(|ctx| {
806 for input in inputs {
807 input.fmt_text(f, ctx)?;
808 }
809 Ok(())
810 })?;
811 }
812 Reduce {
813 input_key,
814 input,
815 key_val_plan,
816 plan,
817 mfp_after,
818 temporal_bucketing_strategy,
819 } => {
820 use crate::plan::reduce::ReducePlan;
821 match plan {
822 ReducePlan::Distinct => {
823 writeln!(f, "{}Reduce::Distinct{}", ctx.indent, annotations)?;
824 }
825 ReducePlan::Accumulable(plan) => {
826 writeln!(f, "{}Reduce::Accumulable{}", ctx.indent, annotations)?;
827 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
828 }
829 ReducePlan::Hierarchical(plan) => {
830 writeln!(f, "{}Reduce::Hierarchical{}", ctx.indent, annotations)?;
831 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
832 }
833 ReducePlan::Basic(plan) => {
834 writeln!(f, "{}Reduce::Basic{}", ctx.indent, annotations)?;
835 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
836 }
837 }
838 ctx.indented(|ctx| {
839 if let Some(key) = input_key {
840 let key = mode.seq(key, None);
841 let key = CompactScalars(key);
842 writeln!(f, "{}input_key={}", ctx.indent, key)?;
843 }
844 if !matches!(temporal_bucketing_strategy, ArrangementStrategy::Direct) {
845 writeln!(
846 f,
847 "{}temporal_bucketing_strategy={}",
848 ctx.indent, temporal_bucketing_strategy
849 )?;
850 }
851 if key_val_plan.key_plan.deref().is_identity() {
852 writeln!(f, "{}key_plan=id", ctx.indent)?;
853 } else {
854 writeln!(f, "{}key_plan", ctx.indent)?;
855 ctx.indented(|ctx| {
856 let key_plan = mode.expr(key_val_plan.key_plan.deref(), None);
857 key_plan.fmt_text(f, ctx)
858 })?;
859 }
860 if key_val_plan.val_plan.deref().is_identity() {
861 writeln!(f, "{}val_plan=id", ctx.indent)?;
862 } else {
863 writeln!(f, "{}val_plan", ctx.indent)?;
864 ctx.indented(|ctx| {
865 let val_plan = mode.expr(key_val_plan.val_plan.deref(), None);
866 val_plan.fmt_text(f, ctx)
867 })?;
868 }
869 if !mfp_after.is_identity() {
870 writeln!(f, "{}mfp_after", ctx.indent)?;
871 ctx.indented(|ctx| mode.expr(mfp_after.deref(), None).fmt_text(f, ctx))?;
872 }
873
874 input.fmt_text(f, ctx)
875 })?;
876 }
877 TopK {
878 input,
879 top_k_plan,
880 temporal_bucketing_strategy,
881 } => {
882 use crate::plan::top_k::TopKPlan;
883 match top_k_plan {
884 TopKPlan::MonotonicTop1(plan) => {
885 write!(f, "{}TopK::MonotonicTop1", ctx.indent)?;
886 if plan.group_key.len() > 0 {
887 let group_by = mode.seq(&plan.group_key, None);
888 let group_by = CompactScalars(group_by);
889 write!(f, " group_by=[{}]", group_by)?;
890 }
891 if plan.order_key.len() > 0 {
892 let order_by = mode.seq(&plan.order_key, None);
893 let order_by = separated(", ", order_by);
894 write!(f, " order_by=[{}]", order_by)?;
895 }
896 if plan.must_consolidate {
897 write!(f, " must_consolidate")?;
898 }
899 }
900 TopKPlan::MonotonicTopK(plan) => {
901 write!(f, "{}TopK::MonotonicTopK", ctx.indent)?;
902 if plan.group_key.len() > 0 {
903 let group_by = mode.seq(&plan.group_key, None);
904 let group_by = CompactScalars(group_by);
905 write!(f, " group_by=[{}]", group_by)?;
906 }
907 if plan.order_key.len() > 0 {
908 let order_by = mode.seq(&plan.order_key, None);
909 let order_by = separated(", ", order_by);
910 write!(f, " order_by=[{}]", order_by)?;
911 }
912 if let Some(limit) = &plan.limit {
913 let limit = mode.expr(limit, None);
914 write!(f, " limit={}", limit)?;
915 }
916 if plan.must_consolidate {
917 write!(f, " must_consolidate")?;
918 }
919 }
920 TopKPlan::Basic(plan) => {
921 write!(f, "{}TopK::Basic", ctx.indent)?;
922 if plan.group_key.len() > 0 {
923 let group_by = mode.seq(&plan.group_key, None);
924 let group_by = CompactScalars(group_by);
925 write!(f, " group_by=[{}]", group_by)?;
926 }
927 if plan.order_key.len() > 0 {
928 let order_by = mode.seq(&plan.order_key, None);
929 let order_by = separated(", ", order_by);
930 write!(f, " order_by=[{}]", order_by)?;
931 }
932 if let Some(limit) = &plan.limit {
933 let limit = mode.expr(limit, None);
934 write!(f, " limit={}", limit)?;
935 }
936 if &plan.offset > &0 {
937 write!(f, " offset={}", plan.offset)?;
938 }
939 }
940 }
941 writeln!(f, "{}", annotations)?;
942 ctx.indented(|ctx| {
943 if !matches!(temporal_bucketing_strategy, ArrangementStrategy::Direct) {
944 writeln!(
945 f,
946 "{}temporal_bucketing_strategy={}",
947 ctx.indent, temporal_bucketing_strategy
948 )?;
949 }
950 input.fmt_text(f, ctx)
951 })?;
952 }
953 Negate { input } => {
954 writeln!(f, "{}Negate{}", ctx.indent, annotations)?;
955 ctx.indented(|ctx| input.fmt_text(f, ctx))?;
956 }
957 Threshold {
958 input,
959 threshold_plan,
960 } => {
961 use crate::plan::threshold::ThresholdPlan;
962 match threshold_plan {
963 ThresholdPlan::Basic(plan) => {
964 let ensure_arrangement = Arrangement::from(&plan.ensure_arrangement);
965 write!(f, "{}Threshold::Basic", ctx.indent)?;
966 write!(f, " ensure_arrangement=")?;
967 ensure_arrangement.fmt_text(f, ctx)?;
968 writeln!(f, "{}", annotations)?;
969 }
970 };
971 ctx.indented(|ctx| input.fmt_text(f, ctx))?;
972 }
973 Union {
974 inputs,
975 consolidate_output,
976 temporal_bucketing_strategies,
977 } => {
978 if *consolidate_output {
979 writeln!(
980 f,
981 "{}Union consolidate_output={}{}",
982 ctx.indent, consolidate_output, annotations
983 )?;
984 } else {
985 writeln!(f, "{}Union{}", ctx.indent, annotations)?;
986 }
987 ctx.indented(|ctx| {
988 if temporal_bucketing_strategies
989 .iter()
990 .any(|s| !matches!(s, ArrangementStrategy::Direct))
991 {
992 let strategies = temporal_bucketing_strategies
993 .iter()
994 .map(|s| format!("{}", s))
995 .collect::<Vec<_>>()
996 .join(", ");
997 writeln!(
998 f,
999 "{}temporal_bucketing_strategies=[{}]",
1000 ctx.indent, strategies
1001 )?;
1002 }
1003 for input in inputs.iter() {
1004 input.fmt_text(f, ctx)?;
1005 }
1006 Ok(())
1007 })?;
1008 }
1009 ArrangeBy {
1010 input_key,
1011 input,
1012 input_mfp,
1013 forms,
1014 strategy,
1015 } => {
1016 writeln!(f, "{}ArrangeBy{}", ctx.indent, annotations)?;
1017 ctx.indented(|ctx| {
1018 if let Some(key) = input_key {
1019 let key = mode.seq(key, None);
1020 let key = CompactScalars(key);
1021 writeln!(f, "{}input_key=[{}]", ctx.indent, key)?;
1022 }
1023 if !matches!(strategy, ArrangementStrategy::Direct) {
1024 writeln!(f, "{}strategy={:?}", ctx.indent, strategy)?;
1025 }
1026 fmt_mfp_verbose_text(input_mfp, &mode, f, ctx)?;
1027 forms.fmt_text(f, ctx)?;
1028 input.fmt_text(f, ctx)
1030 })?;
1031 }
1032 }
1033
1034 Ok(())
1035 }
1036}
1037
1038fn fmt_mfp_default_text(
1046 mfp_plan: &MfpPlan<LirScalarExpr>,
1047 mode: &HumanizedExplain,
1048 f: &mut fmt::Formatter<'_>,
1049 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1050) -> fmt::Result {
1051 mode.expr(mfp_plan.safe_mfp().deref(), None)
1052 .fmt_default_text(f, ctx)?;
1053 fmt_temporal_bounds(mfp_plan, mode, f, ctx)
1054}
1055
1056fn fmt_mfp_verbose_text(
1058 mfp_plan: &MfpPlan<LirScalarExpr>,
1059 mode: &HumanizedExplain,
1060 f: &mut fmt::Formatter<'_>,
1061 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1062) -> fmt::Result {
1063 mode.expr(mfp_plan.safe_mfp().deref(), None)
1064 .fmt_text(f, ctx)?;
1065 fmt_temporal_bounds(mfp_plan, mode, f, ctx)
1066}
1067
1068#[allow(clippy::needless_pass_by_ref_mut)]
1074fn fmt_temporal_bounds(
1075 mfp_plan: &MfpPlan<LirScalarExpr>,
1076 mode: &HumanizedExplain,
1077 f: &mut fmt::Formatter<'_>,
1078 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1079) -> fmt::Result {
1080 let (_, lower_bounds, upper_bounds) = mfp_plan.as_parts();
1081
1082 if lower_bounds.is_empty() && upper_bounds.is_empty() {
1083 return Ok(());
1084 }
1085
1086 write!(f, "{}TemporalFilter: ", ctx.indent)?;
1087 if !lower_bounds.is_empty() {
1088 let lowers = lower_bounds.iter().map(|b| mode.expr(b, None));
1089 write!(f, "{} <= ", separated(", ", lowers))?;
1090 }
1091 write!(f, "mz_now()")?;
1092 if !upper_bounds.is_empty() {
1093 let uppers = upper_bounds.iter().map(|b| mode.expr(b, None));
1094 write!(f, " < {}", separated(", ", uppers))?;
1095 }
1096 writeln!(f)?;
1097
1098 Ok(())
1099}
1100
1101impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for AvailableCollections {
1102 fn fmt_text(
1103 &self,
1104 f: &mut fmt::Formatter<'_>,
1105 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1106 ) -> fmt::Result {
1107 if ctx.config.verbose_syntax {
1108 self.fmt_verbose_text(f, ctx)
1109 } else {
1110 self.fmt_default_text(f, ctx)
1111 }
1112 }
1113}
1114impl AvailableCollections {
1115 fn fmt_default_text(
1116 &self,
1117 f: &mut fmt::Formatter<'_>,
1118 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1119 ) -> fmt::Result {
1120 let plural = if self.arranged.len() == 1 { "" } else { "s" };
1121 write!(
1122 f,
1123 "{}Keys: {} arrangement{plural} available",
1124 ctx.indent,
1125 self.arranged.len()
1126 )?;
1127
1128 if self.raw {
1129 writeln!(f, ", plus raw stream")?;
1130 } else {
1131 writeln!(f, ", no raw stream")?;
1132 }
1133
1134 ctx.indented(|ctx| {
1135 for (i, arrangement) in self.arranged.iter().enumerate() {
1136 let arrangement = Arrangement::from(arrangement);
1137 write!(f, "{}Arrangement {i}: ", ctx.indent)?;
1138 arrangement.fmt_text(f, ctx)?;
1139 writeln!(f, "")?;
1140 }
1141 Ok(())
1142 })?;
1143
1144 Ok(())
1145 }
1146
1147 fn fmt_verbose_text(
1148 &self,
1149 f: &mut fmt::Formatter<'_>,
1150 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1151 ) -> fmt::Result {
1152 let raw = &self.raw;
1154 writeln!(f, "{}raw={}", ctx.indent, raw)?;
1155 for (i, arrangement) in self.arranged.iter().enumerate() {
1157 let arrangement = Arrangement::from(arrangement);
1158 write!(f, "{}arrangements[{}]=", ctx.indent, i)?;
1159 arrangement.fmt_text(f, ctx)?;
1160 writeln!(f, "")?;
1161 }
1162 Ok(())
1163 }
1164}
1165
1166fn fmt_join_chain<'a, I>(
1174 f: &mut fmt::Formatter<'_>,
1175 humanizer: &dyn ExprHumanizer,
1176 mode: &HumanizedExplain,
1177 inputs: &[LirRelationExpr],
1178 source_relation: usize,
1179 source_key: Option<&'a Vec<LirScalarExpr>>,
1180 stages: I,
1181) -> fmt::Result
1182where
1183 I: IntoIterator<Item = (usize, &'a Vec<LirScalarExpr>)>,
1184{
1185 write!(
1186 f,
1187 "{}",
1188 humanize_input_name(humanizer, &inputs[source_relation], source_relation)
1189 )?;
1190 if let Some(key) = source_key {
1191 fmt_join_key_brackets(f, mode, key)?;
1192 }
1193 for (lookup_relation, lookup_key) in stages {
1194 write!(
1195 f,
1196 " » {}",
1197 humanize_input_name(humanizer, &inputs[lookup_relation], lookup_relation)
1198 )?;
1199 fmt_join_key_brackets(f, mode, lookup_key)?;
1200 }
1201 Ok(())
1202}
1203
1204fn fmt_join_key_brackets(
1206 f: &mut fmt::Formatter<'_>,
1207 mode: &HumanizedExplain,
1208 key: &Vec<LirScalarExpr>,
1209) -> fmt::Result {
1210 if key.is_empty() {
1211 write!(f, "[×]")
1212 } else {
1213 let key = CompactScalars(mode.seq(key, None));
1214 write!(f, "[{key}]")
1215 }
1216}
1217
1218fn humanize_input_name(
1222 humanizer: &dyn ExprHumanizer,
1223 plan: &LirRelationExpr,
1224 pos: usize,
1225) -> String {
1226 fn dig(humanizer: &dyn ExprHumanizer, plan: &LirRelationExpr) -> Option<String> {
1227 use crate::plan::LirRelationNode::*;
1228 match &plan.node {
1229 Get { id, .. } => match id {
1230 Id::Local(lid) => Some(lid.to_string()),
1231 Id::Global(gid) => Some(
1232 humanizer
1233 .humanize_id_unqualified(*gid)
1234 .unwrap_or_else(|| gid.to_string()),
1235 ),
1236 },
1237 ArrangeBy { input, .. } => dig(humanizer, input),
1239 Mfp { input, .. } => dig(humanizer, input),
1240 _ => None,
1241 }
1242 }
1243 match dig(humanizer, plan) {
1244 Some(name) => format!("%{pos}:{name}"),
1245 None => format!("%{pos}"),
1246 }
1247}
1248
1249impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for LinearJoinPlan {
1250 fn fmt_text(
1251 &self,
1252 f: &mut fmt::Formatter<'_>,
1253 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1254 ) -> fmt::Result {
1255 if ctx.config.verbose_syntax {
1256 self.fmt_verbose_text(f, ctx)
1257 } else {
1258 self.fmt_default_text(f, ctx)
1259 }
1260 }
1261}
1262impl LinearJoinPlan {
1263 fn has_cross_stage(&self) -> bool {
1265 self.stage_plans.iter().any(|s| s.lookup_key.is_empty())
1266 }
1267
1268 #[allow(clippy::needless_pass_by_ref_mut)]
1269 fn fmt_default_text(
1270 &self,
1271 f: &mut fmt::Formatter<'_>,
1272 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1273 ) -> fmt::Result {
1274 for stage in self.stage_plans.iter() {
1278 if stage.closure.maps_or_filters() {
1279 writeln!(f, "{}after %{}:", ctx.indent, stage.lookup_relation)?;
1280 ctx.indented(|ctx| stage.closure.fmt_default_text(f, ctx))?;
1281 }
1282 }
1283 if let Some(final_closure) = &self.final_closure {
1284 if final_closure.maps_or_filters() {
1285 writeln!(f, "{}Final closure:", ctx.indent)?;
1286 ctx.indented(|ctx| final_closure.fmt_default_text(f, ctx))?;
1287 }
1288 }
1289 Ok(())
1290 }
1291
1292 fn fmt_verbose_text(
1293 &self,
1294 f: &mut fmt::Formatter<'_>,
1295 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1296 ) -> fmt::Result {
1297 let mode = HumanizedExplain::new(ctx.config.redacted);
1298 let plan = self;
1299 if let Some(closure) = plan.final_closure.as_ref() {
1300 if !closure.is_identity() {
1301 writeln!(f, "{}final_closure", ctx.indent)?;
1302 ctx.indented(|ctx| closure.fmt_text(f, ctx))?;
1303 }
1304 }
1305 for (i, plan) in plan.stage_plans.iter().enumerate() {
1306 writeln!(f, "{}linear_stage[{}]", ctx.indent, i)?;
1307 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
1308 }
1309 if let Some(closure) = plan.initial_closure.as_ref() {
1310 if !closure.is_identity() {
1311 writeln!(f, "{}initial_closure", ctx.indent)?;
1312 ctx.indented(|ctx| closure.fmt_text(f, ctx))?;
1313 }
1314 }
1315 match &plan.source_key {
1316 Some(source_key) => {
1317 let source_key = mode.seq(source_key, None);
1318 let source_key = CompactScalars(source_key);
1319 writeln!(
1320 f,
1321 "{}source={{ relation={}, key=[{}] }}",
1322 ctx.indent, &plan.source_relation, source_key
1323 )?
1324 }
1325 None => writeln!(
1326 f,
1327 "{}source={{ relation={}, raw }}",
1328 ctx.indent, &plan.source_relation
1329 )?,
1330 };
1331 Ok(())
1332 }
1333}
1334
1335impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for LinearStagePlan {
1336 fn fmt_text(
1337 &self,
1338 f: &mut fmt::Formatter<'_>,
1339 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1340 ) -> fmt::Result {
1341 if ctx.config.verbose_syntax {
1342 self.fmt_verbose_text(f, ctx)
1343 } else {
1344 self.fmt_default_text(f, ctx)
1345 }
1346 }
1347}
1348impl LinearStagePlan {
1349 #[allow(clippy::needless_pass_by_ref_mut)]
1350 fn fmt_default_text(
1351 &self,
1352 f: &mut fmt::Formatter<'_>,
1353 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1354 ) -> fmt::Result {
1355 let lookup_relation = &self.lookup_relation;
1358 if !self.lookup_key.is_empty() {
1359 let lookup_key = CompactScalarSeq(&self.lookup_key);
1360 writeln!(
1361 f,
1362 "{}Lookup key {lookup_key} in %{lookup_relation}",
1363 ctx.indent
1364 )
1365 } else {
1366 writeln!(f, "{}Lookup in %{lookup_relation}", ctx.indent)
1367 }
1368 }
1369
1370 fn fmt_verbose_text(
1371 &self,
1372 f: &mut fmt::Formatter<'_>,
1373 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1374 ) -> fmt::Result {
1375 let mode = HumanizedExplain::new(ctx.config.redacted);
1376
1377 let plan = self;
1378 if !plan.closure.is_identity() {
1379 writeln!(f, "{}closure", ctx.indent)?;
1380 ctx.indented(|ctx| plan.closure.fmt_text(f, ctx))?;
1381 }
1382 {
1383 let lookup_relation = &plan.lookup_relation;
1384 let lookup_key = CompactScalarSeq(&plan.lookup_key);
1385 writeln!(
1386 f,
1387 "{}lookup={{ relation={}, key=[{}] }}",
1388 ctx.indent, lookup_relation, lookup_key
1389 )?;
1390 }
1391 {
1392 let stream_key = mode.seq(&plan.stream_key, None);
1393 let stream_key = CompactScalars(stream_key);
1394 let stream_thinning = Indices(&plan.stream_thinning);
1395 writeln!(
1396 f,
1397 "{}stream={{ key=[{}], thinning=({}) }}",
1398 ctx.indent, stream_key, stream_thinning
1399 )?;
1400 }
1401 Ok(())
1402 }
1403}
1404
1405impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for DeltaJoinPlan {
1406 fn fmt_text(
1407 &self,
1408 f: &mut fmt::Formatter<'_>,
1409 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1410 ) -> fmt::Result {
1411 if ctx.config.verbose_syntax {
1412 self.fmt_verbose_text(f, ctx)
1413 } else {
1414 self.fmt_default_text(f, ctx)
1415 }
1416 }
1417}
1418impl DeltaJoinPlan {
1419 fn has_cross_stage(&self) -> bool {
1421 self.path_plans
1422 .iter()
1423 .any(|p| p.stage_plans.iter().any(|s| s.lookup_key.is_empty()))
1424 }
1425
1426 fn fmt_default_text(
1427 &self,
1428 f: &mut fmt::Formatter<'_>,
1429 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1430 ) -> fmt::Result {
1431 for plan in self.path_plans.iter() {
1435 let has_stage_closure = plan.stage_plans.iter().any(|s| s.closure.maps_or_filters());
1436 let has_final_closure = plan
1437 .final_closure
1438 .as_ref()
1439 .is_some_and(|c| c.maps_or_filters());
1440 if !has_stage_closure && !has_final_closure {
1441 continue;
1442 }
1443 writeln!(f, "{}path %{}:", ctx.indent, plan.source_relation)?;
1444 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
1445 }
1446 Ok(())
1447 }
1448
1449 fn fmt_verbose_text(
1450 &self,
1451 f: &mut fmt::Formatter<'_>,
1452 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1453 ) -> fmt::Result {
1454 for (i, plan) in self.path_plans.iter().enumerate() {
1455 writeln!(f, "{}plan_path[{}]", ctx.indent, i)?;
1456 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
1457 }
1458 Ok(())
1459 }
1460}
1461
1462impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for DeltaPathPlan {
1463 fn fmt_text(
1464 &self,
1465 f: &mut fmt::Formatter<'_>,
1466 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1467 ) -> fmt::Result {
1468 if ctx.config.verbose_syntax {
1469 self.fmt_verbose_text(f, ctx)
1470 } else {
1471 self.fmt_default_text(f, ctx)
1472 }
1473 }
1474}
1475
1476impl DeltaPathPlan {
1477 #[allow(clippy::needless_pass_by_ref_mut)]
1478 fn fmt_default_text(
1479 &self,
1480 f: &mut fmt::Formatter<'_>,
1481 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1482 ) -> fmt::Result {
1483 for stage in self.stage_plans.iter() {
1484 if stage.closure.maps_or_filters() {
1485 writeln!(f, "{}after %{}:", ctx.indent, stage.lookup_relation)?;
1486 ctx.indented(|ctx| stage.closure.fmt_default_text(f, ctx))?;
1487 }
1488 }
1489 if let Some(final_closure) = &self.final_closure {
1490 if final_closure.maps_or_filters() {
1491 writeln!(f, "{}Final closure:", ctx.indent)?;
1492 ctx.indented(|ctx| final_closure.fmt_default_text(f, ctx))?;
1493 }
1494 }
1495 Ok(())
1496 }
1497
1498 fn fmt_verbose_text(
1499 &self,
1500 f: &mut fmt::Formatter<'_>,
1501 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1502 ) -> fmt::Result {
1503 let mode = HumanizedExplain::new(ctx.config.redacted);
1504 let plan = self;
1505 if let Some(closure) = plan.final_closure.as_ref() {
1506 if !closure.is_identity() {
1507 writeln!(f, "{}final_closure", ctx.indent)?;
1508 ctx.indented(|ctx| closure.fmt_text(f, ctx))?;
1509 }
1510 }
1511 for (i, plan) in plan.stage_plans.iter().enumerate().rev() {
1512 writeln!(f, "{}delta_stage[{}]", ctx.indent, i)?;
1513 ctx.indented(|ctx| plan.fmt_text(f, ctx))?;
1514 }
1515 if !plan.initial_closure.is_identity() {
1516 writeln!(f, "{}initial_closure", ctx.indent)?;
1517 ctx.indented(|ctx| plan.initial_closure.fmt_text(f, ctx))?;
1518 }
1519 match &plan.source_key {
1520 Some(source_key) => {
1521 let source_key = mode.seq(source_key, None);
1522 let source_key = CompactScalars(source_key);
1523 writeln!(
1524 f,
1525 "{}source={{ relation={}, key=[{}] }}",
1526 ctx.indent, &plan.source_relation, source_key
1527 )?
1528 }
1529 None => writeln!(
1530 f,
1531 "{}source={{ relation={}, raw }}",
1532 ctx.indent, &plan.source_relation
1533 )?,
1534 };
1535 Ok(())
1536 }
1537}
1538
1539impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for DeltaStagePlan {
1540 fn fmt_text(
1541 &self,
1542 f: &mut fmt::Formatter<'_>,
1543 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1544 ) -> fmt::Result {
1545 if ctx.config.verbose_syntax {
1546 self.fmt_verbose_text(f, ctx)
1547 } else {
1548 self.fmt_default_text(f, ctx)
1549 }
1550 }
1551}
1552impl DeltaStagePlan {
1553 #[allow(clippy::needless_pass_by_ref_mut)]
1554 fn fmt_default_text(
1555 &self,
1556 f: &mut fmt::Formatter<'_>,
1557 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1558 ) -> fmt::Result {
1559 let lookup_relation = &self.lookup_relation;
1562 let lookup_key = CompactScalarSeq(&self.lookup_key);
1563 writeln!(
1564 f,
1565 "{}Lookup key {lookup_key} in %{lookup_relation}",
1566 ctx.indent
1567 )
1568 }
1569
1570 fn fmt_verbose_text(
1571 &self,
1572 f: &mut fmt::Formatter<'_>,
1573 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1574 ) -> fmt::Result {
1575 let mode = HumanizedExplain::new(ctx.config.redacted);
1576 let plan = self;
1577 if !plan.closure.is_identity() {
1578 writeln!(f, "{}closure", ctx.indent)?;
1579 ctx.indented(|ctx| plan.closure.fmt_text(f, ctx))?;
1580 }
1581 {
1582 let lookup_relation = &plan.lookup_relation;
1583 let lookup_key = mode.seq(&plan.lookup_key, None);
1584 let lookup_key = CompactScalars(lookup_key);
1585 writeln!(
1586 f,
1587 "{}lookup={{ relation={}, key=[{}] }}",
1588 ctx.indent, lookup_relation, lookup_key
1589 )?;
1590 }
1591 {
1592 let stream_key = mode.seq(&plan.stream_key, None);
1593 let stream_key = CompactScalars(stream_key);
1594 let stream_thinning = mode.seq(&plan.stream_thinning, None);
1595 let stream_thinning = CompactScalars(stream_thinning);
1596 writeln!(
1597 f,
1598 "{}stream={{ key=[{}], thinning=({}) }}",
1599 ctx.indent, stream_key, stream_thinning
1600 )?;
1601 }
1602 Ok(())
1603 }
1604}
1605
1606impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for JoinClosure {
1607 fn fmt_text(
1608 &self,
1609 f: &mut fmt::Formatter<'_>,
1610 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1611 ) -> fmt::Result {
1612 if ctx.config.verbose_syntax {
1613 self.fmt_verbose_text(f, ctx)
1614 } else {
1615 self.fmt_default_text(f, ctx)
1616 }
1617 }
1618}
1619impl JoinClosure {
1620 fn fmt_default_text(
1621 &self,
1622 f: &mut fmt::Formatter<'_>,
1623 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1624 ) -> fmt::Result {
1625 let mode = HumanizedExplain::new(ctx.config.redacted);
1626 if !self.before.is_identity() {
1627 mode.expr(self.before.deref(), None)
1628 .fmt_default_text(f, ctx)?;
1629 }
1630 if !self.ready_equivalences.is_empty() {
1631 let equivalences = separated(
1632 " AND ",
1633 self.ready_equivalences
1634 .iter()
1635 .map(|equivalence| separated(" = ", mode.seq(equivalence, None))),
1636 );
1637 writeln!(f, "{}Equivalences: {equivalences}", ctx.indent)?;
1638 }
1639 Ok(())
1640 }
1641
1642 fn fmt_verbose_text(
1643 &self,
1644 f: &mut fmt::Formatter<'_>,
1645 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1646 ) -> fmt::Result {
1647 let mode = HumanizedExplain::new(ctx.config.redacted);
1648 mode.expr(self.before.deref(), None).fmt_text(f, ctx)?;
1649 if !self.ready_equivalences.is_empty() {
1650 let equivalences = separated(
1651 " AND ",
1652 self.ready_equivalences
1653 .iter()
1654 .map(|equivalence| separated(" = ", mode.seq(equivalence, None))),
1655 );
1656 writeln!(f, "{}ready_equivalences={}", ctx.indent, equivalences)?;
1657 }
1658 Ok(())
1659 }
1660}
1661
1662impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for AccumulablePlan {
1663 fn fmt_text(
1664 &self,
1665 f: &mut fmt::Formatter<'_>,
1666 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1667 ) -> fmt::Result {
1668 if ctx.config.verbose_syntax {
1669 self.fmt_verbose_text(f, ctx)
1670 } else {
1671 self.fmt_default_text(f, ctx)
1672 }
1673 }
1674}
1675impl AccumulablePlan {
1676 #[allow(clippy::needless_pass_by_ref_mut)]
1677 fn fmt_default_text(
1678 &self,
1679 f: &mut fmt::Formatter<'_>,
1680 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1681 ) -> fmt::Result {
1682 let mode = HumanizedExplain::new(ctx.config.redacted);
1683
1684 if !self.simple_aggrs.is_empty() {
1685 let simple_aggrs = self
1686 .simple_aggrs
1687 .iter()
1688 .map(|(_i_datum, agg)| mode.expr(agg, None));
1689 let simple_aggrs = separated(", ", simple_aggrs);
1690 writeln!(f, "{}Simple aggregates: {simple_aggrs}", ctx.indent)?;
1691 }
1692
1693 if !self.distinct_aggrs.is_empty() {
1694 let distinct_aggrs = self
1695 .distinct_aggrs
1696 .iter()
1697 .map(|(_i_datum, agg)| mode.expr(agg, None));
1698 let distinct_aggrs = separated(", ", distinct_aggrs);
1699 writeln!(f, "{}Distinct aggregates: {distinct_aggrs}", ctx.indent)?;
1700 }
1701 Ok(())
1702 }
1703
1704 #[allow(clippy::needless_pass_by_ref_mut)]
1705 fn fmt_verbose_text(
1706 &self,
1707 f: &mut fmt::Formatter<'_>,
1708 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1709 ) -> fmt::Result {
1710 let mode = HumanizedExplain::new(ctx.config.redacted);
1711 for (i, (i_datum, agg)) in self.simple_aggrs.iter().enumerate() {
1719 let agg = mode.expr(agg, None);
1720 write!(f, "{}simple_aggrs[{}]=", ctx.indent, i)?;
1721 writeln!(f, "({}, {})", i_datum, agg)?;
1722 }
1723 for (i, (i_datum, agg)) in self.distinct_aggrs.iter().enumerate() {
1725 let agg = mode.expr(agg, None);
1726 write!(f, "{}distinct_aggrs[{}]=", ctx.indent, i)?;
1727 writeln!(f, "({}, {})", i_datum, agg)?;
1728 }
1729 Ok(())
1730 }
1731}
1732
1733impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for HierarchicalPlan {
1734 fn fmt_text(
1735 &self,
1736 f: &mut fmt::Formatter<'_>,
1737 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1738 ) -> fmt::Result {
1739 if ctx.config.verbose_syntax {
1740 self.fmt_verbose_text(f, ctx)
1741 } else {
1742 self.fmt_default_text(f, ctx)
1743 }
1744 }
1745}
1746impl HierarchicalPlan {
1747 #[allow(clippy::needless_pass_by_ref_mut)]
1748 fn fmt_default_text(
1749 &self,
1750 f: &mut fmt::Formatter<'_>,
1751 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1752 ) -> fmt::Result {
1753 let mode = HumanizedExplain::new(ctx.config.redacted);
1754 let aggr_funcs = mode.seq(self.aggr_funcs(), None);
1755 let aggr_funcs = separated(", ", aggr_funcs);
1756 writeln!(f, "{}Aggregations: {aggr_funcs}", ctx.indent)
1757 }
1758
1759 #[allow(clippy::needless_pass_by_ref_mut)]
1760 fn fmt_verbose_text(
1761 &self,
1762 f: &mut fmt::Formatter<'_>,
1763 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1764 ) -> fmt::Result {
1765 let mode = HumanizedExplain::new(ctx.config.redacted);
1766 match self {
1767 HierarchicalPlan::Monotonic(plan) => {
1768 let aggr_funcs = mode.seq(&plan.aggr_funcs, None);
1769 let aggr_funcs = separated(", ", aggr_funcs);
1770 writeln!(f, "{}aggr_funcs=[{}]", ctx.indent, aggr_funcs)?;
1771 writeln!(f, "{}monotonic", ctx.indent)?;
1772 if plan.must_consolidate {
1773 writeln!(f, "{}must_consolidate", ctx.indent)?;
1774 }
1775 }
1776 HierarchicalPlan::Bucketed(plan) => {
1777 let aggr_funcs = mode.seq(&plan.aggr_funcs, None);
1778 let aggr_funcs = separated(", ", aggr_funcs);
1779 writeln!(f, "{}aggr_funcs=[{}]", ctx.indent, aggr_funcs)?;
1780 let buckets = separated(", ", &plan.buckets);
1781 writeln!(f, "{}buckets=[{}]", ctx.indent, buckets)?;
1782 }
1783 }
1784 Ok(())
1785 }
1786}
1787
1788impl DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for BasicPlan {
1789 fn fmt_text(
1790 &self,
1791 f: &mut fmt::Formatter<'_>,
1792 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1793 ) -> fmt::Result {
1794 if ctx.config.verbose_syntax {
1795 self.fmt_verbose_text(f, ctx)
1796 } else {
1797 self.fmt_default_text(f, ctx)
1798 }
1799 }
1800}
1801impl BasicPlan {
1802 #[allow(clippy::needless_pass_by_ref_mut)]
1803 fn fmt_default_text(
1804 &self,
1805 f: &mut fmt::Formatter<'_>,
1806 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1807 ) -> fmt::Result {
1808 let mode = HumanizedExplain::new(ctx.config.redacted);
1809 match self {
1810 BasicPlan::Single(SingleBasicPlan {
1811 expr,
1812 fused_unnest_list: _,
1813 }) => {
1814 let agg = mode.expr(expr, None);
1815 writeln!(f, "{}Aggregation: {agg}", ctx.indent)?;
1816 }
1817 BasicPlan::Multiple(aggs) => {
1818 let mode = HumanizedExplain::new(ctx.config.redacted);
1819 write!(f, "{}Aggregations:", ctx.indent)?;
1820
1821 for agg in aggs.iter() {
1822 let agg = mode.expr(agg, None);
1823 write!(f, " {agg}")?;
1824 }
1825 writeln!(f)?;
1826 }
1827 }
1828 Ok(())
1829 }
1830
1831 #[allow(clippy::needless_pass_by_ref_mut)]
1832 fn fmt_verbose_text(
1833 &self,
1834 f: &mut fmt::Formatter<'_>,
1835 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1836 ) -> fmt::Result {
1837 let mode = HumanizedExplain::new(ctx.config.redacted);
1838 match self {
1839 BasicPlan::Single(SingleBasicPlan {
1840 expr,
1841 fused_unnest_list,
1842 }) => {
1843 let agg = mode.expr(expr, None);
1844 let fused_unnest_list = if *fused_unnest_list {
1845 ", fused_unnest_list=true"
1846 } else {
1847 ""
1848 };
1849 writeln!(f, "{}aggr=({}{})", ctx.indent, agg, fused_unnest_list)?;
1850 }
1851 BasicPlan::Multiple(aggs) => {
1852 for (i, agg) in aggs.iter().enumerate() {
1853 let agg = mode.expr(agg, None);
1854 writeln!(f, "{}aggrs[{}]={}", ctx.indent, i, agg)?;
1855 }
1856 }
1857 }
1858 Ok(())
1859 }
1860}
1861
1862struct Arrangement<'a> {
1864 key: &'a Vec<LirScalarExpr>,
1865 permutation: Permutation<'a>,
1866 thinning: &'a Vec<usize>,
1867}
1868
1869impl<'a> From<&'a (Vec<LirScalarExpr>, Vec<usize>, Vec<usize>)> for Arrangement<'a> {
1870 fn from(
1871 (key, permutation, thinning): &'a (Vec<LirScalarExpr>, Vec<usize>, Vec<usize>),
1872 ) -> Self {
1873 Arrangement {
1874 key,
1875 permutation: Permutation(permutation),
1876 thinning,
1877 }
1878 }
1879}
1880
1881impl<'a> DisplayText<PlanRenderingContext<'_, LirRelationExpr>> for Arrangement<'a> {
1882 fn fmt_text(
1883 &self,
1884 f: &mut fmt::Formatter<'_>,
1885 ctx: &mut PlanRenderingContext<'_, LirRelationExpr>,
1886 ) -> fmt::Result {
1887 if ctx.config.verbose_syntax {
1888 self.fmt_verbose_text(f, ctx)
1889 } else {
1890 self.fmt_default_text(f, ctx)
1891 }
1892 }
1893}
1894
1895impl<'a> Arrangement<'a> {
1896 #[allow(clippy::needless_pass_by_ref_mut)]
1897 fn fmt_default_text(
1898 &self,
1899 f: &mut fmt::Formatter<'_>,
1900 ctx: &PlanRenderingContext<'_, LirRelationExpr>,
1901 ) -> fmt::Result {
1902 let mode = HumanizedExplain::new(ctx.config.redacted);
1903 if !self.key.is_empty() {
1904 let key = mode.seq(self.key, None);
1905 let key = CompactScalars(key);
1906 write!(f, "{key}")
1907 } else {
1908 write!(f, "(empty key)")
1909 }
1910 }
1911
1912 #[allow(clippy::needless_pass_by_ref_mut)]
1913 fn fmt_verbose_text(
1914 &self,
1915 f: &mut fmt::Formatter<'_>,
1916 ctx: &PlanRenderingContext<'_, LirRelationExpr>,
1917 ) -> fmt::Result {
1918 let mode = HumanizedExplain::new(ctx.config.redacted);
1919 let key = mode.seq(self.key, None);
1921 let key = CompactScalars(key);
1922 let permutation = &self.permutation;
1924 let thinning = Indices(self.thinning);
1926 write!(
1928 f,
1929 "{{ key=[{}], permutation={}, thinning=({}) }}",
1930 key, permutation, thinning
1931 )
1932 }
1933}
1934
1935struct Permutation<'a>(&'a Vec<usize>);
1937
1938impl<'a> fmt::Display for Permutation<'a> {
1939 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1940 let mut pairs = vec![];
1941 for (x, y) in self.0.iter().enumerate().filter(|(x, y)| x != *y) {
1942 pairs.push(format!("#{}: #{}", x, y));
1943 }
1944
1945 if pairs.len() > 0 {
1946 write!(f, "{{{}}}", separated(", ", pairs))
1947 } else {
1948 write!(f, "id")
1949 }
1950 }
1951}
1952
1953struct PlanAnnotations {
1955 config: ExplainConfig,
1956 node_id: LirId,
1957}
1958
1959impl PlanAnnotations {
1965 fn new(config: ExplainConfig, plan: &LirRelationExpr) -> Self {
1966 let node_id = plan.lir_id;
1967 Self { config, node_id }
1968 }
1969}
1970
1971impl fmt::Display for PlanAnnotations {
1972 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1973 if self.config.node_ids {
1974 f.debug_struct(" //")
1975 .field("node_id", &self.node_id)
1976 .finish()
1977 } else {
1978 Ok(())
1980 }
1981 }
1982}