1use std::collections::{BTreeMap, HashMap};
22use std::fmt;
23use std::fmt::Formatter;
24use std::time::Duration;
25
26use arrow::datatypes::SchemaRef;
27
28use datafusion_common::display::{GraphvizBuilder, PlanType, StringifiedPlan};
29use datafusion_expr::display_schema;
30use datafusion_physical_expr::LexOrdering;
31
32use crate::metrics::{MetricCategory, MetricType, MetricValue};
33use crate::render_tree::RenderTree;
34
35use crate::statistics::{StatisticsArgs, StatisticsContext};
36
37use super::{ExecutionPlan, ExecutionPlanVisitor, accept};
38
39#[derive(Debug, Clone, Copy, PartialEq)]
41pub enum DisplayFormatType {
42 Default,
47 Verbose,
51 TreeRender,
83}
84
85#[derive(Debug, Clone)]
119pub struct DisplayableExecutionPlan<'a> {
120 inner: &'a dyn ExecutionPlan,
121 show_metrics: ShowMetrics,
123 show_statistics: bool,
125 show_schema: bool,
127 metric_types: Vec<MetricType>,
129 metric_categories: Option<Vec<MetricCategory>>,
132 metric_names: Option<Vec<String>>,
135 tree_maximum_render_width: usize,
137 summary: Option<AnalyzeSummary>,
140}
141
142#[derive(Debug, Clone, Copy)]
145struct AnalyzeSummary {
146 total_rows: Option<usize>,
147 duration: Option<Duration>,
148}
149
150impl<'a> DisplayableExecutionPlan<'a> {
151 fn default_metric_types() -> Vec<MetricType> {
152 vec![MetricType::Summary, MetricType::Dev]
153 }
154
155 pub fn new(inner: &'a dyn ExecutionPlan) -> Self {
158 Self {
159 inner,
160 show_metrics: ShowMetrics::None,
161 show_statistics: false,
162 show_schema: false,
163 metric_types: Self::default_metric_types(),
164 metric_categories: None,
165 metric_names: None,
166 tree_maximum_render_width: 240,
167 summary: None,
168 }
169 }
170
171 pub fn with_metrics(inner: &'a dyn ExecutionPlan) -> Self {
175 Self {
176 inner,
177 show_metrics: ShowMetrics::Aggregated,
178 show_statistics: false,
179 show_schema: false,
180 metric_types: Self::default_metric_types(),
181 metric_categories: None,
182 metric_names: None,
183 tree_maximum_render_width: 240,
184 summary: None,
185 }
186 }
187
188 pub fn with_full_metrics(inner: &'a dyn ExecutionPlan) -> Self {
192 Self {
193 inner,
194 show_metrics: ShowMetrics::Full,
195 show_statistics: false,
196 show_schema: false,
197 metric_types: Self::default_metric_types(),
198 metric_categories: None,
199 metric_names: None,
200 tree_maximum_render_width: 240,
201 summary: None,
202 }
203 }
204
205 pub fn set_show_schema(mut self, show_schema: bool) -> Self {
210 self.show_schema = show_schema;
211 self
212 }
213
214 pub fn set_show_statistics(mut self, show_statistics: bool) -> Self {
216 self.show_statistics = show_statistics;
217 self
218 }
219
220 pub fn set_metric_types(mut self, metric_types: Vec<MetricType>) -> Self {
222 self.metric_types = metric_types;
223 self
224 }
225
226 pub fn set_metric_categories(
236 mut self,
237 metric_categories: Option<Vec<MetricCategory>>,
238 ) -> Self {
239 self.metric_categories = metric_categories;
240 self
241 }
242
243 pub fn set_metric_names(mut self, metric_names: Vec<String>) -> Self {
251 self.metric_names = Some(metric_names);
252 self
253 }
254
255 pub fn set_tree_maximum_render_width(mut self, width: usize) -> Self {
257 self.tree_maximum_render_width = width;
258 self
259 }
260
261 pub fn set_summary(
265 mut self,
266 total_rows: Option<usize>,
267 duration: Option<Duration>,
268 ) -> Self {
269 self.summary = Some(AnalyzeSummary {
270 total_rows,
271 duration,
272 });
273 self
274 }
275
276 pub fn indent(&self, verbose: bool) -> impl fmt::Display + 'a {
287 let format_type = if verbose {
288 DisplayFormatType::Verbose
289 } else {
290 DisplayFormatType::Default
291 };
292 struct Wrapper<'a> {
293 format_type: DisplayFormatType,
294 plan: &'a dyn ExecutionPlan,
295 show_metrics: ShowMetrics,
296 show_statistics: bool,
297 show_schema: bool,
298 metric_types: Vec<MetricType>,
299 metric_categories: Option<Vec<MetricCategory>>,
300 metric_names: Option<Vec<String>>,
301 }
302 impl fmt::Display for Wrapper<'_> {
303 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
304 let mut visitor = IndentVisitor {
305 t: self.format_type,
306 f,
307 indent: 0,
308 show_metrics: self.show_metrics,
309 show_statistics: self.show_statistics,
310 show_schema: self.show_schema,
311 metric_types: &self.metric_types,
312 metric_categories: self.metric_categories.as_deref(),
313 metric_names: self.metric_names.as_deref(),
314 };
315 accept(self.plan, &mut visitor)
316 }
317 }
318 Wrapper {
319 format_type,
320 plan: self.inner,
321 show_metrics: self.show_metrics,
322 show_statistics: self.show_statistics,
323 show_schema: self.show_schema,
324 metric_types: self.metric_types.clone(),
325 metric_categories: self.metric_categories.clone(),
326 metric_names: self.metric_names.clone(),
327 }
328 }
329
330 pub fn graphviz(&self) -> impl fmt::Display + 'a {
342 struct Wrapper<'a> {
343 plan: &'a dyn ExecutionPlan,
344 show_metrics: ShowMetrics,
345 show_statistics: bool,
346 metric_types: Vec<MetricType>,
347 metric_categories: Option<Vec<MetricCategory>>,
348 metric_names: Option<Vec<String>>,
349 }
350 impl fmt::Display for Wrapper<'_> {
351 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
352 let t = DisplayFormatType::Default;
353
354 let mut visitor = GraphvizVisitor {
355 f,
356 t,
357 show_metrics: self.show_metrics,
358 show_statistics: self.show_statistics,
359 metric_types: &self.metric_types,
360 metric_categories: self.metric_categories.as_deref(),
361 metric_names: self.metric_names.as_deref(),
362 graphviz_builder: GraphvizBuilder::default(),
363 parents: Vec::new(),
364 };
365
366 visitor.start_graph()?;
367
368 accept(self.plan, &mut visitor)?;
369
370 visitor.end_graph()?;
371 Ok(())
372 }
373 }
374
375 Wrapper {
376 plan: self.inner,
377 show_metrics: self.show_metrics,
378 show_statistics: self.show_statistics,
379 metric_types: self.metric_types.clone(),
380 metric_categories: self.metric_categories.clone(),
381 metric_names: self.metric_names.clone(),
382 }
383 }
384
385 pub fn tree_render(&self) -> impl fmt::Display + 'a {
389 struct Wrapper<'a> {
390 plan: &'a dyn ExecutionPlan,
391 maximum_render_width: usize,
392 }
393 impl fmt::Display for Wrapper<'_> {
394 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
395 let mut visitor = TreeRenderVisitor {
396 f,
397 maximum_render_width: self.maximum_render_width,
398 };
399 visitor.visit(self.plan)
400 }
401 }
402 Wrapper {
403 plan: self.inner,
404 maximum_render_width: self.tree_maximum_render_width,
405 }
406 }
407
408 pub fn pgjson(&self, verbose: bool) -> impl fmt::Display + 'a {
423 struct Wrapper<'a> {
424 plan: &'a dyn ExecutionPlan,
425 verbose: bool,
426 show_metrics: ShowMetrics,
427 show_schema: bool,
428 metric_types: Vec<MetricType>,
429 metric_categories: Option<Vec<MetricCategory>>,
430 metric_names: Option<Vec<String>>,
431 summary: Option<AnalyzeSummary>,
432 }
433 impl fmt::Display for Wrapper<'_> {
434 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
435 let mut visitor = PgJsonExecutionPlanVisitor {
436 verbose: self.verbose,
437 show_metrics: self.show_metrics,
438 show_schema: self.show_schema,
439 metric_types: &self.metric_types,
440 metric_categories: self.metric_categories.as_deref(),
441 metric_names: self.metric_names.as_deref(),
442 objects: HashMap::new(),
443 parent_ids: Vec::new(),
444 next_id: 0,
445 root: None,
446 };
447 accept(self.plan, &mut visitor).map_err(|_| fmt::Error)?;
448 let root = visitor.root.ok_or(fmt::Error)?;
449 let mut root_entry = serde_json::json!({ "Plan": root });
450 if let Some(summary) = self.summary {
451 if let Some(total_rows) = summary.total_rows {
452 root_entry["Total Rows"] = serde_json::Value::from(total_rows);
453 }
454 if let Some(duration) = summary.duration {
455 root_entry["Duration"] =
456 serde_json::Value::from(format!("{duration:?}"));
457 }
458 }
459 let doc = serde_json::Value::Array(vec![root_entry]);
460 write!(
461 f,
462 "{}",
463 serde_json::to_string_pretty(&doc).map_err(|_| fmt::Error)?
464 )
465 }
466 }
467
468 Wrapper {
469 plan: self.inner,
470 verbose,
471 show_metrics: self.show_metrics,
472 show_schema: self.show_schema,
473 metric_types: self.metric_types.clone(),
474 metric_categories: self.metric_categories.clone(),
475 metric_names: self.metric_names.clone(),
476 summary: self.summary,
477 }
478 }
479
480 pub fn one_line(&self) -> impl fmt::Display + 'a {
483 struct Wrapper<'a> {
484 plan: &'a dyn ExecutionPlan,
485 show_metrics: ShowMetrics,
486 show_statistics: bool,
487 show_schema: bool,
488 metric_types: Vec<MetricType>,
489 metric_categories: Option<Vec<MetricCategory>>,
490 metric_names: Option<Vec<String>>,
491 }
492
493 impl fmt::Display for Wrapper<'_> {
494 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
495 let mut visitor = IndentVisitor {
496 f,
497 t: DisplayFormatType::Default,
498 indent: 0,
499 show_metrics: self.show_metrics,
500 show_statistics: self.show_statistics,
501 show_schema: self.show_schema,
502 metric_types: &self.metric_types,
503 metric_categories: self.metric_categories.as_deref(),
504 metric_names: self.metric_names.as_deref(),
505 };
506 visitor.pre_visit(self.plan)?;
507 Ok(())
508 }
509 }
510
511 Wrapper {
512 plan: self.inner,
513 show_metrics: self.show_metrics,
514 show_statistics: self.show_statistics,
515 show_schema: self.show_schema,
516 metric_types: self.metric_types.clone(),
517 metric_categories: self.metric_categories.clone(),
518 metric_names: self.metric_names.clone(),
519 }
520 }
521
522 #[deprecated(since = "47.0.0", note = "indent() or tree_render() instead")]
523 pub fn to_stringified(
524 &self,
525 verbose: bool,
526 plan_type: PlanType,
527 explain_format: DisplayFormatType,
528 ) -> StringifiedPlan {
529 match (&explain_format, &plan_type) {
530 (DisplayFormatType::TreeRender, PlanType::FinalPhysicalPlan) => {
531 StringifiedPlan::new(plan_type, self.tree_render().to_string())
532 }
533 _ => StringifiedPlan::new(plan_type, self.indent(verbose).to_string()),
534 }
535 }
536}
537
538#[derive(Debug, Clone, Copy)]
540enum ShowMetrics {
541 None,
543
544 Aggregated,
546
547 Full,
549}
550
551struct IndentVisitor<'a, 'b> {
561 t: DisplayFormatType,
563 f: &'a mut Formatter<'b>,
565 indent: usize,
567 show_metrics: ShowMetrics,
569 show_statistics: bool,
571 show_schema: bool,
573 metric_types: &'a [MetricType],
575 metric_categories: Option<&'a [MetricCategory]>,
577 metric_names: Option<&'a [String]>,
579}
580
581impl ExecutionPlanVisitor for IndentVisitor<'_, '_> {
582 type Error = fmt::Error;
583 fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
584 write!(self.f, "{:indent$}", "", indent = self.indent * 2)?;
585 plan.fmt_as(self.t, self.f)?;
586 match self.show_metrics {
587 ShowMetrics::None => {}
588 ShowMetrics::Aggregated => {
589 if let Some(metrics) = plan.metrics() {
590 let mut metrics = metrics
591 .filter_by_metric_types(self.metric_types)
592 .aggregate_by_name()
593 .sorted_for_display()
594 .timestamps_removed();
595 if let Some(cats) = self.metric_categories {
596 metrics = metrics.filter_by_categories(cats);
597 }
598 if let Some(names) = self.metric_names {
599 metrics = metrics.filter_by_names(names);
600 }
601 write!(self.f, ", metrics=[{metrics}]")?;
602 } else {
603 write!(self.f, ", metrics=[]")?;
604 }
605 }
606 ShowMetrics::Full => {
607 if let Some(metrics) = plan.metrics() {
608 let mut metrics = metrics.filter_by_metric_types(self.metric_types);
609 if let Some(cats) = self.metric_categories {
610 metrics = metrics.filter_by_categories(cats);
611 }
612 if let Some(names) = self.metric_names {
613 metrics = metrics.filter_by_names(names);
614 }
615 write!(self.f, ", metrics=[{metrics}]")?;
616 } else {
617 write!(self.f, ", metrics=[]")?;
618 }
619 }
620 }
621 if self.show_statistics {
622 let stats = StatisticsContext::new()
623 .compute(plan, &StatisticsArgs::new())
624 .map_err(|_e| fmt::Error)?;
625 write!(self.f, ", statistics=[{stats}]")?;
626 }
627 if self.show_schema {
628 write!(
629 self.f,
630 ", schema={}",
631 display_schema(plan.schema().as_ref())
632 )?;
633 }
634 writeln!(self.f)?;
635 self.indent += 1;
636 Ok(true)
637 }
638
639 fn post_visit(&mut self, _plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
640 self.indent -= 1;
641 Ok(true)
642 }
643}
644
645struct GraphvizVisitor<'a, 'b> {
646 f: &'a mut Formatter<'b>,
647 t: DisplayFormatType,
649 show_metrics: ShowMetrics,
651 show_statistics: bool,
653 metric_types: &'a [MetricType],
655 metric_categories: Option<&'a [MetricCategory]>,
657 metric_names: Option<&'a [String]>,
659
660 graphviz_builder: GraphvizBuilder,
661 parents: Vec<usize>,
663}
664
665impl GraphvizVisitor<'_, '_> {
666 fn start_graph(&mut self) -> fmt::Result {
667 self.graphviz_builder.start_graph(self.f)
668 }
669
670 fn end_graph(&mut self) -> fmt::Result {
671 self.graphviz_builder.end_graph(self.f)
672 }
673}
674
675impl ExecutionPlanVisitor for GraphvizVisitor<'_, '_> {
676 type Error = fmt::Error;
677
678 fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
679 let id = self.graphviz_builder.next_id();
680
681 struct Wrapper<'a>(&'a dyn ExecutionPlan, DisplayFormatType);
682
683 impl fmt::Display for Wrapper<'_> {
684 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
685 self.0.fmt_as(self.1, f)
686 }
687 }
688
689 let label = { format!("{}", Wrapper(plan, self.t)) };
690
691 let metrics = match self.show_metrics {
692 ShowMetrics::None => "".to_string(),
693 ShowMetrics::Aggregated => {
694 if let Some(metrics) = plan.metrics() {
695 let mut metrics = metrics
696 .filter_by_metric_types(self.metric_types)
697 .aggregate_by_name()
698 .sorted_for_display()
699 .timestamps_removed();
700 if let Some(cats) = self.metric_categories {
701 metrics = metrics.filter_by_categories(cats);
702 }
703 if let Some(names) = self.metric_names {
704 metrics = metrics.filter_by_names(names);
705 }
706 format!("metrics=[{metrics}]")
707 } else {
708 "metrics=[]".to_string()
709 }
710 }
711 ShowMetrics::Full => {
712 if let Some(metrics) = plan.metrics() {
713 let mut metrics = metrics.filter_by_metric_types(self.metric_types);
714 if let Some(cats) = self.metric_categories {
715 metrics = metrics.filter_by_categories(cats);
716 }
717 if let Some(names) = self.metric_names {
718 metrics = metrics.filter_by_names(names);
719 }
720 format!("metrics=[{metrics}]")
721 } else {
722 "metrics=[]".to_string()
723 }
724 }
725 };
726
727 let statistics = if self.show_statistics {
728 let stats = StatisticsContext::new()
729 .compute(plan, &StatisticsArgs::new())
730 .map_err(|_e| fmt::Error)?;
731 format!("statistics=[{stats}]")
732 } else {
733 "".to_string()
734 };
735
736 let delimiter = if !metrics.is_empty() && !statistics.is_empty() {
737 ", "
738 } else {
739 ""
740 };
741
742 self.graphviz_builder.add_node(
743 self.f,
744 id,
745 &label,
746 Some(&format!("{metrics}{delimiter}{statistics}")),
747 )?;
748
749 if let Some(parent_node_id) = self.parents.last() {
750 self.graphviz_builder
751 .add_edge(self.f, *parent_node_id, id)?;
752 }
753
754 self.parents.push(id);
755
756 Ok(true)
757 }
758
759 fn post_visit(&mut self, _plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
760 self.parents.pop();
761 Ok(true)
762 }
763}
764
765struct PgJsonExecutionPlanVisitor<'a> {
773 verbose: bool,
774 show_metrics: ShowMetrics,
775 show_schema: bool,
776 metric_types: &'a [MetricType],
777 metric_categories: Option<&'a [MetricCategory]>,
778 metric_names: Option<&'a [String]>,
779 objects: HashMap<u32, serde_json::Value>,
780 parent_ids: Vec<u32>,
781 next_id: u32,
782 root: Option<serde_json::Value>,
783}
784
785impl PgJsonExecutionPlanVisitor<'_> {
786 fn one_line_details(plan: &dyn ExecutionPlan) -> String {
788 struct One<'b>(&'b dyn ExecutionPlan);
789 impl fmt::Display for One<'_> {
790 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
791 self.0.fmt_as(DisplayFormatType::Default, f)
792 }
793 }
794 format!("{}", One(plan))
797 .replace('\n', " ")
798 .trim()
799 .to_string()
800 }
801
802 fn metric_value_to_json(value: &MetricValue) -> serde_json::Value {
806 match value {
807 MetricValue::OutputRows(c) => serde_json::Value::from(c.value()),
808 MetricValue::SpillCount(c)
809 | MetricValue::OutputBatches(c)
810 | MetricValue::SpilledRows(c) => serde_json::Value::from(c.value()),
811 MetricValue::SpilledBytes(c) | MetricValue::OutputBytes(c) => {
812 serde_json::Value::from(c.value())
813 }
814 MetricValue::CurrentMemoryUsage(g) => serde_json::Value::from(g.value()),
815 MetricValue::ElapsedCompute(t) => {
816 let ms = (t.value() as f64) / 1_000_000.0;
821 serde_json::Value::from(ms)
822 }
823 MetricValue::Count { count, .. } => serde_json::Value::from(count.value()),
824 MetricValue::Gauge { gauge, .. } => serde_json::Value::from(gauge.value()),
825 MetricValue::PeakMemoryUsage { gauge, .. } => {
826 serde_json::Value::from(gauge.value())
827 }
828 MetricValue::Time { time, .. } => {
829 let ms = (time.value() as f64) / 1_000_000.0;
830 serde_json::Value::from(ms)
831 }
832 other => serde_json::Value::String(format!("{other}")),
834 }
835 }
836
837 fn attach_metrics(&self, plan: &dyn ExecutionPlan, object: &mut serde_json::Value) {
841 if matches!(self.show_metrics, ShowMetrics::None) {
842 return;
843 }
844 let Some(metrics) = plan.metrics() else {
845 return;
846 };
847
848 let metrics = match self.show_metrics {
849 ShowMetrics::None => return,
850 ShowMetrics::Aggregated => metrics
851 .filter_by_metric_types(self.metric_types)
852 .aggregate_by_name()
853 .sorted_for_display()
854 .timestamps_removed(),
855 ShowMetrics::Full => metrics.filter_by_metric_types(self.metric_types),
856 };
857 let metrics = if let Some(cats) = self.metric_categories {
858 metrics.filter_by_categories(cats)
859 } else {
860 metrics
861 };
862
863 let metrics = if let Some(names) = self.metric_names {
864 metrics.filter_by_names(names)
865 } else {
866 metrics
867 };
868
869 let mut extras = serde_json::Map::new();
872 for metric in metrics.iter() {
873 let value = metric.value();
874 match value {
875 MetricValue::OutputRows(c) => {
876 object["Actual Rows"] = serde_json::Value::from(c.value());
877 }
878 MetricValue::ElapsedCompute(t) => {
879 let ms = (t.value() as f64) / 1_000_000.0;
880 object["Actual Total Time"] = serde_json::Value::from(ms);
881 }
882 _ => {
883 extras.insert(
884 value.name().to_string(),
885 Self::metric_value_to_json(value),
886 );
887 }
888 }
889 }
890 if !extras.is_empty() {
891 object["Extras"] = serde_json::Value::Object(extras);
892 }
893 }
894}
895
896impl ExecutionPlanVisitor for PgJsonExecutionPlanVisitor<'_> {
897 type Error = fmt::Error;
898
899 fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
900 let id = self.next_id;
901 self.next_id += 1;
902
903 let mut object = serde_json::json!({
907 "Node Type": plan.name(),
908 "Details": Self::one_line_details(plan),
909 });
910
911 if self.show_schema || self.verbose {
912 let columns: Vec<serde_json::Value> = plan
916 .schema()
917 .fields()
918 .iter()
919 .map(|f| serde_json::Value::String(f.name().to_string()))
920 .collect();
921 object["Output"] = serde_json::Value::Array(columns);
922 }
923
924 self.attach_metrics(plan, &mut object);
925
926 object["Plans"] = serde_json::Value::Array(vec![]);
927
928 self.objects.insert(id, object);
929 self.parent_ids.push(id);
930 Ok(true)
931 }
932
933 fn post_visit(&mut self, _plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
934 let id = self.parent_ids.pop().ok_or(fmt::Error)?;
935 let current = self.objects.remove(&id).ok_or(fmt::Error)?;
936
937 if let Some(parent_id) = self.parent_ids.last() {
938 let parent = self.objects.get_mut(parent_id).ok_or(fmt::Error)?;
939 let plans = parent
940 .get_mut("Plans")
941 .and_then(|p| p.as_array_mut())
942 .ok_or(fmt::Error)?;
943 plans.push(current);
944 } else {
945 self.root = Some(current);
946 }
947 Ok(true)
948 }
949}
950
951struct TreeRenderVisitor<'a, 'b> {
975 f: &'a mut Formatter<'b>,
977 maximum_render_width: usize,
979}
980
981impl TreeRenderVisitor<'_, '_> {
982 const LTCORNER: &'static str = "┌"; const RTCORNER: &'static str = "┐"; const LDCORNER: &'static str = "└"; const RDCORNER: &'static str = "┘"; const TMIDDLE: &'static str = "┬"; const LMIDDLE: &'static str = "├"; const DMIDDLE: &'static str = "┴"; const VERTICAL: &'static str = "│"; const HORIZONTAL: &'static str = "─"; const NODE_RENDER_WIDTH: usize = 29; const MAX_EXTRA_LINES: usize = 30; pub fn visit(&mut self, plan: &dyn ExecutionPlan) -> Result<(), fmt::Error> {
1005 let root = RenderTree::create_tree(plan);
1006
1007 for y in 0..root.height {
1008 self.render_top_layer(&root, y)?;
1010 self.render_box_content(&root, y)?;
1012 self.render_bottom_layer(&root, y)?;
1014 }
1015
1016 Ok(())
1017 }
1018
1019 fn render_top_layer(
1025 &mut self,
1026 root: &RenderTree,
1027 y: usize,
1028 ) -> Result<(), fmt::Error> {
1029 for x in 0..root.width {
1030 if self.maximum_render_width > 0
1031 && x * Self::NODE_RENDER_WIDTH >= self.maximum_render_width
1032 {
1033 break;
1034 }
1035
1036 if root.has_node(x, y) {
1037 write!(self.f, "{}", Self::LTCORNER)?;
1038 write!(
1039 self.f,
1040 "{}",
1041 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH / 2 - 1)
1042 )?;
1043 if y == 0 {
1044 write!(self.f, "{}", Self::HORIZONTAL)?;
1046 } else {
1047 write!(self.f, "{}", Self::DMIDDLE)?;
1049 }
1050 write!(
1051 self.f,
1052 "{}",
1053 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH / 2 - 1)
1054 )?;
1055 write!(self.f, "{}", Self::RTCORNER)?;
1056 } else {
1057 let mut has_adjacent_nodes = false;
1058 for i in 0..(root.width - x) {
1059 has_adjacent_nodes = has_adjacent_nodes || root.has_node(x + i, y);
1060 }
1061 if !has_adjacent_nodes {
1062 continue;
1065 }
1066 write!(self.f, "{}", " ".repeat(Self::NODE_RENDER_WIDTH))?;
1068 }
1069 }
1070 writeln!(self.f)?;
1071
1072 Ok(())
1073 }
1074
1075 fn render_box_content(
1081 &mut self,
1082 root: &RenderTree,
1083 y: usize,
1084 ) -> Result<(), fmt::Error> {
1085 let mut extra_info: Vec<Vec<String>> = vec![vec![]; root.width];
1086 let mut extra_height = 0;
1087
1088 for (x, extra_info_item) in extra_info.iter_mut().enumerate().take(root.width) {
1089 if let Some(node) = root.get_node(x, y) {
1090 Self::split_up_extra_info(
1091 &node.extra_text,
1092 extra_info_item,
1093 Self::MAX_EXTRA_LINES,
1094 );
1095 if extra_info_item.len() > extra_height {
1096 extra_height = extra_info_item.len();
1097 }
1098 }
1099 }
1100
1101 let halfway_point = extra_height.div_ceil(2);
1102
1103 for render_y in 0..=extra_height {
1105 for (x, _) in root.nodes.iter().enumerate().take(root.width) {
1106 if self.maximum_render_width > 0
1107 && x * Self::NODE_RENDER_WIDTH >= self.maximum_render_width
1108 {
1109 break;
1110 }
1111
1112 let mut has_adjacent_nodes = false;
1113 for i in 0..(root.width - x) {
1114 has_adjacent_nodes = has_adjacent_nodes || root.has_node(x + i, y);
1115 }
1116
1117 if let Some(node) = root.get_node(x, y) {
1118 write!(self.f, "{}", Self::VERTICAL)?;
1119
1120 let mut render_text = if render_y == 0 {
1122 node.name.clone()
1123 } else if render_y <= extra_info[x].len() {
1124 extra_info[x][render_y - 1].clone()
1125 } else {
1126 String::new()
1127 };
1128
1129 render_text = Self::adjust_text_for_rendering(
1130 &render_text,
1131 Self::NODE_RENDER_WIDTH - 2,
1132 );
1133 write!(self.f, "{render_text}")?;
1134
1135 if render_y == halfway_point && node.child_positions.len() > 1 {
1136 write!(self.f, "{}", Self::LMIDDLE)?;
1137 } else {
1138 write!(self.f, "{}", Self::VERTICAL)?;
1139 }
1140 } else if render_y == halfway_point {
1141 let has_child_to_the_right =
1142 Self::should_render_whitespace(root, x, y);
1143 if root.has_node(x, y + 1) {
1144 write!(
1146 self.f,
1147 "{}",
1148 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH / 2)
1149 )?;
1150 if has_child_to_the_right {
1151 write!(self.f, "{}", Self::TMIDDLE)?;
1152 write!(
1154 self.f,
1155 "{}",
1156 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH / 2)
1157 )?;
1158 } else {
1159 write!(self.f, "{}", Self::RTCORNER)?;
1160 if has_adjacent_nodes {
1161 write!(
1163 self.f,
1164 "{}",
1165 " ".repeat(Self::NODE_RENDER_WIDTH / 2)
1166 )?;
1167 }
1168 }
1169 } else if has_child_to_the_right {
1170 write!(
1173 self.f,
1174 "{}",
1175 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH)
1176 )?;
1177 } else if has_adjacent_nodes {
1178 write!(self.f, "{}", " ".repeat(Self::NODE_RENDER_WIDTH))?;
1180 }
1181 } else if render_y >= halfway_point {
1182 if root.has_node(x, y + 1) {
1183 write!(
1185 self.f,
1186 "{}{}",
1187 " ".repeat(Self::NODE_RENDER_WIDTH / 2),
1188 Self::VERTICAL
1189 )?;
1190 if has_adjacent_nodes
1191 || Self::should_render_whitespace(root, x, y)
1192 {
1193 write!(
1194 self.f,
1195 "{}",
1196 " ".repeat(Self::NODE_RENDER_WIDTH / 2)
1197 )?;
1198 }
1199 } else if has_adjacent_nodes
1200 || Self::should_render_whitespace(root, x, y)
1201 {
1202 write!(self.f, "{}", " ".repeat(Self::NODE_RENDER_WIDTH))?;
1204 }
1205 } else if has_adjacent_nodes {
1206 write!(self.f, "{}", " ".repeat(Self::NODE_RENDER_WIDTH))?;
1208 }
1209 }
1210 writeln!(self.f)?;
1211 }
1212
1213 Ok(())
1214 }
1215
1216 fn render_bottom_layer(
1222 &mut self,
1223 root: &RenderTree,
1224 y: usize,
1225 ) -> Result<(), fmt::Error> {
1226 for x in 0..=root.width {
1227 if self.maximum_render_width > 0
1228 && x * Self::NODE_RENDER_WIDTH >= self.maximum_render_width
1229 {
1230 break;
1231 }
1232 let mut has_adjacent_nodes = false;
1233 for i in 0..(root.width - x) {
1234 has_adjacent_nodes = has_adjacent_nodes || root.has_node(x + i, y);
1235 }
1236 if root.get_node(x, y).is_some() {
1237 write!(self.f, "{}", Self::LDCORNER)?;
1238 write!(
1239 self.f,
1240 "{}",
1241 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH / 2 - 1)
1242 )?;
1243 if root.has_node(x, y + 1) {
1244 write!(self.f, "{}", Self::TMIDDLE)?;
1246 } else {
1247 write!(self.f, "{}", Self::HORIZONTAL)?;
1249 }
1250 write!(
1251 self.f,
1252 "{}",
1253 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH / 2 - 1)
1254 )?;
1255 write!(self.f, "{}", Self::RDCORNER)?;
1256 } else if root.has_node(x, y + 1) {
1257 write!(self.f, "{}", " ".repeat(Self::NODE_RENDER_WIDTH / 2))?;
1258 write!(self.f, "{}", Self::VERTICAL)?;
1259 if has_adjacent_nodes || Self::should_render_whitespace(root, x, y) {
1260 write!(self.f, "{}", " ".repeat(Self::NODE_RENDER_WIDTH / 2))?;
1261 }
1262 } else if has_adjacent_nodes || Self::should_render_whitespace(root, x, y) {
1263 write!(self.f, "{}", " ".repeat(Self::NODE_RENDER_WIDTH))?;
1264 }
1265 }
1266 writeln!(self.f)?;
1267
1268 Ok(())
1269 }
1270
1271 fn extra_info_separator() -> String {
1272 "-".repeat(Self::NODE_RENDER_WIDTH - 9)
1273 }
1274
1275 fn remove_padding(s: &str) -> String {
1276 s.trim().to_string()
1277 }
1278
1279 pub fn split_up_extra_info(
1280 extra_info: &HashMap<String, String>,
1281 result: &mut Vec<String>,
1282 max_lines: usize,
1283 ) {
1284 if extra_info.is_empty() {
1285 return;
1286 }
1287
1288 result.push(Self::extra_info_separator());
1289
1290 let mut requires_padding = false;
1291 let mut was_inlined = false;
1292
1293 let sorted_extra_info: BTreeMap<_, _> = extra_info.iter().collect();
1295 for (key, value) in sorted_extra_info {
1296 let mut str = Self::remove_padding(value);
1297 let mut is_inlined = false;
1298 let available_width = Self::NODE_RENDER_WIDTH - 7;
1299 let total_size = key.len() + str.len() + 2;
1300 let is_multiline = str.contains('\n');
1301
1302 if str.is_empty() {
1303 str = key.to_string();
1304 } else if !is_multiline && total_size < available_width {
1305 str = format!("{key}: {str}");
1306 is_inlined = true;
1307 } else {
1308 str = format!("{key}:\n{str}");
1309 }
1310
1311 if is_inlined && was_inlined {
1312 requires_padding = false;
1313 }
1314
1315 if requires_padding {
1316 result.push(String::new());
1317 }
1318
1319 let mut splits: Vec<String> = str.split('\n').map(String::from).collect();
1320 if splits.len() > max_lines {
1321 let mut truncated_splits = Vec::new();
1322 for split in splits.iter().take(max_lines / 2) {
1323 truncated_splits.push(split.clone());
1324 }
1325 truncated_splits.push("...".to_string());
1326 for split in splits.iter().skip(splits.len() - max_lines / 2) {
1327 truncated_splits.push(split.clone());
1328 }
1329 splits = truncated_splits;
1330 }
1331 for split in splits {
1332 Self::split_string_buffer(&split, result);
1333 }
1334 if result.len() > max_lines {
1335 result.truncate(max_lines);
1336 result.push("...".to_string());
1337 }
1338
1339 requires_padding = true;
1340 was_inlined = is_inlined;
1341 }
1342 }
1343
1344 fn adjust_text_for_rendering(source: &str, max_render_width: usize) -> String {
1348 let render_width = source.chars().count();
1349 if render_width > max_render_width {
1350 let truncated = &source[..max_render_width - 3];
1351 format!("{truncated}...")
1352 } else {
1353 let total_spaces = max_render_width - render_width;
1354 let half_spaces = total_spaces / 2;
1355 let extra_left_space = if total_spaces.is_multiple_of(2) { 0 } else { 1 };
1356 format!(
1357 "{}{}{}",
1358 " ".repeat(half_spaces + extra_left_space),
1359 source,
1360 " ".repeat(half_spaces)
1361 )
1362 }
1363 }
1364
1365 fn should_render_whitespace(root: &RenderTree, x: usize, y: usize) -> bool {
1371 let mut found_children = 0;
1372
1373 for i in (0..=x).rev() {
1374 let node = root.get_node(i, y);
1375 if root.has_node(i, y + 1) {
1376 found_children += 1;
1377 }
1378 if let Some(node) = node {
1379 if node.child_positions.len() > 1
1380 && found_children < node.child_positions.len()
1381 {
1382 return true;
1383 }
1384
1385 return false;
1386 }
1387 }
1388
1389 false
1390 }
1391
1392 fn split_string_buffer(source: &str, result: &mut Vec<String>) {
1393 let mut character_pos = 0;
1394 let mut start_pos = 0;
1395 let mut render_width = 0;
1396 let mut last_possible_split = 0;
1397
1398 let chars: Vec<char> = source.chars().collect();
1399
1400 while character_pos < chars.len() {
1401 let char_width = 1;
1403
1404 if render_width + char_width > Self::NODE_RENDER_WIDTH - 2 {
1406 if start_pos + 8 > last_possible_split {
1407 last_possible_split = character_pos;
1410 }
1411
1412 result.push(source[start_pos..last_possible_split].to_string());
1413 render_width = character_pos - last_possible_split;
1414 start_pos = last_possible_split;
1415 character_pos = last_possible_split;
1416 }
1417
1418 if Self::can_split_on_this_char(chars[character_pos]) {
1420 last_possible_split = character_pos;
1421 }
1422
1423 character_pos += 1;
1424 render_width += char_width;
1425 }
1426
1427 if source.len() > start_pos {
1428 result.push(source[start_pos..].to_string());
1430 }
1431 }
1432
1433 fn can_split_on_this_char(c: char) -> bool {
1434 (!c.is_ascii_digit() && !c.is_ascii_uppercase() && !c.is_ascii_lowercase())
1435 && c != '_'
1436 }
1437}
1438
1439pub trait DisplayAs {
1441 fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> fmt::Result;
1446}
1447
1448pub struct DefaultDisplay<T>(pub T);
1450
1451impl<T: DisplayAs> fmt::Display for DefaultDisplay<T> {
1452 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1453 self.0.fmt_as(DisplayFormatType::Default, f)
1454 }
1455}
1456
1457pub struct VerboseDisplay<T>(pub T);
1459
1460impl<T: DisplayAs> fmt::Display for VerboseDisplay<T> {
1461 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1462 self.0.fmt_as(DisplayFormatType::Verbose, f)
1463 }
1464}
1465
1466#[derive(Debug)]
1468pub struct ProjectSchemaDisplay<'a>(pub &'a SchemaRef);
1469
1470impl fmt::Display for ProjectSchemaDisplay<'_> {
1471 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1472 let parts: Vec<_> = self
1473 .0
1474 .fields()
1475 .iter()
1476 .map(|x| x.name().to_owned())
1477 .collect::<Vec<String>>();
1478 write!(f, "[{}]", parts.join(", "))
1479 }
1480}
1481
1482pub fn display_orderings(f: &mut Formatter, orderings: &[LexOrdering]) -> fmt::Result {
1483 if !orderings.is_empty() {
1484 let start = if orderings.len() == 1 {
1485 ", output_ordering="
1486 } else {
1487 ", output_orderings=["
1488 };
1489 write!(f, "{start}")?;
1490 for (idx, ordering) in orderings.iter().enumerate() {
1491 match idx {
1492 0 => write!(f, "[{ordering}]")?,
1493 _ => write!(f, ", [{ordering}]")?,
1494 }
1495 }
1496 let end = if orderings.len() == 1 { "" } else { "]" };
1497 write!(f, "{end}")?;
1498 }
1499 Ok(())
1500}
1501
1502#[cfg(test)]
1503mod tests {
1504 use std::fmt::Write;
1505 use std::sync::Arc;
1506
1507 use datafusion_common::{
1508 Result, Statistics, internal_datafusion_err, tree_node::TreeNodeRecursion,
1509 };
1510 use datafusion_execution::{SendableRecordBatchStream, TaskContext};
1511 use datafusion_physical_expr::PhysicalExpr;
1512
1513 use crate::statistics::StatisticsArgs;
1514 use crate::{
1515 ChildrenPropertiesMode, DisplayAs, ExecutionPlan, PlanProperties,
1516 ReplaceChildrenOptions,
1517 };
1518
1519 use super::DisplayableExecutionPlan;
1520
1521 #[derive(Debug, Clone, Copy)]
1522 enum TestStatsExecPlan {
1523 Panic,
1524 Error,
1525 Ok,
1526 }
1527
1528 impl DisplayAs for TestStatsExecPlan {
1529 fn fmt_as(
1530 &self,
1531 _t: crate::DisplayFormatType,
1532 f: &mut std::fmt::Formatter,
1533 ) -> std::fmt::Result {
1534 write!(f, "TestStatsExecPlan")
1535 }
1536 }
1537
1538 impl ExecutionPlan for TestStatsExecPlan {
1539 fn name(&self) -> &'static str {
1540 "TestStatsExecPlan"
1541 }
1542
1543 fn properties(&self) -> &Arc<PlanProperties> {
1544 unimplemented!()
1545 }
1546
1547 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
1548 vec![]
1549 }
1550
1551 fn replace_children(
1552 self: Arc<Self>,
1553 _: Vec<Arc<dyn ExecutionPlan>>,
1554 _: ReplaceChildrenOptions,
1555 ) -> Result<Arc<dyn ExecutionPlan>> {
1556 unimplemented!()
1557 }
1558
1559 fn apply_expressions(
1560 &self,
1561 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
1562 ) -> Result<TreeNodeRecursion> {
1563 Ok(TreeNodeRecursion::Continue)
1564 }
1565
1566 fn with_new_children(
1567 self: Arc<Self>,
1568 children: Vec<Arc<dyn ExecutionPlan>>,
1569 ) -> Result<Arc<dyn ExecutionPlan>> {
1570 self.replace_children(
1571 children,
1572 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
1573 )
1574 }
1575
1576 fn execute(
1577 &self,
1578 _: usize,
1579 _: Arc<TaskContext>,
1580 ) -> Result<SendableRecordBatchStream> {
1581 todo!()
1582 }
1583
1584 fn statistics_from_inputs(
1585 &self,
1586 _input_stats: &[Arc<Statistics>],
1587 args: &StatisticsArgs,
1588 ) -> Result<Arc<Statistics>> {
1589 if args.partition().is_some() {
1590 return Ok(Arc::new(Statistics::new_unknown(self.schema().as_ref())));
1591 }
1592 match self {
1593 Self::Panic => panic!("expected panic"),
1594 Self::Error => Err(internal_datafusion_err!("expected error")),
1595 Self::Ok => Ok(Arc::new(Statistics::new_unknown(self.schema().as_ref()))),
1596 }
1597 }
1598 }
1599
1600 fn test_stats_display(exec: TestStatsExecPlan, show_stats: bool) {
1601 let display =
1602 DisplayableExecutionPlan::new(&exec).set_show_statistics(show_stats);
1603
1604 let mut buf = String::new();
1605 write!(&mut buf, "{}", display.one_line()).unwrap();
1606 let buf = buf.trim();
1607 assert_eq!(buf, "TestStatsExecPlan");
1608 }
1609
1610 #[test]
1611 fn test_display_when_stats_panic_with_no_show_stats() {
1612 test_stats_display(TestStatsExecPlan::Panic, false);
1613 }
1614
1615 #[test]
1616 fn test_display_when_stats_error_with_no_show_stats() {
1617 test_stats_display(TestStatsExecPlan::Error, false);
1618 }
1619
1620 #[test]
1621 fn test_display_when_stats_ok_with_no_show_stats() {
1622 test_stats_display(TestStatsExecPlan::Ok, false);
1623 }
1624
1625 #[test]
1626 #[should_panic(expected = "expected panic")]
1627 fn test_display_when_stats_panic_with_show_stats() {
1628 test_stats_display(TestStatsExecPlan::Panic, true);
1629 }
1630
1631 #[test]
1632 #[should_panic(expected = "Error")] fn test_display_when_stats_error_with_show_stats() {
1634 test_stats_display(TestStatsExecPlan::Error, true);
1635 }
1636
1637 #[test]
1638 fn test_display_when_stats_ok_with_show_stats() {
1639 test_stats_display(TestStatsExecPlan::Ok, false);
1640 }
1641
1642 mod pgjson {
1643 use std::sync::Arc;
1644 use std::time::Duration;
1645
1646 use arrow::datatypes::{DataType, Field, Schema};
1647 use insta::assert_snapshot;
1648
1649 use super::super::DisplayableExecutionPlan;
1650 use crate::empty::EmptyExec;
1651 use crate::filter::FilterExec;
1652 use crate::projection::ProjectionExec;
1653 use crate::{ChildrenPropertiesMode, ExecutionPlan, ReplaceChildrenOptions};
1654 use datafusion_physical_expr::expressions::{binary, col, lit};
1655 use datafusion_physical_expr::{Partitioning, PhysicalExpr};
1656
1657 fn sample_plan() -> Arc<dyn ExecutionPlan> {
1658 let schema = Arc::new(Schema::new(vec![
1659 Field::new("a", DataType::Int32, false),
1660 Field::new("b", DataType::Int32, false),
1661 ]));
1662 let empty = Arc::new(EmptyExec::new(Arc::clone(&schema)));
1663 let predicate = binary(
1664 col("a", &schema).unwrap(),
1665 datafusion_expr::Operator::Gt,
1666 lit(5i32),
1667 &schema,
1668 )
1669 .unwrap();
1670 let filter = Arc::new(FilterExec::try_new(predicate, empty).unwrap());
1671 let proj_expr: Vec<(Arc<dyn PhysicalExpr>, String)> =
1672 vec![(col("a", &schema).unwrap(), "a".to_string())];
1673 let _ = Partitioning::UnknownPartitioning(1);
1674 Arc::new(ProjectionExec::try_new(proj_expr, filter).unwrap())
1675 }
1676
1677 #[test]
1678 fn pgjson_renders_plan_without_metrics() {
1679 let plan = sample_plan();
1680 let out = DisplayableExecutionPlan::new(plan.as_ref())
1681 .pgjson(false)
1682 .to_string();
1683 let value: serde_json::Value = serde_json::from_str(&out).unwrap();
1684 let root = value
1686 .as_array()
1687 .expect("root array")
1688 .first()
1689 .expect("root entry")
1690 .get("Plan")
1691 .expect("plan object");
1692 assert_eq!(root["Node Type"].as_str(), Some("ProjectionExec"));
1693 assert!(root.get("Actual Rows").is_none());
1694 assert!(root.get("Extras").is_none());
1695 let plans = root["Plans"].as_array().expect("Plans array");
1696 assert_eq!(plans.len(), 1);
1697 assert_eq!(plans[0]["Node Type"].as_str(), Some("FilterExec"));
1698 }
1699
1700 #[test]
1701 fn pgjson_emits_pg_canonical_metric_keys() {
1702 use crate::metrics::{Count, Metric, MetricValue, MetricsSet, Time};
1703 use crate::{DisplayFormatType, ExecutionPlan, PlanProperties};
1704 use datafusion_common::Result;
1705 use datafusion_execution::{SendableRecordBatchStream, TaskContext};
1706
1707 #[derive(Debug)]
1711 struct WithMetrics {
1712 inner: Arc<dyn ExecutionPlan>,
1713 metrics: MetricsSet,
1714 }
1715 impl crate::DisplayAs for WithMetrics {
1716 fn fmt_as(
1717 &self,
1718 _t: DisplayFormatType,
1719 f: &mut std::fmt::Formatter,
1720 ) -> std::fmt::Result {
1721 write!(f, "WithMetrics")
1722 }
1723 }
1724 impl ExecutionPlan for WithMetrics {
1725 fn name(&self) -> &'static str {
1726 "WithMetrics"
1727 }
1728 fn properties(&self) -> &Arc<PlanProperties> {
1729 self.inner.properties()
1730 }
1731 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
1732 vec![&self.inner]
1733 }
1734 fn apply_expressions(
1735 &self,
1736 _f: &mut dyn FnMut(
1737 &Arc<dyn PhysicalExpr>,
1738 ) -> Result<
1739 datafusion_common::tree_node::TreeNodeRecursion,
1740 >,
1741 ) -> Result<datafusion_common::tree_node::TreeNodeRecursion>
1742 {
1743 Ok(datafusion_common::tree_node::TreeNodeRecursion::Continue)
1744 }
1745
1746 fn replace_children(
1747 self: Arc<Self>,
1748 _: Vec<Arc<dyn ExecutionPlan>>,
1749 _: ReplaceChildrenOptions,
1750 ) -> Result<Arc<dyn ExecutionPlan>> {
1751 unimplemented!()
1752 }
1753 fn with_new_children(
1754 self: Arc<Self>,
1755 children: Vec<Arc<dyn ExecutionPlan>>,
1756 ) -> Result<Arc<dyn ExecutionPlan>> {
1757 self.replace_children(
1758 children,
1759 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
1760 )
1761 }
1762 fn execute(
1763 &self,
1764 _: usize,
1765 _: Arc<TaskContext>,
1766 ) -> Result<SendableRecordBatchStream> {
1767 unimplemented!()
1768 }
1769 fn metrics(&self) -> Option<MetricsSet> {
1770 Some(self.metrics.clone())
1771 }
1772 }
1773
1774 let mut metrics = MetricsSet::new();
1775 let rows = Count::new();
1776 rows.add(42);
1777 metrics.push(Arc::new(Metric::new(MetricValue::OutputRows(rows), None)));
1778 let elapsed = Time::new();
1779 elapsed.add_duration(Duration::from_millis(5));
1780 metrics.push(Arc::new(Metric::new(
1781 MetricValue::ElapsedCompute(elapsed),
1782 None,
1783 )));
1784 let batches = Count::new();
1785 batches.add(7);
1786 metrics.push(Arc::new(Metric::new(
1787 MetricValue::OutputBatches(batches),
1788 None,
1789 )));
1790
1791 let plan: Arc<dyn ExecutionPlan> = Arc::new(WithMetrics {
1792 inner: sample_plan(),
1793 metrics,
1794 });
1795
1796 let out = DisplayableExecutionPlan::with_metrics(plan.as_ref())
1797 .pgjson(false)
1798 .to_string();
1799 let value: serde_json::Value = serde_json::from_str(&out).unwrap();
1800 let root = value[0].get("Plan").expect("plan");
1801 assert_eq!(root["Actual Rows"].as_u64(), Some(42));
1802 assert_eq!(root["Actual Total Time"].as_f64(), Some(5.0));
1803 assert_eq!(root["Extras"]["output_batches"].as_u64(), Some(7));
1804
1805 let metric_names = vec!["output_rows".to_string()];
1806 for rendered in [
1807 DisplayableExecutionPlan::with_metrics(plan.as_ref())
1808 .set_metric_names(metric_names.clone())
1809 .indent(false)
1810 .to_string(),
1811 DisplayableExecutionPlan::with_full_metrics(plan.as_ref())
1812 .set_metric_names(metric_names.clone())
1813 .indent(false)
1814 .to_string(),
1815 DisplayableExecutionPlan::with_metrics(plan.as_ref())
1816 .set_metric_names(metric_names.clone())
1817 .graphviz()
1818 .to_string(),
1819 DisplayableExecutionPlan::with_full_metrics(plan.as_ref())
1820 .set_metric_names(metric_names.clone())
1821 .graphviz()
1822 .to_string(),
1823 ] {
1824 assert!(rendered.contains("output_rows"));
1825 assert!(!rendered.contains("elapsed_compute"));
1826 assert!(!rendered.contains("output_batches"));
1827 }
1828
1829 let out = DisplayableExecutionPlan::with_metrics(plan.as_ref())
1830 .set_metric_names(metric_names)
1831 .pgjson(false)
1832 .to_string();
1833 let value: serde_json::Value = serde_json::from_str(&out).unwrap();
1834 let root = value[0].get("Plan").expect("plan");
1835 assert_eq!(root["Actual Rows"].as_u64(), Some(42));
1836 assert!(root.get("Actual Total Time").is_none());
1837 assert!(root.get("Extras").is_none());
1838 }
1839
1840 #[test]
1841 fn pgjson_includes_summary_when_set() {
1842 let plan = sample_plan();
1843 let out = DisplayableExecutionPlan::with_metrics(plan.as_ref())
1844 .set_summary(Some(42), Some(Duration::from_millis(7)))
1845 .pgjson(false)
1846 .to_string();
1847 let value: serde_json::Value = serde_json::from_str(&out).unwrap();
1848 let entry = &value.as_array().unwrap()[0];
1849 assert_eq!(entry["Total Rows"].as_u64(), Some(42));
1850 assert!(entry["Duration"].is_string());
1851 }
1852
1853 #[test]
1854 fn pgjson_snapshot_of_sample_plan() {
1855 let plan = sample_plan();
1856 let out = DisplayableExecutionPlan::new(plan.as_ref())
1857 .pgjson(false)
1858 .to_string();
1859 assert_snapshot!(out, @r#"
1862 [
1863 {
1864 "Plan": {
1865 "Node Type": "ProjectionExec",
1866 "Details": "ProjectionExec: expr=[a@0 as a]",
1867 "Plans": [
1868 {
1869 "Node Type": "FilterExec",
1870 "Details": "FilterExec: a@0 > 5",
1871 "Plans": [
1872 {
1873 "Node Type": "EmptyExec",
1874 "Details": "EmptyExec",
1875 "Plans": []
1876 }
1877 ]
1878 }
1879 ]
1880 }
1881 }
1882 ]
1883 "#);
1884 }
1885 }
1886}