Skip to content

Commit 187f38e

Browse files
committed
Support EXPLAIN FORMAT <format>
1 parent a6cd714 commit 187f38e

File tree

11 files changed

+624
-67
lines changed

11 files changed

+624
-67
lines changed

datafusion/core/src/execution/session_state.rs

+2
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

+57-14
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};
@@ -1740,18 +1739,62 @@ impl DefaultPhysicalPlanner {
17401739
let mut stringified_plans = vec![];
17411740

17421741
let config = &session_state.config_options().explain;
1743-
let explain_format = DisplayFormatType::from_str(&config.format)?;
1742+
let explain_format = &e.explain_format;
1743+
1744+
match explain_format {
1745+
ExplainFormat::Indent => { /* fall through */ }
1746+
ExplainFormat::Tree => {
1747+
// Tree render does not try to explain errors,
1748+
let physical_plan = self
1749+
.create_initial_plan(e.plan.as_ref(), session_state)
1750+
.await?;
1751+
1752+
let optimized_plan = self.optimize_physical_plan(
1753+
physical_plan,
1754+
session_state,
1755+
|_plan, _optimizer| {},
1756+
)?;
1757+
1758+
stringified_plans.push(
1759+
displayable(optimized_plan.as_ref()).to_stringified(
1760+
e.verbose,
1761+
FinalPhysicalPlan,
1762+
DisplayFormatType::TreeRender,
1763+
),
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+
};
17441779

1745-
let skip_logical_plan =
1746-
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+
}
17471787

1748-
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 {
17491791
stringified_plans.clone_from(&e.stringified_plans);
17501792
if e.logical_optimization_succeeded {
17511793
stringified_plans.push(e.plan.to_stringified(FinalLogicalPlan));
17521794
}
17531795
}
17541796

1797+
let display_format = DisplayFormatType::Default;
17551798
if !config.logical_plan_only && e.logical_optimization_succeeded {
17561799
match self
17571800
.create_initial_plan(e.plan.as_ref(), session_state)
@@ -1766,7 +1809,7 @@ impl DefaultPhysicalPlanner {
17661809
.to_stringified(
17671810
e.verbose,
17681811
InitialPhysicalPlan,
1769-
explain_format,
1812+
display_format,
17701813
),
17711814
);
17721815

@@ -1780,7 +1823,7 @@ impl DefaultPhysicalPlanner {
17801823
.to_stringified(
17811824
e.verbose,
17821825
InitialPhysicalPlanWithStats,
1783-
explain_format,
1826+
display_format,
17841827
),
17851828
);
17861829
}
@@ -1791,7 +1834,7 @@ impl DefaultPhysicalPlanner {
17911834
.to_stringified(
17921835
e.verbose,
17931836
InitialPhysicalPlanWithSchema,
1794-
explain_format,
1837+
display_format,
17951838
),
17961839
);
17971840
}
@@ -1807,7 +1850,7 @@ impl DefaultPhysicalPlanner {
18071850
displayable(plan)
18081851
.set_show_statistics(config.show_statistics)
18091852
.set_show_schema(config.show_schema)
1810-
.to_stringified(e.verbose, plan_type, explain_format),
1853+
.to_stringified(e.verbose, plan_type, display_format),
18111854
);
18121855
},
18131856
);
@@ -1821,7 +1864,7 @@ impl DefaultPhysicalPlanner {
18211864
.to_stringified(
18221865
e.verbose,
18231866
FinalPhysicalPlan,
1824-
explain_format,
1867+
display_format,
18251868
),
18261869
);
18271870

@@ -1835,7 +1878,7 @@ impl DefaultPhysicalPlanner {
18351878
.to_stringified(
18361879
e.verbose,
18371880
FinalPhysicalPlanWithStats,
1838-
explain_format,
1881+
display_format,
18391882
),
18401883
);
18411884
}
@@ -1846,7 +1889,7 @@ impl DefaultPhysicalPlanner {
18461889
.to_stringified(
18471890
e.verbose,
18481891
FinalPhysicalPlanWithSchema,
1849-
explain_format,
1892+
display_format,
18501893
),
18511894
);
18521895
}

datafusion/expr/src/logical_plan/builder.rs

+2-1
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

