Skip to content

Commit 3e7bd4f

Browse files
committed
Support EXPLAIN FORMAT <format>
1 parent ab1e77e commit 3e7bd4f

File tree

9 files changed

+442
-178
lines changed

9 files changed

+442
-178
lines changed

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+
format: e.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+
format: e.format.clone(),
615617
plan,
616618
stringified_plans,
617619
schema: Arc::clone(&e.schema),

datafusion/core/src/physical_planner.rs

Lines changed: 242 additions & 154 deletions
Large diffs are not rendered by default.

datafusion/expr/src/logical_plan/builder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,6 +1211,7 @@ impl LogicalPlanBuilder {
12111211
Ok(Self::new(LogicalPlan::Explain(Explain {
12121212
verbose,
12131213
plan: self.plan,
1214+
format: None,
12141215
stringified_plans,
12151216
schema,
12161217
logical_optimization_succeeded: false,

datafusion/expr/src/logical_plan/plan.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,7 @@ impl LogicalPlan {
10841084
Ok(LogicalPlan::Explain(Explain {
10851085
verbose: e.verbose,
10861086
plan: Arc::new(input),
1087+
format: e.format.clone(),
10871088
stringified_plans: e.stringified_plans.clone(),
10881089
schema: Arc::clone(&e.schema),
10891090
logical_optimization_succeeded: e.logical_optimization_succeeded,
@@ -2930,6 +2931,9 @@ impl PartialOrd for DescribeTable {
29302931
pub struct Explain {
29312932
/// Should extra (detailed, intermediate plans) be included?
29322933
pub verbose: bool,
2934+
/// Output format for explain, if specified.
2935+
/// If none, defaults to `text`
2936+
pub format: Option<String>,
29332937
/// The logical plan that is being EXPLAIN'd
29342938
pub plan: Arc<LogicalPlan>,
29352939
/// Represent the various stages plans have gone through

datafusion/expr/src/logical_plan/tree_node.rs

Lines changed: 2 additions & 0 deletions
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+
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+
format,
212214
plan,
213215
stringified_plans,
214216
schema,

datafusion/physical-plan/src/display.rs

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@
1919
//! [`crate::displayable`] for examples of how to format
2020
2121
use std::collections::{BTreeMap, HashMap};
22+
use std::fmt;
2223
use std::fmt::Formatter;
23-
use std::{fmt, str::FromStr};
2424

2525
use arrow::datatypes::SchemaRef;
2626

2727
use datafusion_common::display::{GraphvizBuilder, PlanType, StringifiedPlan};
28-
use datafusion_common::DataFusionError;
2928
use datafusion_expr::display_schema;
3029
use datafusion_physical_expr::LexOrdering;
3130

@@ -39,7 +38,7 @@ pub enum DisplayFormatType {
3938
/// Default, compact format. Example: `FilterExec: c12 < 10.0`
4039
///
4140
/// This format is designed to provide a detailed textual description
42-
/// of all rele
41+
/// of all parts of the plan.
4342
Default,
4443
/// Verbose, showing all available details.
4544
///
@@ -79,21 +78,6 @@ pub enum DisplayFormatType {
7978
TreeRender,
8079
}
8180

82-
impl FromStr for DisplayFormatType {
83-
type Err = DataFusionError;
84-
85-
fn from_str(s: &str) -> Result<Self, Self::Err> {
86-
match s.to_lowercase().as_str() {
87-
"indent" => Ok(Self::Default),
88-
"tree" => Ok(Self::TreeRender),
89-
_ => Err(DataFusionError::Configuration(format!(
90-
"Invalid explain format: {}",
91-
s
92-
))),
93-
}
94-
}
95-
}
96-
9781
/// Wraps an `ExecutionPlan` with various methods for formatting
9882
///
9983
///

datafusion/sql/src/parser.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
// under the License.
1717

1818
//! [`DFParser`]: DataFusion SQL Parser based on [`sqlparser`]
19+
//!
20+
//! This parser implements DataFusion specific statements such as
21+
//! `CREATE EXTERNAL TABLE`
1922
2023
use std::collections::VecDeque;
2124
use std::fmt;
@@ -43,12 +46,23 @@ fn parse_file_type(s: &str) -> Result<String, ParserError> {
4346
Ok(s.to_uppercase())
4447
}
4548

46-
/// DataFusion specific EXPLAIN (needed so we can EXPLAIN datafusion
47-
/// specific COPY and other statements)
49+
/// DataFusion specific `EXPLAIN`
50+
///
51+
/// Syntax:
52+
/// ```sql
53+
/// EXPLAIN <ANALYZE> <VERBOSE> [FORMAT format] statement
54+
///```
4855
#[derive(Debug, Clone, PartialEq, Eq)]
4956
pub struct ExplainStatement {
57+
/// `EXPLAIN ANALYZE ..`
5058
pub analyze: bool,
59+
/// `EXPLAIN .. VERBOSE ..`
5160
pub verbose: bool,
61+
/// `EXPLAIN .. FORMAT `
62+
pub format: Option<String>,
63+
/// The statement to analyze. Note this is a DataFusion [`Statement`] (not a
64+
/// [`sqlparser::ast::Statement`] so that we can `EXPLAIN` `COPY` and other
65+
/// DataFusion specific statements
5266
pub statement: Box<Statement>,
5367
}
5468

@@ -57,6 +71,7 @@ impl fmt::Display for ExplainStatement {
5771
let Self {
5872
analyze,
5973
verbose,
74+
format,
6075
statement,
6176
} = self;
6277

@@ -67,6 +82,9 @@ impl fmt::Display for ExplainStatement {
6782
if *verbose {
6883
write!(f, "VERBOSE ")?;
6984
}
85+
if let Some(format) = format.as_ref() {
86+
write!(f, "FORMAT {format} ")?;
87+
}
7088

7189
write!(f, "{statement}")
7290
}
@@ -446,7 +464,6 @@ impl<'a> DFParser<'a> {
446464
self.parse_copy()
447465
}
448466
Keyword::EXPLAIN => {
449-
// (TODO parse all supported statements)
450467
self.parser.next_token(); // EXPLAIN
451468
self.parse_explain()
452469
}
@@ -620,15 +637,35 @@ impl<'a> DFParser<'a> {
620637
pub fn parse_explain(&mut self) -> Result<Statement, ParserError> {
621638
let analyze = self.parser.parse_keyword(Keyword::ANALYZE);
622639
let verbose = self.parser.parse_keyword(Keyword::VERBOSE);
640+
let format = self.parse_explain_format()?;
641+
623642
let statement = self.parse_statement()?;
624643

625644
Ok(Statement::Explain(ExplainStatement {
626645
statement: Box::new(statement),
627646
analyze,
628647
verbose,
648+
format,
629649
}))
630650
}
631651

652+
pub fn parse_explain_format(&mut self) -> Result<Option<String>, ParserError> {
653+
if !self.parser.parse_keyword(Keyword::FORMAT) {
654+
return Ok(None);
655+
}
656+
657+
let next_token = self.parser.next_token();
658+
let format = match next_token.token {
659+
Token::Word(w) => Ok(w.value),
660+
Token::SingleQuotedString(w) => Ok(w),
661+
Token::DoubleQuotedString(w) => Ok(w),
662+
_ => self
663+
.parser
664+
.expected("an explain format like JSON or TREE", next_token),
665+
}?;
666+
Ok(Some(format))
667+
}
668+
632669
/// Parse a SQL `CREATE` statement handling `CREATE EXTERNAL TABLE`
633670
pub fn parse_create(&mut self) -> Result<Statement, ParserError> {
634671
if self.parser.parse_keyword(Keyword::EXTERNAL) {
@@ -1543,6 +1580,7 @@ mod tests {
15431580
let expected = Statement::Explain(ExplainStatement {
15441581
analyze,
15451582
verbose,
1583+
format: None,
15461584
statement: Box::new(expected_copy),
15471585
});
15481586
assert_eq!(verified_stmt(sql), expected);

datafusion/sql/src/statement.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,9 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
177177
DFStatement::Explain(ExplainStatement {
178178
verbose,
179179
analyze,
180+
format,
180181
statement,
181-
}) => self.explain_to_plan(verbose, analyze, *statement),
182+
}) => self.explain_to_plan(verbose, analyze, format, *statement),
182183
}
183184
}
184185

@@ -214,11 +215,13 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
214215
verbose,
215216
statement,
216217
analyze,
217-
format: _,
218+
format,
218219
describe_alias: _,
219220
..
220221
} => {
221-
self.explain_to_plan(verbose, analyze, DFStatement::Statement(statement))
222+
let format = format.map(|format| format.to_string());
223+
let statement = DFStatement::Statement(statement);
224+
self.explain_to_plan(verbose, analyze, format, statement)
222225
}
223226
Statement::Query(query) => self.query_to_plan(*query, planner_context),
224227
Statement::ShowVariable { variable } => self.show_variable_to_plan(&variable),
@@ -1564,17 +1567,26 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
15641567
&self,
15651568
verbose: bool,
15661569
analyze: bool,
1570+
format: Option<String>,
15671571
statement: DFStatement,
15681572
) -> Result<LogicalPlan> {
15691573
let plan = self.statement_to_plan(statement)?;
15701574
if matches!(plan, LogicalPlan::Explain(_)) {
15711575
return plan_err!("Nested EXPLAINs are not supported");
15721576
}
1577+
15731578
let plan = Arc::new(plan);
15741579
let schema = LogicalPlan::explain_schema();
15751580
let schema = schema.to_dfschema_ref()?;
15761581

1582+
if verbose && format.is_some() {
1583+
return plan_err!("EXPLAIN VERBOSE with FORMAT is not supported");
1584+
}
1585+
15771586
if analyze {
1587+
if format.is_some() {
1588+
return plan_err!("EXPLAIN ANALYZE with FORMAT is not supported");
1589+
}
15781590
Ok(LogicalPlan::Analyze(Analyze {
15791591
verbose,
15801592
input: plan,
@@ -1585,6 +1597,7 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
15851597
vec![plan.to_stringified(PlanType::InitialLogicalPlan)];
15861598
Ok(LogicalPlan::Explain(Explain {
15871599
verbose,
1600+
format,
15881601
plan,
15891602
stringified_plans,
15901603
schema,

0 commit comments

Comments
 (0)