datafusion_common/format.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::fmt::{self, Display};
19use std::str::FromStr;
20
21use arrow::compute::CastOptions;
22use arrow::util::display::{DurationFormat, FormatOptions};
23
24use crate::config::{ConfigField, Visit};
25use crate::error::{DataFusionError, Result};
26
27/// The default [`FormatOptions`] to use within DataFusion
28/// Also see [`crate::config::FormatOptions`]
29pub const DEFAULT_FORMAT_OPTIONS: FormatOptions<'static> =
30 FormatOptions::new().with_duration_format(DurationFormat::Pretty);
31
32/// The default [`CastOptions`] to use within DataFusion
33pub const DEFAULT_CAST_OPTIONS: CastOptions<'static> = CastOptions {
34 safe: false,
35 format_options: DEFAULT_FORMAT_OPTIONS,
36};
37
38/// Output formats for controlling for Explain plans
39#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40pub 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`
170impl 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
186impl 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
198impl 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}
208
209/// Verbosity levels controlling how `EXPLAIN ANALYZE` renders metrics
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
211pub enum ExplainAnalyzeLevel {
212 /// Show a compact view containing high-level metrics
213 Summary,
214 /// Show a developer-focused view with per-operator details
215 Dev,
216 // When adding new enum, update the error message in `from_str()` accordingly.
217}
218
219impl FromStr for ExplainAnalyzeLevel {
220 type Err = DataFusionError;
221
222 fn from_str(level: &str) -> Result<Self, Self::Err> {
223 match level.to_lowercase().as_str() {
224 "summary" => Ok(ExplainAnalyzeLevel::Summary),
225 "dev" => Ok(ExplainAnalyzeLevel::Dev),
226 other => Err(DataFusionError::Configuration(format!(
227 "Invalid explain analyze level. Expected 'summary' or 'dev'. Got '{other}'"
228 ))),
229 }
230 }
231}
232
233impl Display for ExplainAnalyzeLevel {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 let s = match self {
236 ExplainAnalyzeLevel::Summary => "summary",
237 ExplainAnalyzeLevel::Dev => "dev",
238 };
239 write!(f, "{s}")
240 }
241}
242
243impl ConfigField for ExplainAnalyzeLevel {
244 fn visit<V: Visit>(&self, v: &mut V, key: &str, description: &'static str) {
245 v.some(key, self, description)
246 }
247
248 fn set(&mut self, _: &str, value: &str) -> Result<()> {
249 *self = ExplainAnalyzeLevel::from_str(value)?;
250 Ok(())
251 }
252}