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}