Skip to content

Commit e2c2d38

Browse files
authored
feat: Ensure explain format in config is valid (#17549)
* feat: Validate explain format * chore: fmt * chore * fix: Update explain format default value to lowercase
1 parent cad78be commit e2c2d38

File tree

7 files changed

+199
-162
lines changed

7 files changed

+199
-162
lines changed

datafusion-cli/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use datafusion::execution::memory_pool::{
2828
FairSpillPool, GreedyMemoryPool, MemoryPool, TrackConsumersPool,
2929
};
3030
use datafusion::execution::runtime_env::RuntimeEnvBuilder;
31+
use datafusion::logical_expr::ExplainFormat;
3132
use datafusion::prelude::SessionContext;
3233
use datafusion_cli::catalog::DynamicObjectStoreCatalog;
3334
use datafusion_cli::functions::{MetadataCacheFunc, ParquetMetadataFunc};
@@ -288,7 +289,7 @@ fn get_session_config(args: &Args) -> Result<SessionConfig> {
288289
// use easier to understand "tree" mode by default
289290
// if the user hasn't specified an explain format in the environment
290291
if env::var_os("DATAFUSION_EXPLAIN_FORMAT").is_none() {
291-
config_options.explain.format = String::from("tree");
292+
config_options.explain.format = ExplainFormat::Tree;
292293
}
293294

294295
// in the CLI, we want to show NULL values rather the empty strings

datafusion/common/src/config.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use arrow_ipc::CompressionType;
2222
#[cfg(feature = "parquet_encryption")]
2323
use crate::encryption::{FileDecryptionProperties, FileEncryptionProperties};
2424
use crate::error::_config_err;
25+
use crate::format::ExplainFormat;
2526
use crate::parsers::CompressionTypeVariant;
2627
use crate::utils::get_available_parallelism;
2728
use crate::{DataFusionError, Result};
@@ -879,7 +880,7 @@ config_namespace! {
879880

880881
/// Display format of explain. Default is "indent".
881882
/// When set to "tree", it will print the plan in a tree-rendered format.
882-
pub format: String, default = "indent".to_string()
883+
pub format: ExplainFormat, default = ExplainFormat::Indent
883884

884885
/// (format=tree only) Maximum total width of the rendered tree.
885886
/// When set to 0, the tree will have no width limit.

datafusion/common/src/format.rs

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18+
use std::fmt::{self, Display};
19+
use std::str::FromStr;
20+
1821
use arrow::compute::CastOptions;
1922
use arrow::util::display::{DurationFormat, FormatOptions};
2023

24+
use crate::config::{ConfigField, Visit};
25+
use crate::error::{DataFusionError, Result};
26+
2127
/// The default [`FormatOptions`] to use within DataFusion
2228
/// Also see [`crate::config::FormatOptions`]
2329
pub const DEFAULT_FORMAT_OPTIONS: FormatOptions<'static> =
@@ -28,3 +34,174 @@ pub const DEFAULT_CAST_OPTIONS: CastOptions<'static> = CastOptions {
2834
safe: false,
2935
format_options: DEFAULT_FORMAT_OPTIONS,
3036
};
37+
38+
/// Output formats for controlling for Explain plans
39+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40+
pub enum ExplainFormat {
41+
/// Indent mode
42+
///
43+
/// Example:
44+
/// ```text
45+
/// > explain format indent select x from values (1) t(x);
46+
/// +---------------+-----------------------------------------------------+
47+
/// | plan_type | plan |
48+
/// +---------------+-----------------------------------------------------+
49+
/// | logical_plan | SubqueryAlias: t |
50+
/// | | Projection: column1 AS x |
51+
/// | | Values: (Int64(1)) |
52+
/// | physical_plan | ProjectionExec: expr=[column1@0 as x] |
53+
/// | | DataSourceExec: partitions=1, partition_sizes=[1] |
54+
/// | | |
55+
/// +---------------+-----------------------------------------------------+
56+
/// ```
57+
Indent,
58+
/// Tree mode
59+
///
60+
/// Example:
61+
/// ```text
62+
/// > explain format tree select x from values (1) t(x);
63+
/// +---------------+-------------------------------+
64+
/// | plan_type | plan |
65+
/// +---------------+-------------------------------+
66+
/// | physical_plan | ┌───────────────────────────┐ |
67+
/// | | │ ProjectionExec │ |
68+
/// | | │ -------------------- │ |
69+
/// | | │ x: column1@0 │ |
70+
/// | | └─────────────┬─────────────┘ |
71+
/// | | ┌─────────────┴─────────────┐ |
72+
/// | | │ DataSourceExec │ |
73+
/// | | │ -------------------- │ |
74+
/// | | │ bytes: 128 │ |
75+
/// | | │ format: memory │ |
76+
/// | | │ rows: 1 │ |
77+
/// | | └───────────────────────────┘ |
78+
/// | | |
79+
/// +---------------+-------------------------------+
80+
/// ```
81+
Tree,
82+
/// Postgres Json mode
83+
///
84+
/// A displayable structure that produces plan in postgresql JSON format.
85+
///
86+
/// Users can use this format to visualize the plan in existing plan
87+
/// visualization tools, for example [dalibo](https://explain.dalibo.com/)
88+
///
89+
/// Example:
90+
/// ```text
91+
/// > explain format pgjson select x from values (1) t(x);
92+
/// +--------------+--------------------------------------+
93+
/// | plan_type | plan |
94+
/// +--------------+--------------------------------------+
95+
/// | logical_plan | [ |
96+
/// | | { |
97+
/// | | "Plan": { |
98+
/// | | "Alias": "t", |
99+
/// | | "Node Type": "Subquery", |
100+
/// | | "Output": [ |
101+
/// | | "x" |
102+
/// | | ], |
103+
/// | | "Plans": [ |
104+
/// | | { |
105+
/// | | "Expressions": [ |
106+
/// | | "column1 AS x" |
107+
/// | | ], |
108+
/// | | "Node Type": "Projection", |
109+
/// | | "Output": [ |
110+
/// | | "x" |
111+
/// | | ], |
112+
/// | | "Plans": [ |
113+
/// | | { |
114+
/// | | "Node Type": "Values", |
115+
/// | | "Output": [ |
116+
/// | | "column1" |
117+
/// | | ], |
118+
/// | | "Plans": [], |
119+
/// | | "Values": "(Int64(1))" |
120+
/// | | } |
121+
/// | | ] |
122+
/// | | } |
123+
/// | | ] |
124+
/// | | } |
125+
/// | | } |
126+
/// | | ] |
127+
/// +--------------+--------------------------------------+
128+
/// ```
129+
PostgresJSON,
130+
/// Graphviz mode
131+
///
132+
/// Example:
133+
/// ```text
134+
/// > explain format graphviz select x from values (1) t(x);
135+
/// +--------------+------------------------------------------------------------------------+
136+
/// | plan_type | plan |
137+
/// +--------------+------------------------------------------------------------------------+
138+
/// | logical_plan | |
139+
/// | | // Begin DataFusion GraphViz Plan, |
140+
/// | | // display it online here: https://dreampuf.github.io/GraphvizOnline |
141+
/// | | |
142+
/// | | digraph { |
143+
/// | | subgraph cluster_1 |
144+
/// | | { |
145+
/// | | graph[label="LogicalPlan"] |
146+
/// | | 2[shape=box label="SubqueryAlias: t"] |
147+
/// | | 3[shape=box label="Projection: column1 AS x"] |
148+
/// | | 2 -> 3 [arrowhead=none, arrowtail=normal, dir=back] |
149+
/// | | 4[shape=box label="Values: (Int64(1))"] |
150+
/// | | 3 -> 4 [arrowhead=none, arrowtail=normal, dir=back] |
151+
/// | | } |
152+
/// | | subgraph cluster_5 |
153+
/// | | { |
154+
/// | | graph[label="Detailed LogicalPlan"] |
155+
/// | | 6[shape=box label="SubqueryAlias: t\nSchema: [x:Int64;N]"] |
156+
/// | | 7[shape=box label="Projection: column1 AS x\nSchema: [x:Int64;N]"] |
157+
/// | | 6 -> 7 [arrowhead=none, arrowtail=normal, dir=back] |
158+
/// | | 8[shape=box label="Values: (Int64(1))\nSchema: [column1:Int64;N]"] |
159+
/// | | 7 -> 8 [arrowhead=none, arrowtail=normal, dir=back] |
160+
/// | | } |
161+
/// | | } |
162+
/// | | // End DataFusion GraphViz Plan |
163+
/// | | |
164+
/// +--------------+------------------------------------------------------------------------+
165+
/// ```
166+
Graphviz,
167+
}
168+
169+
/// Implement parsing strings to `ExplainFormat`
170+
impl FromStr for ExplainFormat {
171+
type Err = DataFusionError;
172+
173+
fn from_str(format: &str) -> Result<Self, Self::Err> {
174+
match format.to_lowercase().as_str() {
175+
"indent" => Ok(ExplainFormat::Indent),
176+
"tree" => Ok(ExplainFormat::Tree),
177+
"pgjson" => Ok(ExplainFormat::PostgresJSON),
178+
"graphviz" => Ok(ExplainFormat::Graphviz),
179+
_ => {
180+
Err(DataFusionError::Configuration(format!("Invalid explain format. Expected 'indent', 'tree', 'pgjson' or 'graphviz'. Got '{format}'")))
181+
}
182+
}
183+
}
184+
}
185+
186+
impl Display for ExplainFormat {
187+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188+
let s = match self {
189+
ExplainFormat::Indent => "indent",
190+
ExplainFormat::Tree => "tree",
191+
ExplainFormat::PostgresJSON => "pgjson",
192+
ExplainFormat::Graphviz => "graphviz",
193+
};
194+
write!(f, "{s}")
195+
}
196+
}
197+
198+
impl ConfigField for ExplainFormat {
199+
fn visit<V: Visit>(&self, v: &mut V, key: &str, description: &'static str) {
200+
v.some(key, self, description)
201+
}
202+
203+
fn set(&mut self, _: &str, value: &str) -> Result<()> {
204+
*self = ExplainFormat::from_str(value)?;
205+
Ok(())
206+
}
207+
}

datafusion/expr/src/logical_plan/mod.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,18 @@ pub use ddl::{
3939
pub use dml::{DmlStatement, WriteOp};
4040
pub use plan::{
4141
projection_schema, Aggregate, Analyze, ColumnUnnestList, DescribeTable, Distinct,
42-
DistinctOn, EmptyRelation, Explain, ExplainFormat, ExplainOption, Extension,
43-
FetchType, Filter, Join, JoinConstraint, JoinType, Limit, LogicalPlan, Partitioning,
44-
PlanType, Projection, RecursiveQuery, Repartition, SkipType, Sort, StringifiedPlan,
45-
Subquery, SubqueryAlias, TableScan, ToStringifiedPlan, Union, Unnest, Values, Window,
42+
DistinctOn, EmptyRelation, Explain, ExplainOption, Extension, FetchType, Filter,
43+
Join, JoinConstraint, JoinType, Limit, LogicalPlan, Partitioning, PlanType,
44+
Projection, RecursiveQuery, Repartition, SkipType, Sort, StringifiedPlan, Subquery,
45+
SubqueryAlias, TableScan, ToStringifiedPlan, Union, Unnest, Values, Window,
4646
};
4747
pub use statement::{
4848
Deallocate, Execute, Prepare, SetVariable, Statement, TransactionAccessMode,
4949
TransactionConclusion, TransactionEnd, TransactionIsolationLevel, TransactionStart,
5050
};
5151

52+
pub use datafusion_common::format::ExplainFormat;
53+
5254
pub use display::display_schema;
5355

5456
pub use extension::{UserDefinedLogicalNode, UserDefinedLogicalNodeCore};

0 commit comments

Comments
 (0)