This is an automated email from the ASF dual-hosted git repository. alamb pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/datafusion.git
The following commit(s) were added to refs/heads/main by this push:
new a0ce581092 feat: Allow tree explain format width to be customizable
(#16827)
a0ce581092 is described below
commit a0ce581092430271e67552ec6370f100cbb80046
Author: Nuno Faria <[email protected]>
AuthorDate: Tue Jul 22 15:46:07 2025 +0100
feat: Allow tree explain format width to be customizable (#16827)
- Also fix a bug in the tree format.
---
datafusion/common/src/config.rs | 4 +
datafusion/core/src/physical_planner.rs | 1 +
datafusion/physical-plan/src/display.rs | 39 +++-
.../sqllogictest/test_files/explain_tree.slt | 231 +++++++++++++++++++++
.../sqllogictest/test_files/information_schema.slt | 2 +
docs/source/user-guide/configs.md | 1 +
6 files changed, 273 insertions(+), 5 deletions(-)
diff --git a/datafusion/common/src/config.rs b/datafusion/common/src/config.rs
index 31159d4a85..5796edc283 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 ab123dccea..5a0ee327cb 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 56335f13d0..1cad0ee85c 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 f4188f4cb3..f57c505068 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 f76e436e0a..86dfbd7c84 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 c618aa18c2..8592e8bca4 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.
[...]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
