diff --git a/datafusion/common/src/config.rs b/datafusion/common/src/config.rs index 31159d4a8588..5796edc283e0 100644 --- a/datafusion/common/src/config.rs +++ b/datafusion/common/src/config.rs @@ -840,6 +840,10 @@ config_namespace! { /// Display format of explain. Default is "indent". /// When set to "tree", it will print the plan in a tree-rendered format. pub format: String, default = "indent".to_string() + + /// (format=tree only) Maximum total width of the rendered tree. + /// When set to 0, the tree will have no width limit. + pub tree_maximum_render_width: usize, default = 240 } } diff --git a/datafusion/core/src/physical_planner.rs b/datafusion/core/src/physical_planner.rs index ab123dcceada..5a0ee327cb6a 100644 --- a/datafusion/core/src/physical_planner.rs +++ b/datafusion/core/src/physical_planner.rs @@ -1856,6 +1856,7 @@ impl DefaultPhysicalPlanner { stringified_plans.push(StringifiedPlan::new( FinalPhysicalPlan, displayable(optimized_plan.as_ref()) + .set_tree_maximum_render_width(config.tree_maximum_render_width) .tree_render() .to_string(), )); diff --git a/datafusion/physical-plan/src/display.rs b/datafusion/physical-plan/src/display.rs index 56335f13d01b..1cad0ee85c0d 100644 --- a/datafusion/physical-plan/src/display.rs +++ b/datafusion/physical-plan/src/display.rs @@ -120,6 +120,8 @@ pub struct DisplayableExecutionPlan<'a> { show_statistics: bool, /// If schema should be displayed. See [`Self::set_show_schema`] show_schema: bool, + // (TreeRender) Maximum total width of the rendered tree + tree_maximum_render_width: usize, } impl<'a> DisplayableExecutionPlan<'a> { @@ -131,6 +133,7 @@ impl<'a> DisplayableExecutionPlan<'a> { show_metrics: ShowMetrics::None, show_statistics: false, show_schema: false, + tree_maximum_render_width: 240, } } @@ -143,6 +146,7 @@ impl<'a> DisplayableExecutionPlan<'a> { show_metrics: ShowMetrics::Aggregated, show_statistics: false, show_schema: false, + tree_maximum_render_width: 240, } } @@ -155,6 +159,7 @@ impl<'a> DisplayableExecutionPlan<'a> { show_metrics: ShowMetrics::Full, show_statistics: false, show_schema: false, + tree_maximum_render_width: 240, } } @@ -173,6 +178,12 @@ impl<'a> DisplayableExecutionPlan<'a> { self } + /// Set the maximum render width for the tree format + pub fn set_tree_maximum_render_width(mut self, width: usize) -> Self { + self.tree_maximum_render_width = width; + self + } + /// Return a `format`able structure that produces a single line /// per node. /// @@ -270,14 +281,21 @@ impl<'a> DisplayableExecutionPlan<'a> { pub fn tree_render(&self) -> impl fmt::Display + 'a { struct Wrapper<'a> { plan: &'a dyn ExecutionPlan, + maximum_render_width: usize, } impl fmt::Display for Wrapper<'_> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let mut visitor = TreeRenderVisitor { f }; + let mut visitor = TreeRenderVisitor { + f, + maximum_render_width: self.maximum_render_width, + }; visitor.visit(self.plan) } } - Wrapper { plan: self.inner } + Wrapper { + plan: self.inner, + maximum_render_width: self.tree_maximum_render_width, + } } /// Return a single-line summary of the root of the plan @@ -540,6 +558,8 @@ impl ExecutionPlanVisitor for GraphvizVisitor<'_, '_> { struct TreeRenderVisitor<'a, 'b> { /// Write to this formatter f: &'a mut Formatter<'b>, + /// Maximum total width of the rendered tree + maximum_render_width: usize, } impl TreeRenderVisitor<'_, '_> { @@ -557,7 +577,6 @@ impl TreeRenderVisitor<'_, '_> { const HORIZONTAL: &'static str = "─"; // Horizontal line // TODO: Make these variables configurable. - const MAXIMUM_RENDER_WIDTH: usize = 240; // Maximum total width of the rendered tree const NODE_RENDER_WIDTH: usize = 29; // Width of each node's box const MAX_EXTRA_LINES: usize = 30; // Maximum number of extra info lines per node @@ -592,6 +611,12 @@ impl TreeRenderVisitor<'_, '_> { y: usize, ) -> Result<(), fmt::Error> { for x in 0..root.width { + if self.maximum_render_width > 0 + && x * Self::NODE_RENDER_WIDTH >= self.maximum_render_width + { + break; + } + if root.has_node(x, y) { write!(self.f, "{}", Self::LTCORNER)?; write!( @@ -662,7 +687,9 @@ impl TreeRenderVisitor<'_, '_> { // Render the actual node. for render_y in 0..=extra_height { for (x, _) in root.nodes.iter().enumerate().take(root.width) { - if x * Self::NODE_RENDER_WIDTH >= Self::MAXIMUM_RENDER_WIDTH { + if self.maximum_render_width > 0 + && x * Self::NODE_RENDER_WIDTH >= self.maximum_render_width + { break; } @@ -780,7 +807,9 @@ impl TreeRenderVisitor<'_, '_> { y: usize, ) -> Result<(), fmt::Error> { for x in 0..=root.width { - if x * Self::NODE_RENDER_WIDTH >= Self::MAXIMUM_RENDER_WIDTH { + if self.maximum_render_width > 0 + && x * Self::NODE_RENDER_WIDTH >= self.maximum_render_width + { break; } let mut has_adjacent_nodes = false; diff --git a/datafusion/sqllogictest/test_files/explain_tree.slt b/datafusion/sqllogictest/test_files/explain_tree.slt index f4188f4cb395..f57c50506893 100644 --- a/datafusion/sqllogictest/test_files/explain_tree.slt +++ b/datafusion/sqllogictest/test_files/explain_tree.slt @@ -1981,3 +1981,234 @@ physical_plan 06)┌─────────────┴─────────────┐ 07)│ PlaceholderRowExec │ 08)└───────────────────────────┘ + + +# Test explain for large plans + +statement ok +CREATE TABLE t (k int) + +# By default, the plan of this large query is cropped +query TT +EXPLAIN SELECT * FROM t t1, t t2, t t3, t t4, t t5, t t6, t t7, t t8, t t9, t t10 +---- +physical_plan +01)┌───────────────────────────┐ +02)│ CrossJoinExec ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +03)└─────────────┬─────────────┘ +04)┌─────────────┴─────────────┐ +05)│ CrossJoinExec │ +06)│ │ +07)│ ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +08)│ │ │ +09)│ │ │ +10)└─────────────┬─────────────┘ │ +11)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +12)│ CrossJoinExec │ │ DataSourceExec │ +13)│ │ │ -------------------- │ +14)│ ├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ bytes: 0 │ +15)│ │ │ │ format: memory │ +16)│ │ │ │ rows: 0 │ +17)└─────────────┬─────────────┘ │ └───────────────────────────┘ +18)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +19)│ CrossJoinExec │ │ DataSourceExec │ +20)│ │ │ -------------------- │ +21)│ ├───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ bytes: 0 │ +22)│ │ │ │ format: memory │ +23)│ │ │ │ rows: 0 │ +24)└─────────────┬─────────────┘ │ └───────────────────────────┘ +25)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +26)│ CrossJoinExec │ │ DataSourceExec │ +27)│ │ │ -------------------- │ +28)│ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ bytes: 0 │ +29)│ │ │ │ format: memory │ +30)│ │ │ │ rows: 0 │ +31)└─────────────┬─────────────┘ │ └───────────────────────────┘ +32)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +33)│ CrossJoinExec │ │ DataSourceExec │ +34)│ │ │ -------------------- │ +35)│ ├─────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ bytes: 0 │ +36)│ │ │ │ format: memory │ +37)│ │ │ │ rows: 0 │ +38)└─────────────┬─────────────┘ │ └───────────────────────────┘ +39)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +40)│ CrossJoinExec │ │ DataSourceExec │ +41)│ │ │ -------------------- │ +42)│ ├────────────────────────────────────────────────────────────────────────┐ │ bytes: 0 │ +43)│ │ │ │ format: memory │ +44)│ │ │ │ rows: 0 │ +45)└─────────────┬─────────────┘ │ └───────────────────────────┘ +46)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +47)│ CrossJoinExec │ │ DataSourceExec │ +48)│ │ │ -------------------- │ +49)│ ├───────────────────────────────────────────┐ │ bytes: 0 │ +50)│ │ │ │ format: memory │ +51)│ │ │ │ rows: 0 │ +52)└─────────────┬─────────────┘ │ └───────────────────────────┘ +53)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +54)│ CrossJoinExec │ │ DataSourceExec │ +55)│ │ │ -------------------- │ +56)│ ├──────────────┐ │ bytes: 0 │ +57)│ │ │ │ format: memory │ +58)│ │ │ │ rows: 0 │ +59)└─────────────┬─────────────┘ │ └───────────────────────────┘ +60)┌─────────────┴─────────────┐┌─────────────┴─────────────┐ +61)│ DataSourceExec ││ DataSourceExec │ +62)│ -------------------- ││ -------------------- │ +63)│ bytes: 0 ││ bytes: 0 │ +64)│ format: memory ││ format: memory │ +65)│ rows: 0 ││ rows: 0 │ +66)└───────────────────────────┘└───────────────────────────┘ + +# Setting the tree_maximum_render_size to 0 will allow the entire plan to be rendered +statement ok +SET datafusion.explain.tree_maximum_render_width = 0 + +query TT +EXPLAIN SELECT * FROM t t1, t t2, t t3, t t4, t t5, t t6, t t7, t t8, t t9, t t10 +---- +physical_plan +01)┌───────────────────────────┐ +02)│ CrossJoinExec ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +03)└─────────────┬─────────────┘ │ +04)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +05)│ CrossJoinExec │ │ DataSourceExec │ +06)│ │ │ -------------------- │ +07)│ ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ bytes: 0 │ +08)│ │ │ │ format: memory │ +09)│ │ │ │ rows: 0 │ +10)└─────────────┬─────────────┘ │ └───────────────────────────┘ +11)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +12)│ CrossJoinExec │ │ DataSourceExec │ +13)│ │ │ -------------------- │ +14)│ ├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ bytes: 0 │ +15)│ │ │ │ format: memory │ +16)│ │ │ │ rows: 0 │ +17)└─────────────┬─────────────┘ │ └───────────────────────────┘ +18)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +19)│ CrossJoinExec │ │ DataSourceExec │ +20)│ │ │ -------------------- │ +21)│ ├───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ bytes: 0 │ +22)│ │ │ │ format: memory │ +23)│ │ │ │ rows: 0 │ +24)└─────────────┬─────────────┘ │ └───────────────────────────┘ +25)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +26)│ CrossJoinExec │ │ DataSourceExec │ +27)│ │ │ -------------------- │ +28)│ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ bytes: 0 │ +29)│ │ │ │ format: memory │ +30)│ │ │ │ rows: 0 │ +31)└─────────────┬─────────────┘ │ └───────────────────────────┘ +32)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +33)│ CrossJoinExec │ │ DataSourceExec │ +34)│ │ │ -------------------- │ +35)│ ├─────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ bytes: 0 │ +36)│ │ │ │ format: memory │ +37)│ │ │ │ rows: 0 │ +38)└─────────────┬─────────────┘ │ └───────────────────────────┘ +39)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +40)│ CrossJoinExec │ │ DataSourceExec │ +41)│ │ │ -------------------- │ +42)│ ├────────────────────────────────────────────────────────────────────────┐ │ bytes: 0 │ +43)│ │ │ │ format: memory │ +44)│ │ │ │ rows: 0 │ +45)└─────────────┬─────────────┘ │ └───────────────────────────┘ +46)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +47)│ CrossJoinExec │ │ DataSourceExec │ +48)│ │ │ -------------------- │ +49)│ ├───────────────────────────────────────────┐ │ bytes: 0 │ +50)│ │ │ │ format: memory │ +51)│ │ │ │ rows: 0 │ +52)└─────────────┬─────────────┘ │ └───────────────────────────┘ +53)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +54)│ CrossJoinExec │ │ DataSourceExec │ +55)│ │ │ -------------------- │ +56)│ ├──────────────┐ │ bytes: 0 │ +57)│ │ │ │ format: memory │ +58)│ │ │ │ rows: 0 │ +59)└─────────────┬─────────────┘ │ └───────────────────────────┘ +60)┌─────────────┴─────────────┐┌─────────────┴─────────────┐ +61)│ DataSourceExec ││ DataSourceExec │ +62)│ -------------------- ││ -------------------- │ +63)│ bytes: 0 ││ bytes: 0 │ +64)│ format: memory ││ format: memory │ +65)│ rows: 0 ││ rows: 0 │ +66)└───────────────────────────┘└───────────────────────────┘ + +# Setting the tree_maximum_render_size to a smaller size +statement ok +SET datafusion.explain.tree_maximum_render_width = 60 + +query TT +EXPLAIN SELECT * FROM t t1, t t2, t t3, t t4, t t5, t t6, t t7, t t8, t t9, t t10 +---- +physical_plan +01)┌───────────────────────────┐ +02)│ CrossJoinExec ├────────────────────────────────────────────────────────── +03)└─────────────┬─────────────┘ +04)┌─────────────┴─────────────┐ +05)│ CrossJoinExec │ +06)│ │ +07)│ ├────────────────────────────────────────────────────────── +08)│ │ +09)│ │ +10)└─────────────┬─────────────┘ +11)┌─────────────┴─────────────┐ +12)│ CrossJoinExec │ +13)│ │ +14)│ ├────────────────────────────────────────────────────────── +15)│ │ +16)│ │ +17)└─────────────┬─────────────┘ +18)┌─────────────┴─────────────┐ +19)│ CrossJoinExec │ +20)│ │ +21)│ ├────────────────────────────────────────────────────────── +22)│ │ +23)│ │ +24)└─────────────┬─────────────┘ +25)┌─────────────┴─────────────┐ +26)│ CrossJoinExec │ +27)│ │ +28)│ ├────────────────────────────────────────────────────────── +29)│ │ +30)│ │ +31)└─────────────┬─────────────┘ +32)┌─────────────┴─────────────┐ +33)│ CrossJoinExec │ +34)│ │ +35)│ ├────────────────────────────────────────────────────────── +36)│ │ +37)│ │ +38)└─────────────┬─────────────┘ +39)┌─────────────┴─────────────┐ +40)│ CrossJoinExec │ +41)│ │ +42)│ ├────────────────────────────────────────────────────────── +43)│ │ +44)│ │ +45)└─────────────┬─────────────┘ +46)┌─────────────┴─────────────┐ +47)│ CrossJoinExec │ +48)│ │ +49)│ ├───────────────────────────────────────────┐ +50)│ │ │ +51)│ │ │ +52)└─────────────┬─────────────┘ │ +53)┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ +54)│ CrossJoinExec │ │ DataSourceExec │ +55)│ │ │ -------------------- │ +56)│ ├──────────────┐ │ bytes: 0 │ +57)│ │ │ │ format: memory │ +58)│ │ │ │ rows: 0 │ +59)└─────────────┬─────────────┘ │ └───────────────────────────┘ +60)┌─────────────┴─────────────┐┌─────────────┴─────────────┐ +61)│ DataSourceExec ││ DataSourceExec │ +62)│ -------------------- ││ -------------------- │ +63)│ bytes: 0 ││ bytes: 0 │ +64)│ format: memory ││ format: memory │ +65)│ rows: 0 ││ rows: 0 │ +66)└───────────────────────────┘└───────────────────────────┘ + +statement ok +DROP TABLE t diff --git a/datafusion/sqllogictest/test_files/information_schema.slt b/datafusion/sqllogictest/test_files/information_schema.slt index f76e436e0ad3..86dfbd7c8496 100644 --- a/datafusion/sqllogictest/test_files/information_schema.slt +++ b/datafusion/sqllogictest/test_files/information_schema.slt @@ -274,6 +274,7 @@ datafusion.explain.physical_plan_only false datafusion.explain.show_schema false datafusion.explain.show_sizes true datafusion.explain.show_statistics false +datafusion.explain.tree_maximum_render_width 240 datafusion.format.date_format %Y-%m-%d datafusion.format.datetime_format %Y-%m-%dT%H:%M:%S%.f datafusion.format.duration_format pretty @@ -386,6 +387,7 @@ datafusion.explain.physical_plan_only false When set to true, the explain statem datafusion.explain.show_schema false When set to true, the explain statement will print schema information datafusion.explain.show_sizes true When set to true, the explain statement will print the partition sizes datafusion.explain.show_statistics false When set to true, the explain statement will print operator statistics for physical plans +datafusion.explain.tree_maximum_render_width 240 (format=tree only) Maximum total width of the rendered tree. When set to 0, the tree will have no width limit. datafusion.format.date_format %Y-%m-%d Date format for date arrays datafusion.format.datetime_format %Y-%m-%dT%H:%M:%S%.f Format for DateTime arrays datafusion.format.duration_format pretty Duration format. Can be either `"pretty"` or `"ISO8601"` diff --git a/docs/source/user-guide/configs.md b/docs/source/user-guide/configs.md index c618aa18c231..8592e8bca4e1 100644 --- a/docs/source/user-guide/configs.md +++ b/docs/source/user-guide/configs.md @@ -127,6 +127,7 @@ Environment variables are read during `SessionConfig` initialisation so they mus | datafusion.explain.show_sizes | true | When set to true, the explain statement will print the partition sizes | | datafusion.explain.show_schema | false | When set to true, the explain statement will print schema information | | datafusion.explain.format | indent | Display format of explain. Default is "indent". When set to "tree", it will print the plan in a tree-rendered format. | +| datafusion.explain.tree_maximum_render_width | 240 | (format=tree only) Maximum total width of the rendered tree. When set to 0, the tree will have no width limit. | | datafusion.sql_parser.parse_float_as_decimal | false | When set to true, SQL parser will parse float as decimal type | | datafusion.sql_parser.enable_ident_normalization | true | When set to true, SQL parser will normalize ident (convert ident to lowercase when not quoted) | | datafusion.sql_parser.enable_options_value_normalization | false | When set to true, SQL parser will normalize options value (convert value to lowercase). Note that this option is ignored and will be removed in the future. All case-insensitive values are normalized automatically. |