Skip to content

Commit 41e7aed

Browse files
alambwaynexia
andauthored
Support EXPLAIN ... FORMAT <indent | tree | json | graphviz > ... (#15166)
* Support EXPLAIN FORMAT <format> * Update datafusion/sql/src/parser.rs Co-authored-by: Ruihang Xia <waynestxia@gmail.com> * Improve documentation * Remove to_stringified and simplify code * mark deprecated rather than remove * Add a note about explain format configuration --------- Co-authored-by: Ruihang Xia <waynestxia@gmail.com>
1 parent 0e77fb2 commit 41e7aed

File tree

12 files changed

+670
-131
lines changed

12 files changed

+670
-131
lines changed

datafusion-examples/examples/planner_api.rs

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
// under the License.
1717

1818
use datafusion::error::Result;
19-
use datafusion::logical_expr::{LogicalPlan, PlanType};
20-
use datafusion::physical_plan::{displayable, DisplayFormatType};
19+
use datafusion::logical_expr::LogicalPlan;
20+
use datafusion::physical_plan::displayable;
2121
use datafusion::physical_planner::DefaultPhysicalPlanner;
2222
use datafusion::prelude::*;
2323

@@ -77,13 +77,7 @@ async fn to_physical_plan_in_one_api_demo(
7777

7878
println!(
7979
"Physical plan direct from logical plan:\n\n{}\n\n",
80-
displayable(physical_plan.as_ref())
81-
.to_stringified(
82-
false,
83-
PlanType::InitialPhysicalPlan,
84-
DisplayFormatType::Default
85-
)
86-
.plan
80+
displayable(physical_plan.as_ref()).indent(false)
8781
);
8882

8983
Ok(())
@@ -123,13 +117,7 @@ async fn to_physical_plan_step_by_step_demo(
123117
.await?;
124118
println!(
125119
"Final physical plan:\n\n{}\n\n",
126-
displayable(physical_plan.as_ref())
127-
.to_stringified(
128-
false,
129-
PlanType::InitialPhysicalPlan,
130-
DisplayFormatType::Default
131-
)
132-
.plan
120+
displayable(physical_plan.as_ref()).indent(false)
133121
);
134122

135123
// Call the physical optimizer with an existing physical plan (in this
@@ -142,13 +130,7 @@ async fn to_physical_plan_step_by_step_demo(
142130
planner.optimize_physical_plan(physical_plan, &ctx.state(), |_, _| {})?;
143131
println!(
144132
"Optimized physical plan:\n\n{}\n\n",
145-
displayable(physical_plan.as_ref())
146-
.to_stringified(
147-
false,
148-
PlanType::InitialPhysicalPlan,
149-
DisplayFormatType::Default
150-
)
151-
.plan
133+
displayable(physical_plan.as_ref()).indent(false)
152134
);
153135

154136
Ok(())

datafusion/core/src/execution/session_state.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ impl SessionState {
577577
return Ok(LogicalPlan::Explain(Explain {
578578
verbose: e.verbose,
579579
plan: Arc::clone(&e.plan),
580+
explain_format: e.explain_format.clone(),
580581
stringified_plans,
581582
schema: Arc::clone(&e.schema),
582583
logical_optimization_succeeded: false,
@@ -612,6 +613,7 @@ impl SessionState {
612613

613614
Ok(LogicalPlan::Explain(Explain {
614615
verbose: e.verbose,
616+
explain_format: e.explain_format.clone(),
615617
plan,
616618
stringified_plans,
617619
schema: Arc::clone(&e.schema),

datafusion/core/src/physical_planner.rs

Lines changed: 85 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
2020
use std::borrow::Cow;
2121
use std::collections::HashMap;
22-
use std::str::FromStr;
2322
use std::sync::Arc;
2423

2524
use crate::datasource::file_format::file_type_to_format;
@@ -78,8 +77,8 @@ use datafusion_expr::expr::{
7877
use datafusion_expr::expr_rewriter::unnormalize_cols;
7978
use datafusion_expr::logical_plan::builder::wrap_projection_for_join_if_necessary;
8079
use datafusion_expr::{
81-
Analyze, DescribeTable, DmlStatement, Explain, Extension, FetchType, Filter,
82-
JoinType, RecursiveQuery, SkipType, SortExpr, StringifiedPlan, WindowFrame,
80+
Analyze, DescribeTable, DmlStatement, Explain, ExplainFormat, Extension, FetchType,
81+
Filter, JoinType, RecursiveQuery, SkipType, SortExpr, StringifiedPlan, WindowFrame,
8382
WindowFrameBound, WriteOp,
8483
};
8584
use datafusion_physical_expr::aggregate::{AggregateExprBuilder, AggregateFunctionExpr};
@@ -89,7 +88,6 @@ use datafusion_physical_optimizer::PhysicalOptimizerRule;
8988
use datafusion_physical_plan::execution_plan::InvariantLevel;
9089
use datafusion_physical_plan::placeholder_row::PlaceholderRowExec;
9190
use datafusion_physical_plan::unnest::ListUnnest;
92-
use datafusion_physical_plan::DisplayFormatType;
9391

9492
use crate::schema_equivalence::schema_satisfied_by;
9593
use async_trait::async_trait;
@@ -1742,12 +1740,54 @@ impl DefaultPhysicalPlanner {
17421740
let mut stringified_plans = vec![];
17431741

17441742
let config = &session_state.config_options().explain;
1745-
let explain_format = DisplayFormatType::from_str(&config.format)?;
1743+
let explain_format = &e.explain_format;
1744+
1745+
match explain_format {
1746+
ExplainFormat::Indent => { /* fall through */ }
1747+
ExplainFormat::Tree => {
1748+
// Tree render does not try to explain errors,
1749+
let physical_plan = self
1750+
.create_initial_plan(e.plan.as_ref(), session_state)
1751+
.await?;
1752+
1753+
let optimized_plan = self.optimize_physical_plan(
1754+
physical_plan,
1755+
session_state,
1756+
|_plan, _optimizer| {},
1757+
)?;
1758+
1759+
stringified_plans.push(StringifiedPlan::new(
1760+
FinalPhysicalPlan,
1761+
displayable(optimized_plan.as_ref())
1762+
.tree_render()
1763+
.to_string(),
1764+
));
1765+
}
1766+
ExplainFormat::PostgresJSON => {
1767+
stringified_plans.push(StringifiedPlan::new(
1768+
FinalLogicalPlan,
1769+
e.plan.display_pg_json().to_string(),
1770+
));
1771+
}
1772+
ExplainFormat::Graphviz => {
1773+
stringified_plans.push(StringifiedPlan::new(
1774+
FinalLogicalPlan,
1775+
e.plan.display_graphviz().to_string(),
1776+
));
1777+
}
1778+
};
17461779

1747-
let skip_logical_plan =
1748-
config.physical_plan_only || explain_format == DisplayFormatType::TreeRender;
1780+
if !stringified_plans.is_empty() {
1781+
return Ok(Arc::new(ExplainExec::new(
1782+
Arc::clone(e.schema.inner()),
1783+
stringified_plans,
1784+
e.verbose,
1785+
)));
1786+
}
17491787

1750-
if !skip_logical_plan {
1788+
// The indent mode is quite sophisticated, and handles quite a few
1789+
// different cases / options for displaying the plan.
1790+
if !config.physical_plan_only {
17511791
stringified_plans.clone_from(&e.stringified_plans);
17521792
if e.logical_optimization_succeeded {
17531793
stringified_plans.push(e.plan.to_stringified(FinalLogicalPlan));
@@ -1761,41 +1801,35 @@ impl DefaultPhysicalPlanner {
17611801
{
17621802
Ok(input) => {
17631803
// Include statistics / schema if enabled
1764-
stringified_plans.push(
1804+
stringified_plans.push(StringifiedPlan::new(
1805+
InitialPhysicalPlan,
17651806
displayable(input.as_ref())
17661807
.set_show_statistics(config.show_statistics)
17671808
.set_show_schema(config.show_schema)
1768-
.to_stringified(
1769-
e.verbose,
1770-
InitialPhysicalPlan,
1771-
explain_format,
1772-
),
1773-
);
1809+
.indent(e.verbose)
1810+
.to_string(),
1811+
));
17741812

17751813
// Show statistics + schema in verbose output even if not
17761814
// explicitly requested
17771815
if e.verbose {
17781816
if !config.show_statistics {
1779-
stringified_plans.push(
1817+
stringified_plans.push(StringifiedPlan::new(
1818+
InitialPhysicalPlanWithStats,
17801819
displayable(input.as_ref())
17811820
.set_show_statistics(true)
1782-
.to_stringified(
1783-
e.verbose,
1784-
InitialPhysicalPlanWithStats,
1785-
explain_format,
1786-
),
1787-
);
1821+
.indent(e.verbose)
1822+
.to_string(),
1823+
));
17881824
}
17891825
if !config.show_schema {
1790-
stringified_plans.push(
1826+
stringified_plans.push(StringifiedPlan::new(
1827+
InitialPhysicalPlanWithSchema,
17911828
displayable(input.as_ref())
17921829
.set_show_schema(true)
1793-
.to_stringified(
1794-
e.verbose,
1795-
InitialPhysicalPlanWithSchema,
1796-
explain_format,
1797-
),
1798-
);
1830+
.indent(e.verbose)
1831+
.to_string(),
1832+
));
17991833
}
18001834
}
18011835

@@ -1805,52 +1839,50 @@ impl DefaultPhysicalPlanner {
18051839
|plan, optimizer| {
18061840
let optimizer_name = optimizer.name().to_string();
18071841
let plan_type = OptimizedPhysicalPlan { optimizer_name };
1808-
stringified_plans.push(
1842+
stringified_plans.push(StringifiedPlan::new(
1843+
plan_type,
18091844
displayable(plan)
18101845
.set_show_statistics(config.show_statistics)
18111846
.set_show_schema(config.show_schema)
1812-
.to_stringified(e.verbose, plan_type, explain_format),
1813-
);
1847+
.indent(e.verbose)
1848+
.to_string(),
1849+
));
18141850
},
18151851
);
18161852
match optimized_plan {
18171853
Ok(input) => {
18181854
// This plan will includes statistics if show_statistics is on
1819-
stringified_plans.push(
1855+
stringified_plans.push(StringifiedPlan::new(
1856+
FinalPhysicalPlan,
18201857
displayable(input.as_ref())
18211858
.set_show_statistics(config.show_statistics)
18221859
.set_show_schema(config.show_schema)
1823-
.to_stringified(
1824-
e.verbose,
1825-
FinalPhysicalPlan,
1826-
explain_format,
1827-
),
1828-
);
1860+
.indent(e.verbose)
1861+
.to_string(),
1862+
));
18291863

18301864
// Show statistics + schema in verbose output even if not
18311865
// explicitly requested
18321866
if e.verbose {
18331867
if !config.show_statistics {
1834-
stringified_plans.push(
1868+
stringified_plans.push(StringifiedPlan::new(
1869+
FinalPhysicalPlanWithStats,
18351870
displayable(input.as_ref())
18361871
.set_show_statistics(true)
1837-
.to_stringified(
1838-
e.verbose,
1839-
FinalPhysicalPlanWithStats,
1840-
explain_format,
1841-
),
1842-
);
1872+
.indent(e.verbose)
1873+
.to_string(),
1874+
));
18431875
}
18441876
if !config.show_schema {
1845-
stringified_plans.push(
1877+
stringified_plans.push(StringifiedPlan::new(
1878+
FinalPhysicalPlanWithSchema,
1879+
// This will include schema if show_schema is on
1880+
// and will be set to true if verbose is on
18461881
displayable(input.as_ref())
18471882
.set_show_schema(true)
1848-
.to_stringified(
1849-
e.verbose,
1850-
FinalPhysicalPlanWithSchema,
1851-
explain_format,
1852-
),
1853-
);
1883+
.indent(e.verbose)
1884+
.to_string(),
1885+
));
18541886
}
18551887
}
18561888
}

datafusion/expr/src/logical_plan/builder.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use crate::{
4646
};
4747

4848
use super::dml::InsertOp;
49-
use super::plan::ColumnUnnestList;
49+
use super::plan::{ColumnUnnestList, ExplainFormat};
5050
use arrow::compute::can_cast_types;
5151
use arrow::datatypes::{DataType, Field, Fields, Schema, SchemaRef};
5252
use datafusion_common::display::ToStringifiedPlan;
@@ -1211,6 +1211,7 @@ impl LogicalPlanBuilder {
12111211
Ok(Self::new(LogicalPlan::Explain(Explain {
12121212
verbose,
12131213
plan: self.plan,
1214+
explain_format: ExplainFormat::Indent,
12141215
stringified_plans,
12151216
schema,
12161217
logical_optimization_succeeded: false,

datafusion/expr/src/logical_plan/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ pub use ddl::{
3838
pub use dml::{DmlStatement, WriteOp};
3939
pub use plan::{
4040
projection_schema, Aggregate, Analyze, ColumnUnnestList, DescribeTable, Distinct,
41-
DistinctOn, EmptyRelation, Explain, Extension, FetchType, Filter, Join,
42-
JoinConstraint, JoinType, Limit, LogicalPlan, Partitioning, PlanType, Projection,
43-
RecursiveQuery, Repartition, SkipType, Sort, StringifiedPlan, Subquery,
41+
DistinctOn, EmptyRelation, Explain, ExplainFormat, Extension, FetchType, Filter,
42+
Join, JoinConstraint, JoinType, Limit, LogicalPlan, Partitioning, PlanType,
43+
Projection, RecursiveQuery, Repartition, SkipType, Sort, StringifiedPlan, Subquery,
4444
SubqueryAlias, TableScan, ToStringifiedPlan, Union, Unnest, Values, Window,
4545
};
4646
pub use statement::{

0 commit comments

Comments
 (0)