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