1use std::collections::{BTreeMap, HashMap};
22use std::fmt;
23use std::fmt::Formatter;
24
25use arrow::datatypes::SchemaRef;
26
27use datafusion_common::display::{GraphvizBuilder, PlanType, StringifiedPlan};
28use datafusion_expr::display_schema;
29use datafusion_physical_expr::LexOrdering;
30
31use crate::render_tree::RenderTree;
32
33use super::{accept, ExecutionPlan, ExecutionPlanVisitor};
34
35#[derive(Debug, Clone, Copy, PartialEq)]
37pub enum DisplayFormatType {
38 Default,
43 Verbose,
47 TreeRender,
79}
80
81#[derive(Debug, Clone)]
115pub struct DisplayableExecutionPlan<'a> {
116 inner: &'a dyn ExecutionPlan,
117 show_metrics: ShowMetrics,
119 show_statistics: bool,
121 show_schema: bool,
123 tree_maximum_render_width: usize,
125}
126
127impl<'a> DisplayableExecutionPlan<'a> {
128 pub fn new(inner: &'a dyn ExecutionPlan) -> Self {
131 Self {
132 inner,
133 show_metrics: ShowMetrics::None,
134 show_statistics: false,
135 show_schema: false,
136 tree_maximum_render_width: 240,
137 }
138 }
139
140 pub fn with_metrics(inner: &'a dyn ExecutionPlan) -> Self {
144 Self {
145 inner,
146 show_metrics: ShowMetrics::Aggregated,
147 show_statistics: false,
148 show_schema: false,
149 tree_maximum_render_width: 240,
150 }
151 }
152
153 pub fn with_full_metrics(inner: &'a dyn ExecutionPlan) -> Self {
157 Self {
158 inner,
159 show_metrics: ShowMetrics::Full,
160 show_statistics: false,
161 show_schema: false,
162 tree_maximum_render_width: 240,
163 }
164 }
165
166 pub fn set_show_schema(mut self, show_schema: bool) -> Self {
171 self.show_schema = show_schema;
172 self
173 }
174
175 pub fn set_show_statistics(mut self, show_statistics: bool) -> Self {
177 self.show_statistics = show_statistics;
178 self
179 }
180
181 pub fn set_tree_maximum_render_width(mut self, width: usize) -> Self {
183 self.tree_maximum_render_width = width;
184 self
185 }
186
187 pub fn indent(&self, verbose: bool) -> impl fmt::Display + 'a {
198 let format_type = if verbose {
199 DisplayFormatType::Verbose
200 } else {
201 DisplayFormatType::Default
202 };
203 struct Wrapper<'a> {
204 format_type: DisplayFormatType,
205 plan: &'a dyn ExecutionPlan,
206 show_metrics: ShowMetrics,
207 show_statistics: bool,
208 show_schema: bool,
209 }
210 impl fmt::Display for Wrapper<'_> {
211 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
212 let mut visitor = IndentVisitor {
213 t: self.format_type,
214 f,
215 indent: 0,
216 show_metrics: self.show_metrics,
217 show_statistics: self.show_statistics,
218 show_schema: self.show_schema,
219 };
220 accept(self.plan, &mut visitor)
221 }
222 }
223 Wrapper {
224 format_type,
225 plan: self.inner,
226 show_metrics: self.show_metrics,
227 show_statistics: self.show_statistics,
228 show_schema: self.show_schema,
229 }
230 }
231
232 pub fn graphviz(&self) -> impl fmt::Display + 'a {
244 struct Wrapper<'a> {
245 plan: &'a dyn ExecutionPlan,
246 show_metrics: ShowMetrics,
247 show_statistics: bool,
248 }
249 impl fmt::Display for Wrapper<'_> {
250 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
251 let t = DisplayFormatType::Default;
252
253 let mut visitor = GraphvizVisitor {
254 f,
255 t,
256 show_metrics: self.show_metrics,
257 show_statistics: self.show_statistics,
258 graphviz_builder: GraphvizBuilder::default(),
259 parents: Vec::new(),
260 };
261
262 visitor.start_graph()?;
263
264 accept(self.plan, &mut visitor)?;
265
266 visitor.end_graph()?;
267 Ok(())
268 }
269 }
270
271 Wrapper {
272 plan: self.inner,
273 show_metrics: self.show_metrics,
274 show_statistics: self.show_statistics,
275 }
276 }
277
278 pub fn tree_render(&self) -> impl fmt::Display + 'a {
282 struct Wrapper<'a> {
283 plan: &'a dyn ExecutionPlan,
284 maximum_render_width: usize,
285 }
286 impl fmt::Display for Wrapper<'_> {
287 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
288 let mut visitor = TreeRenderVisitor {
289 f,
290 maximum_render_width: self.maximum_render_width,
291 };
292 visitor.visit(self.plan)
293 }
294 }
295 Wrapper {
296 plan: self.inner,
297 maximum_render_width: self.tree_maximum_render_width,
298 }
299 }
300
301 pub fn one_line(&self) -> impl fmt::Display + 'a {
304 struct Wrapper<'a> {
305 plan: &'a dyn ExecutionPlan,
306 show_metrics: ShowMetrics,
307 show_statistics: bool,
308 show_schema: bool,
309 }
310
311 impl fmt::Display for Wrapper<'_> {
312 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
313 let mut visitor = IndentVisitor {
314 f,
315 t: DisplayFormatType::Default,
316 indent: 0,
317 show_metrics: self.show_metrics,
318 show_statistics: self.show_statistics,
319 show_schema: self.show_schema,
320 };
321 visitor.pre_visit(self.plan)?;
322 Ok(())
323 }
324 }
325
326 Wrapper {
327 plan: self.inner,
328 show_metrics: self.show_metrics,
329 show_statistics: self.show_statistics,
330 show_schema: self.show_schema,
331 }
332 }
333
334 #[deprecated(since = "47.0.0", note = "indent() or tree_render() instead")]
335 pub fn to_stringified(
336 &self,
337 verbose: bool,
338 plan_type: PlanType,
339 explain_format: DisplayFormatType,
340 ) -> StringifiedPlan {
341 match (&explain_format, &plan_type) {
342 (DisplayFormatType::TreeRender, PlanType::FinalPhysicalPlan) => {
343 StringifiedPlan::new(plan_type, self.tree_render().to_string())
344 }
345 _ => StringifiedPlan::new(plan_type, self.indent(verbose).to_string()),
346 }
347 }
348}
349
350#[derive(Debug, Clone, Copy)]
352enum ShowMetrics {
353 None,
355
356 Aggregated,
358
359 Full,
361}
362
363struct IndentVisitor<'a, 'b> {
373 t: DisplayFormatType,
375 f: &'a mut Formatter<'b>,
377 indent: usize,
379 show_metrics: ShowMetrics,
381 show_statistics: bool,
383 show_schema: bool,
385}
386
387impl ExecutionPlanVisitor for IndentVisitor<'_, '_> {
388 type Error = fmt::Error;
389 fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
390 write!(self.f, "{:indent$}", "", indent = self.indent * 2)?;
391 plan.fmt_as(self.t, self.f)?;
392 match self.show_metrics {
393 ShowMetrics::None => {}
394 ShowMetrics::Aggregated => {
395 if let Some(metrics) = plan.metrics() {
396 let metrics = metrics
397 .aggregate_by_name()
398 .sorted_for_display()
399 .timestamps_removed();
400
401 write!(self.f, ", metrics=[{metrics}]")?;
402 } else {
403 write!(self.f, ", metrics=[]")?;
404 }
405 }
406 ShowMetrics::Full => {
407 if let Some(metrics) = plan.metrics() {
408 write!(self.f, ", metrics=[{metrics}]")?;
409 } else {
410 write!(self.f, ", metrics=[]")?;
411 }
412 }
413 }
414 if self.show_statistics {
415 let stats = plan.partition_statistics(None).map_err(|_e| fmt::Error)?;
416 write!(self.f, ", statistics=[{stats}]")?;
417 }
418 if self.show_schema {
419 write!(
420 self.f,
421 ", schema={}",
422 display_schema(plan.schema().as_ref())
423 )?;
424 }
425 writeln!(self.f)?;
426 self.indent += 1;
427 Ok(true)
428 }
429
430 fn post_visit(&mut self, _plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
431 self.indent -= 1;
432 Ok(true)
433 }
434}
435
436struct GraphvizVisitor<'a, 'b> {
437 f: &'a mut Formatter<'b>,
438 t: DisplayFormatType,
440 show_metrics: ShowMetrics,
442 show_statistics: bool,
444
445 graphviz_builder: GraphvizBuilder,
446 parents: Vec<usize>,
448}
449
450impl GraphvizVisitor<'_, '_> {
451 fn start_graph(&mut self) -> fmt::Result {
452 self.graphviz_builder.start_graph(self.f)
453 }
454
455 fn end_graph(&mut self) -> fmt::Result {
456 self.graphviz_builder.end_graph(self.f)
457 }
458}
459
460impl ExecutionPlanVisitor for GraphvizVisitor<'_, '_> {
461 type Error = fmt::Error;
462
463 fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
464 let id = self.graphviz_builder.next_id();
465
466 struct Wrapper<'a>(&'a dyn ExecutionPlan, DisplayFormatType);
467
468 impl fmt::Display for Wrapper<'_> {
469 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
470 self.0.fmt_as(self.1, f)
471 }
472 }
473
474 let label = { format!("{}", Wrapper(plan, self.t)) };
475
476 let metrics = match self.show_metrics {
477 ShowMetrics::None => "".to_string(),
478 ShowMetrics::Aggregated => {
479 if let Some(metrics) = plan.metrics() {
480 let metrics = metrics
481 .aggregate_by_name()
482 .sorted_for_display()
483 .timestamps_removed();
484
485 format!("metrics=[{metrics}]")
486 } else {
487 "metrics=[]".to_string()
488 }
489 }
490 ShowMetrics::Full => {
491 if let Some(metrics) = plan.metrics() {
492 format!("metrics=[{metrics}]")
493 } else {
494 "metrics=[]".to_string()
495 }
496 }
497 };
498
499 let statistics = if self.show_statistics {
500 let stats = plan.partition_statistics(None).map_err(|_e| fmt::Error)?;
501 format!("statistics=[{stats}]")
502 } else {
503 "".to_string()
504 };
505
506 let delimiter = if !metrics.is_empty() && !statistics.is_empty() {
507 ", "
508 } else {
509 ""
510 };
511
512 self.graphviz_builder.add_node(
513 self.f,
514 id,
515 &label,
516 Some(&format!("{metrics}{delimiter}{statistics}")),
517 )?;
518
519 if let Some(parent_node_id) = self.parents.last() {
520 self.graphviz_builder
521 .add_edge(self.f, *parent_node_id, id)?;
522 }
523
524 self.parents.push(id);
525
526 Ok(true)
527 }
528
529 fn post_visit(&mut self, _plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
530 self.parents.pop();
531 Ok(true)
532 }
533}
534
535struct TreeRenderVisitor<'a, 'b> {
559 f: &'a mut Formatter<'b>,
561 maximum_render_width: usize,
563}
564
565impl TreeRenderVisitor<'_, '_> {
566 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> {
589 let root = RenderTree::create_tree(plan);
590
591 for y in 0..root.height {
592 self.render_top_layer(&root, y)?;
594 self.render_box_content(&root, y)?;
596 self.render_bottom_layer(&root, y)?;
598 }
599
600 Ok(())
601 }
602
603 fn render_top_layer(
609 &mut self,
610 root: &RenderTree,
611 y: usize,
612 ) -> Result<(), fmt::Error> {
613 for x in 0..root.width {
614 if self.maximum_render_width > 0
615 && x * Self::NODE_RENDER_WIDTH >= self.maximum_render_width
616 {
617 break;
618 }
619
620 if root.has_node(x, y) {
621 write!(self.f, "{}", Self::LTCORNER)?;
622 write!(
623 self.f,
624 "{}",
625 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH / 2 - 1)
626 )?;
627 if y == 0 {
628 write!(self.f, "{}", Self::HORIZONTAL)?;
630 } else {
631 write!(self.f, "{}", Self::DMIDDLE)?;
633 }
634 write!(
635 self.f,
636 "{}",
637 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH / 2 - 1)
638 )?;
639 write!(self.f, "{}", Self::RTCORNER)?;
640 } else {
641 let mut has_adjacent_nodes = false;
642 for i in 0..(root.width - x) {
643 has_adjacent_nodes = has_adjacent_nodes || root.has_node(x + i, y);
644 }
645 if !has_adjacent_nodes {
646 continue;
649 }
650 write!(self.f, "{}", &" ".repeat(Self::NODE_RENDER_WIDTH))?;
652 }
653 }
654 writeln!(self.f)?;
655
656 Ok(())
657 }
658
659 fn render_box_content(
665 &mut self,
666 root: &RenderTree,
667 y: usize,
668 ) -> Result<(), fmt::Error> {
669 let mut extra_info: Vec<Vec<String>> = vec![vec![]; root.width];
670 let mut extra_height = 0;
671
672 for (x, extra_info_item) in extra_info.iter_mut().enumerate().take(root.width) {
673 if let Some(node) = root.get_node(x, y) {
674 Self::split_up_extra_info(
675 &node.extra_text,
676 extra_info_item,
677 Self::MAX_EXTRA_LINES,
678 );
679 if extra_info_item.len() > extra_height {
680 extra_height = extra_info_item.len();
681 }
682 }
683 }
684
685 let halfway_point = extra_height.div_ceil(2);
686
687 for render_y in 0..=extra_height {
689 for (x, _) in root.nodes.iter().enumerate().take(root.width) {
690 if self.maximum_render_width > 0
691 && x * Self::NODE_RENDER_WIDTH >= self.maximum_render_width
692 {
693 break;
694 }
695
696 let mut has_adjacent_nodes = false;
697 for i in 0..(root.width - x) {
698 has_adjacent_nodes = has_adjacent_nodes || root.has_node(x + i, y);
699 }
700
701 if let Some(node) = root.get_node(x, y) {
702 write!(self.f, "{}", Self::VERTICAL)?;
703
704 let mut render_text = String::new();
706 if render_y == 0 {
707 render_text = node.name.clone();
708 } else if render_y <= extra_info[x].len() {
709 render_text = extra_info[x][render_y - 1].clone();
710 }
711
712 render_text = Self::adjust_text_for_rendering(
713 &render_text,
714 Self::NODE_RENDER_WIDTH - 2,
715 );
716 write!(self.f, "{render_text}")?;
717
718 if render_y == halfway_point && node.child_positions.len() > 1 {
719 write!(self.f, "{}", Self::LMIDDLE)?;
720 } else {
721 write!(self.f, "{}", Self::VERTICAL)?;
722 }
723 } else if render_y == halfway_point {
724 let has_child_to_the_right =
725 Self::should_render_whitespace(root, x, y);
726 if root.has_node(x, y + 1) {
727 write!(
729 self.f,
730 "{}",
731 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH / 2)
732 )?;
733 if has_child_to_the_right {
734 write!(self.f, "{}", Self::TMIDDLE)?;
735 write!(
737 self.f,
738 "{}",
739 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH / 2)
740 )?;
741 } else {
742 write!(self.f, "{}", Self::RTCORNER)?;
743 if has_adjacent_nodes {
744 write!(
746 self.f,
747 "{}",
748 " ".repeat(Self::NODE_RENDER_WIDTH / 2)
749 )?;
750 }
751 }
752 } else if has_child_to_the_right {
753 write!(
756 self.f,
757 "{}",
758 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH)
759 )?;
760 } else if has_adjacent_nodes {
761 write!(self.f, "{}", " ".repeat(Self::NODE_RENDER_WIDTH))?;
763 }
764 } else if render_y >= halfway_point {
765 if root.has_node(x, y + 1) {
766 write!(
768 self.f,
769 "{}{}",
770 " ".repeat(Self::NODE_RENDER_WIDTH / 2),
771 Self::VERTICAL
772 )?;
773 if has_adjacent_nodes
774 || Self::should_render_whitespace(root, x, y)
775 {
776 write!(
777 self.f,
778 "{}",
779 " ".repeat(Self::NODE_RENDER_WIDTH / 2)
780 )?;
781 }
782 } else if has_adjacent_nodes
783 || Self::should_render_whitespace(root, x, y)
784 {
785 write!(self.f, "{}", " ".repeat(Self::NODE_RENDER_WIDTH))?;
787 }
788 } else if has_adjacent_nodes {
789 write!(self.f, "{}", " ".repeat(Self::NODE_RENDER_WIDTH))?;
791 }
792 }
793 writeln!(self.f)?;
794 }
795
796 Ok(())
797 }
798
799 fn render_bottom_layer(
805 &mut self,
806 root: &RenderTree,
807 y: usize,
808 ) -> Result<(), fmt::Error> {
809 for x in 0..=root.width {
810 if self.maximum_render_width > 0
811 && x * Self::NODE_RENDER_WIDTH >= self.maximum_render_width
812 {
813 break;
814 }
815 let mut has_adjacent_nodes = false;
816 for i in 0..(root.width - x) {
817 has_adjacent_nodes = has_adjacent_nodes || root.has_node(x + i, y);
818 }
819 if root.get_node(x, y).is_some() {
820 write!(self.f, "{}", Self::LDCORNER)?;
821 write!(
822 self.f,
823 "{}",
824 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH / 2 - 1)
825 )?;
826 if root.has_node(x, y + 1) {
827 write!(self.f, "{}", Self::TMIDDLE)?;
829 } else {
830 write!(self.f, "{}", Self::HORIZONTAL)?;
832 }
833 write!(
834 self.f,
835 "{}",
836 Self::HORIZONTAL.repeat(Self::NODE_RENDER_WIDTH / 2 - 1)
837 )?;
838 write!(self.f, "{}", Self::RDCORNER)?;
839 } else if root.has_node(x, y + 1) {
840 write!(self.f, "{}", &" ".repeat(Self::NODE_RENDER_WIDTH / 2))?;
841 write!(self.f, "{}", Self::VERTICAL)?;
842 if has_adjacent_nodes || Self::should_render_whitespace(root, x, y) {
843 write!(self.f, "{}", &" ".repeat(Self::NODE_RENDER_WIDTH / 2))?;
844 }
845 } else if has_adjacent_nodes || Self::should_render_whitespace(root, x, y) {
846 write!(self.f, "{}", &" ".repeat(Self::NODE_RENDER_WIDTH))?;
847 }
848 }
849 writeln!(self.f)?;
850
851 Ok(())
852 }
853
854 fn extra_info_separator() -> String {
855 "-".repeat(Self::NODE_RENDER_WIDTH - 9)
856 }
857
858 fn remove_padding(s: &str) -> String {
859 s.trim().to_string()
860 }
861
862 pub fn split_up_extra_info(
863 extra_info: &HashMap<String, String>,
864 result: &mut Vec<String>,
865 max_lines: usize,
866 ) {
867 if extra_info.is_empty() {
868 return;
869 }
870
871 result.push(Self::extra_info_separator());
872
873 let mut requires_padding = false;
874 let mut was_inlined = false;
875
876 let sorted_extra_info: BTreeMap<_, _> = extra_info.iter().collect();
878 for (key, value) in sorted_extra_info {
879 let mut str = Self::remove_padding(value);
880 let mut is_inlined = false;
881 let available_width = Self::NODE_RENDER_WIDTH - 7;
882 let total_size = key.len() + str.len() + 2;
883 let is_multiline = str.contains('\n');
884
885 if str.is_empty() {
886 str = key.to_string();
887 } else if !is_multiline && total_size < available_width {
888 str = format!("{key}: {str}");
889 is_inlined = true;
890 } else {
891 str = format!("{key}:\n{str}");
892 }
893
894 if is_inlined && was_inlined {
895 requires_padding = false;
896 }
897
898 if requires_padding {
899 result.push(String::new());
900 }
901
902 let mut splits: Vec<String> = str.split('\n').map(String::from).collect();
903 if splits.len() > max_lines {
904 let mut truncated_splits = Vec::new();
905 for split in splits.iter().take(max_lines / 2) {
906 truncated_splits.push(split.clone());
907 }
908 truncated_splits.push("...".to_string());
909 for split in splits.iter().skip(splits.len() - max_lines / 2) {
910 truncated_splits.push(split.clone());
911 }
912 splits = truncated_splits;
913 }
914 for split in splits {
915 Self::split_string_buffer(&split, result);
916 }
917 if result.len() > max_lines {
918 result.truncate(max_lines);
919 result.push("...".to_string());
920 }
921
922 requires_padding = true;
923 was_inlined = is_inlined;
924 }
925 }
926
927 fn adjust_text_for_rendering(source: &str, max_render_width: usize) -> String {
931 let render_width = source.chars().count();
932 if render_width > max_render_width {
933 let truncated = &source[..max_render_width - 3];
934 format!("{truncated}...")
935 } else {
936 let total_spaces = max_render_width - render_width;
937 let half_spaces = total_spaces / 2;
938 let extra_left_space = if total_spaces % 2 == 0 { 0 } else { 1 };
939 format!(
940 "{}{}{}",
941 " ".repeat(half_spaces + extra_left_space),
942 source,
943 " ".repeat(half_spaces)
944 )
945 }
946 }
947
948 fn should_render_whitespace(root: &RenderTree, x: usize, y: usize) -> bool {
954 let mut found_children = 0;
955
956 for i in (0..=x).rev() {
957 let node = root.get_node(i, y);
958 if root.has_node(i, y + 1) {
959 found_children += 1;
960 }
961 if let Some(node) = node {
962 if node.child_positions.len() > 1
963 && found_children < node.child_positions.len()
964 {
965 return true;
966 }
967
968 return false;
969 }
970 }
971
972 false
973 }
974
975 fn split_string_buffer(source: &str, result: &mut Vec<String>) {
976 let mut character_pos = 0;
977 let mut start_pos = 0;
978 let mut render_width = 0;
979 let mut last_possible_split = 0;
980
981 let chars: Vec<char> = source.chars().collect();
982
983 while character_pos < chars.len() {
984 let char_width = 1;
986
987 if render_width + char_width > Self::NODE_RENDER_WIDTH - 2 {
989 if start_pos + 8 > last_possible_split {
990 last_possible_split = character_pos;
993 }
994
995 result.push(source[start_pos..last_possible_split].to_string());
996 render_width = character_pos - last_possible_split;
997 start_pos = last_possible_split;
998 character_pos = last_possible_split;
999 }
1000
1001 if Self::can_split_on_this_char(chars[character_pos]) {
1003 last_possible_split = character_pos;
1004 }
1005
1006 character_pos += 1;
1007 render_width += char_width;
1008 }
1009
1010 if source.len() > start_pos {
1011 result.push(source[start_pos..].to_string());
1013 }
1014 }
1015
1016 fn can_split_on_this_char(c: char) -> bool {
1017 (!c.is_ascii_digit() && !c.is_ascii_uppercase() && !c.is_ascii_lowercase())
1018 && c != '_'
1019 }
1020}
1021
1022pub trait DisplayAs {
1024 fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> fmt::Result;
1029}
1030
1031pub struct DefaultDisplay<T>(pub T);
1033
1034impl<T: DisplayAs> fmt::Display for DefaultDisplay<T> {
1035 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1036 self.0.fmt_as(DisplayFormatType::Default, f)
1037 }
1038}
1039
1040pub struct VerboseDisplay<T>(pub T);
1042
1043impl<T: DisplayAs> fmt::Display for VerboseDisplay<T> {
1044 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1045 self.0.fmt_as(DisplayFormatType::Verbose, f)
1046 }
1047}
1048
1049#[derive(Debug)]
1051pub struct ProjectSchemaDisplay<'a>(pub &'a SchemaRef);
1052
1053impl fmt::Display for ProjectSchemaDisplay<'_> {
1054 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1055 let parts: Vec<_> = self
1056 .0
1057 .fields()
1058 .iter()
1059 .map(|x| x.name().to_owned())
1060 .collect::<Vec<String>>();
1061 write!(f, "[{}]", parts.join(", "))
1062 }
1063}
1064
1065pub fn display_orderings(f: &mut Formatter, orderings: &[LexOrdering]) -> fmt::Result {
1066 if !orderings.is_empty() {
1067 let start = if orderings.len() == 1 {
1068 ", output_ordering="
1069 } else {
1070 ", output_orderings=["
1071 };
1072 write!(f, "{start}")?;
1073 for (idx, ordering) in orderings.iter().enumerate() {
1074 match idx {
1075 0 => write!(f, "[{ordering}]")?,
1076 _ => write!(f, ", [{ordering}]")?,
1077 }
1078 }
1079 let end = if orderings.len() == 1 { "" } else { "]" };
1080 write!(f, "{end}")?;
1081 }
1082 Ok(())
1083}
1084
1085#[cfg(test)]
1086mod tests {
1087 use std::fmt::Write;
1088 use std::sync::Arc;
1089
1090 use datafusion_common::{DataFusionError, Result, Statistics};
1091 use datafusion_execution::{SendableRecordBatchStream, TaskContext};
1092
1093 use crate::{DisplayAs, ExecutionPlan, PlanProperties};
1094
1095 use super::DisplayableExecutionPlan;
1096
1097 #[derive(Debug, Clone, Copy)]
1098 enum TestStatsExecPlan {
1099 Panic,
1100 Error,
1101 Ok,
1102 }
1103
1104 impl DisplayAs for TestStatsExecPlan {
1105 fn fmt_as(
1106 &self,
1107 _t: crate::DisplayFormatType,
1108 f: &mut std::fmt::Formatter,
1109 ) -> std::fmt::Result {
1110 write!(f, "TestStatsExecPlan")
1111 }
1112 }
1113
1114 impl ExecutionPlan for TestStatsExecPlan {
1115 fn name(&self) -> &'static str {
1116 "TestStatsExecPlan"
1117 }
1118
1119 fn as_any(&self) -> &dyn std::any::Any {
1120 self
1121 }
1122
1123 fn properties(&self) -> &PlanProperties {
1124 unimplemented!()
1125 }
1126
1127 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
1128 vec![]
1129 }
1130
1131 fn with_new_children(
1132 self: Arc<Self>,
1133 _: Vec<Arc<dyn ExecutionPlan>>,
1134 ) -> Result<Arc<dyn ExecutionPlan>> {
1135 unimplemented!()
1136 }
1137
1138 fn execute(
1139 &self,
1140 _: usize,
1141 _: Arc<TaskContext>,
1142 ) -> Result<SendableRecordBatchStream> {
1143 todo!()
1144 }
1145
1146 fn statistics(&self) -> Result<Statistics> {
1147 self.partition_statistics(None)
1148 }
1149
1150 fn partition_statistics(&self, partition: Option<usize>) -> Result<Statistics> {
1151 if partition.is_some() {
1152 return Ok(Statistics::new_unknown(self.schema().as_ref()));
1153 }
1154 match self {
1155 Self::Panic => panic!("expected panic"),
1156 Self::Error => {
1157 Err(DataFusionError::Internal("expected error".to_string()))
1158 }
1159 Self::Ok => Ok(Statistics::new_unknown(self.schema().as_ref())),
1160 }
1161 }
1162 }
1163
1164 fn test_stats_display(exec: TestStatsExecPlan, show_stats: bool) {
1165 let display =
1166 DisplayableExecutionPlan::new(&exec).set_show_statistics(show_stats);
1167
1168 let mut buf = String::new();
1169 write!(&mut buf, "{}", display.one_line()).unwrap();
1170 let buf = buf.trim();
1171 assert_eq!(buf, "TestStatsExecPlan");
1172 }
1173
1174 #[test]
1175 fn test_display_when_stats_panic_with_no_show_stats() {
1176 test_stats_display(TestStatsExecPlan::Panic, false);
1177 }
1178
1179 #[test]
1180 fn test_display_when_stats_error_with_no_show_stats() {
1181 test_stats_display(TestStatsExecPlan::Error, false);
1182 }
1183
1184 #[test]
1185 fn test_display_when_stats_ok_with_no_show_stats() {
1186 test_stats_display(TestStatsExecPlan::Ok, false);
1187 }
1188
1189 #[test]
1190 #[should_panic(expected = "expected panic")]
1191 fn test_display_when_stats_panic_with_show_stats() {
1192 test_stats_display(TestStatsExecPlan::Panic, true);
1193 }
1194
1195 #[test]
1196 #[should_panic(expected = "Error")] fn test_display_when_stats_error_with_show_stats() {
1198 test_stats_display(TestStatsExecPlan::Error, true);
1199 }
1200
1201 #[test]
1202 fn test_display_when_stats_ok_with_show_stats() {
1203 test_stats_display(TestStatsExecPlan::Ok, false);
1204 }
1205}