+3-3
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::{

datafusion/expr/src/logical_plan/plan.rs

+157
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use std::cmp::Ordering;
2121
use std::collections::{BTreeMap, HashMap, HashSet};
2222
use std::fmt::{self, Debug, Display, Formatter};
2323
use std::hash::{Hash, Hasher};
24+
use std::str::FromStr;
2425
use std::sync::{Arc, LazyLock};
2526

2627
use super::dml::CopyTo;
@@ -1084,6 +1085,7 @@ impl LogicalPlan {
10841085
Ok(LogicalPlan::Explain(Explain {
10851086
verbose: e.verbose,
10861087
plan: Arc::new(input),
1088+
explain_format: e.explain_format.clone(),
10871089
stringified_plans: e.stringified_plans.clone(),
10881090
schema: Arc::clone(&e.schema),
10891091
logical_optimization_succeeded: e.logical_optimization_succeeded,
@@ -2924,12 +2926,167 @@ impl PartialOrd for DescribeTable {
29242926
}
29252927
}
29262928

2929+
/// Output formats for controlling for Explain plans
2930+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2931+
pub enum ExplainFormat {
2932+
/// Indent mode
2933+
///
2934+
/// Example:
2935+
/// ```text
2936+
/// > explain format indent select x from values (1) t(x);
2937+
/// +---------------+-----------------------------------------------------+
2938+
/// | plan_type | plan |
2939+
/// +---------------+-----------------------------------------------------+
2940+
/// | logical_plan | SubqueryAlias: t |
2941+
/// | | Projection: column1 AS x |
2942+
/// | | Values: (Int64(1)) |
2943+
/// | physical_plan | ProjectionExec: expr=[column1@0 as x] |
2944+
/// | | DataSourceExec: partitions=1, partition_sizes=[1] |
2945+
/// | | |
2946+
/// +---------------+-----------------------------------------------------+
2947+
/// ```
2948+
Indent,
2949+
/// Tree mode
2950+
///
2951+
/// Example:
2952+
/// ```text
2953+
/// > explain format tree select x from values (1) t(x);
2954+
/// +---------------+-------------------------------+
2955+
/// | plan_type | plan |
2956+
/// +---------------+-------------------------------+
2957+
/// | physical_plan | ┌───────────────────────────┐ |
2958+
/// | | │ ProjectionExec │ |
2959+
/// | | │ -------------------- │ |
2960+
/// | | │ x: column1@0 │ |
2961+
/// | | └─────────────┬─────────────┘ |
2962+
/// | | ┌─────────────┴─────────────┐ |
2963+
/// | | │ DataSourceExec │ |
2964+
/// | | │ -------------------- │ |
2965+
/// | | │ bytes: 128 │ |
2966+
/// | | │ format: memory │ |
2967+
/// | | │ rows: 1 │ |
2968+
/// | | └───────────────────────────┘ |
2969+
/// | | |
2970+
/// +---------------+-------------------------------+
2971+
/// ```
2972+
Tree,
2973+
/// Postgres Json mode
2974+
///
2975+
/// A displayable structure that produces plan in postgresql JSON format.
2976+
///
2977+
/// Users can use this format to visualize the plan in existing plan
2978+
/// visualization tools, for example [dalibo](https://explain.dalibo.com/)
2979+
///
2980+
/// Example:
2981+
/// ```text
2982+
/// > explain format pgjson select x from values (1) t(x);
2983+
/// +--------------+--------------------------------------+
2984+
/// | plan_type | plan |
2985+
/// +--------------+--------------------------------------+
2986+
/// | logical_plan | [ |
2987+
/// | | { |
2988+
/// | | "Plan": { |
2989+
/// | | "Alias": "t", |
2990+
/// | | "Node Type": "Subquery", |
2991+
/// | | "Output": [ |
2992+
/// | | "x" |
2993+
/// | | ], |
2994+
/// | | "Plans": [ |
2995+
/// | | { |
2996+
/// | | "Expressions": [ |
2997+
/// | | "column1 AS x" |
2998+
/// | | ], |
2999+
/// | | "Node Type": "Projection", |
3000+
/// | | "Output": [ |
3001+
/// | | "x" |
3002+
/// | | ], |
3003+
/// | | "Plans": [ |
3004+
/// | | { |
3005+
/// | | "Node Type": "Values", |
3006+
/// | | "Output": [ |
3007+
/// | | "column1" |
3008+
/// | | ], |
3009+
/// | | "Plans": [], |
3010+
/// | | "Values": "(Int64(1))" |
3011+
/// | | } |
3012+
/// | | ] |
3013+
/// | | } |
3014+
/// | | ] |
3015+
/// | | } |
3016+
/// | | } |
3017+
/// | | ] |
3018+
/// +--------------+--------------------------------------+
3019+
/// ```
3020+
PostgresJSON,
3021+
/// Graphviz mode
3022+
///
3023+
/// Example:
3024+
/// ```text
3025+
/// > explain format graphviz select x from values (1) t(x);
3026+
/// +--------------+------------------------------------------------------------------------+
3027+
/// | plan_type | plan |
3028+
/// +--------------+------------------------------------------------------------------------+
3029+
/// | logical_plan | |
3030+
/// | | // Begin DataFusion GraphViz Plan, |
3031+
/// | | // display it online here: https://dreampuf.github.io/GraphvizOnline |
3032+
/// | | |
3033+
/// | | digraph { |
3034+
/// | | subgraph cluster_1 |
3035+
/// | | { |
3036+
/// | | graph[label="LogicalPlan"] |
3037+
/// | | 2[shape=box label="SubqueryAlias: t"] |
3038+
/// | | 3[shape=box label="Projection: column1 AS x"] |
3039+
/// | | 2 -> 3 [arrowhead=none, arrowtail=normal, dir=back] |
3040+
/// | | 4[shape=box label="Values: (Int64(1))"] |
3041+
/// | | 3 -> 4 [arrowhead=none, arrowtail=normal, dir=back] |
3042+
/// | | } |
3043+
/// | | subgraph cluster_5 |
3044+
/// | | { |
3045+
/// | | graph[label="Detailed LogicalPlan"] |
3046+
/// | | 6[shape=box label="SubqueryAlias: t\nSchema: [x:Int64;N]"] |
3047+
/// | | 7[shape=box label="Projection: column1 AS x\nSchema: [x:Int64;N]"] |
3048+
/// | | 6 -> 7 [arrowhead=none, arrowtail=normal, dir=back] |
3049+
/// | | 8[shape=box label="Values: (Int64(1))\nSchema: [column1:Int64;N]"] |
3050+
/// | | 7 -> 8 [arrowhead=none, arrowtail=normal, dir=back] |
3051+
/// | | } |
3052+
/// | | } |
3053+
/// | | // End DataFusion GraphViz Plan |
3054+
/// | | |
3055+
/// +--------------+------------------------------------------------------------------------+
3056+
/// ```
3057+
Graphviz,
3058+
}
3059+
3060+
/// Implement parsing strings to `ExplainFormat`
3061+
impl FromStr for ExplainFormat {
3062+
type Err = DataFusionError;
3063+
3064+
fn from_str(format: &str) -> std::result::Result<Self, Self::Err> {
3065+
match format.to_lowercase().as_str() {
3066+
"indent" => Ok(ExplainFormat::Indent),
3067+
"tree" => Ok(ExplainFormat::Tree),
3068+
"pgjson" => Ok(ExplainFormat::PostgresJSON),
3069+
"graphviz" => Ok(ExplainFormat::Graphviz),
3070+
_ => {
3071+
plan_err!("Invalid explain format. Expected 'indent', 'tree', 'pgjson' or 'graphviz'. Got '{format}'")
3072+
}
3073+
}
3074+
}
3075+
}
3076+
29273077
/// Produces a relation with string representations of
29283078
/// various parts of the plan
3079+
///
3080+
/// See [the documentation] for more information
3081+
///
3082+
/// [the documentation]: https://datafusion.apache.org/user-guide/sql/explain.html
29293083
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29303084
pub struct Explain {
29313085
/// Should extra (detailed, intermediate plans) be included?
29323086
pub verbose: bool,
3087+
/// Output format for explain, if specified.
3088+
/// If none, defaults to `text`
3089+
pub explain_format: ExplainFormat,
29333090
/// The logical plan that is being EXPLAIN'd
29343091
pub plan: Arc<LogicalPlan>,
29353092
/// Represent the various stages plans have gone through

datafusion/expr/src/logical_plan/tree_node.rs

+2
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,15 @@ impl TreeNode for LogicalPlan {
202202
.update_data(LogicalPlan::Distinct),
203203
LogicalPlan::Explain(Explain {
204204
verbose,
205+
explain_format: format,
205206
plan,
206207
stringified_plans,
207208
schema,
208209
logical_optimization_succeeded,
209210
}) => plan.map_elements(f)?.update_data(|plan| {
210211
LogicalPlan::Explain(Explain {
211212
verbose,
213+
explain_format: format,
212214
plan,
213215
stringified_plans,
214216
schema,

0 commit comments

Comments
 (0)