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