| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| use std::fmt::{self, Display}; |
| use std::str::FromStr; |
| |
| use arrow::compute::CastOptions; |
| use arrow::util::display::{DurationFormat, FormatOptions}; |
| |
| use crate::config::{ConfigField, Visit}; |
| use crate::error::{DataFusionError, Result}; |
| |
| /// The default [`FormatOptions`] to use within DataFusion |
| /// Also see [`crate::config::FormatOptions`] |
| pub const DEFAULT_FORMAT_OPTIONS: FormatOptions<'static> = |
| FormatOptions::new().with_duration_format(DurationFormat::Pretty); |
| |
| /// The default [`CastOptions`] to use within DataFusion |
| pub const DEFAULT_CAST_OPTIONS: CastOptions<'static> = CastOptions { |
| safe: false, |
| format_options: DEFAULT_FORMAT_OPTIONS, |
| }; |
| |
| /// Output formats for controlling for Explain plans |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub enum ExplainFormat { |
| /// Indent mode |
| /// |
| /// Example: |
| /// ```text |
| /// > explain format indent select x from values (1) t(x); |
| /// +---------------+-----------------------------------------------------+ |
| /// | plan_type | plan | |
| /// +---------------+-----------------------------------------------------+ |
| /// | logical_plan | SubqueryAlias: t | |
| /// | | Projection: column1 AS x | |
| /// | | Values: (Int64(1)) | |
| /// | physical_plan | ProjectionExec: expr=[column1@0 as x] | |
| /// | | DataSourceExec: partitions=1, partition_sizes=[1] | |
| /// | | | |
| /// +---------------+-----------------------------------------------------+ |
| /// ``` |
| Indent, |
| /// Tree mode |
| /// |
| /// Example: |
| /// ```text |
| /// > explain format tree select x from values (1) t(x); |
| /// +---------------+-------------------------------+ |
| /// | plan_type | plan | |
| /// +---------------+-------------------------------+ |
| /// | physical_plan | ┌───────────────────────────┐ | |
| /// | | │ ProjectionExec │ | |
| /// | | │ -------------------- │ | |
| /// | | │ x: column1@0 │ | |
| /// | | └─────────────┬─────────────┘ | |
| /// | | ┌─────────────┴─────────────┐ | |
| /// | | │ DataSourceExec │ | |
| /// | | │ -------------------- │ | |
| /// | | │ bytes: 128 │ | |
| /// | | │ format: memory │ | |
| /// | | │ rows: 1 │ | |
| /// | | └───────────────────────────┘ | |
| /// | | | |
| /// +---------------+-------------------------------+ |
| /// ``` |
| Tree, |
| /// Postgres Json mode |
| /// |
| /// A displayable structure that produces plan in postgresql JSON format. |
| /// |
| /// Users can use this format to visualize the plan in existing plan |
| /// visualization tools, for example [dalibo](https://explain.dalibo.com/) |
| /// |
| /// Example: |
| /// ```text |
| /// > explain format pgjson select x from values (1) t(x); |
| /// +--------------+--------------------------------------+ |
| /// | plan_type | plan | |
| /// +--------------+--------------------------------------+ |
| /// | logical_plan | [ | |
| /// | | { | |
| /// | | "Plan": { | |
| /// | | "Alias": "t", | |
| /// | | "Node Type": "Subquery", | |
| /// | | "Output": [ | |
| /// | | "x" | |
| /// | | ], | |
| /// | | "Plans": [ | |
| /// | | { | |
| /// | | "Expressions": [ | |
| /// | | "column1 AS x" | |
| /// | | ], | |
| /// | | "Node Type": "Projection", | |
| /// | | "Output": [ | |
| /// | | "x" | |
| /// | | ], | |
| /// | | "Plans": [ | |
| /// | | { | |
| /// | | "Node Type": "Values", | |
| /// | | "Output": [ | |
| /// | | "column1" | |
| /// | | ], | |
| /// | | "Plans": [], | |
| /// | | "Values": "(Int64(1))" | |
| /// | | } | |
| /// | | ] | |
| /// | | } | |
| /// | | ] | |
| /// | | } | |
| /// | | } | |
| /// | | ] | |
| /// +--------------+--------------------------------------+ |
| /// ``` |
| PostgresJSON, |
| /// Graphviz mode |
| /// |
| /// Example: |
| /// ```text |
| /// > explain format graphviz select x from values (1) t(x); |
| /// +--------------+------------------------------------------------------------------------+ |
| /// | plan_type | plan | |
| /// +--------------+------------------------------------------------------------------------+ |
| /// | logical_plan | | |
| /// | | // Begin DataFusion GraphViz Plan, | |
| /// | | // display it online here: https://dreampuf.github.io/GraphvizOnline | |
| /// | | | |
| /// | | digraph { | |
| /// | | subgraph cluster_1 | |
| /// | | { | |
| /// | | graph[label="LogicalPlan"] | |
| /// | | 2[shape=box label="SubqueryAlias: t"] | |
| /// | | 3[shape=box label="Projection: column1 AS x"] | |
| /// | | 2 -> 3 [arrowhead=none, arrowtail=normal, dir=back] | |
| /// | | 4[shape=box label="Values: (Int64(1))"] | |
| /// | | 3 -> 4 [arrowhead=none, arrowtail=normal, dir=back] | |
| /// | | } | |
| /// | | subgraph cluster_5 | |
| /// | | { | |
| /// | | graph[label="Detailed LogicalPlan"] | |
| /// | | 6[shape=box label="SubqueryAlias: t\nSchema: [x:Int64;N]"] | |
| /// | | 7[shape=box label="Projection: column1 AS x\nSchema: [x:Int64;N]"] | |
| /// | | 6 -> 7 [arrowhead=none, arrowtail=normal, dir=back] | |
| /// | | 8[shape=box label="Values: (Int64(1))\nSchema: [column1:Int64;N]"] | |
| /// | | 7 -> 8 [arrowhead=none, arrowtail=normal, dir=back] | |
| /// | | } | |
| /// | | } | |
| /// | | // End DataFusion GraphViz Plan | |
| /// | | | |
| /// +--------------+------------------------------------------------------------------------+ |
| /// ``` |
| Graphviz, |
| } |
| |
| /// Implement parsing strings to `ExplainFormat` |
| impl FromStr for ExplainFormat { |
| type Err = DataFusionError; |
| |
| fn from_str(format: &str) -> Result<Self, Self::Err> { |
| match format.to_lowercase().as_str() { |
| "indent" => Ok(ExplainFormat::Indent), |
| "tree" => Ok(ExplainFormat::Tree), |
| "pgjson" => Ok(ExplainFormat::PostgresJSON), |
| "graphviz" => Ok(ExplainFormat::Graphviz), |
| _ => { |
| Err(DataFusionError::Configuration(format!("Invalid explain format. Expected 'indent', 'tree', 'pgjson' or 'graphviz'. Got '{format}'"))) |
| } |
| } |
| } |
| } |
| |
| impl Display for ExplainFormat { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| let s = match self { |
| ExplainFormat::Indent => "indent", |
| ExplainFormat::Tree => "tree", |
| ExplainFormat::PostgresJSON => "pgjson", |
| ExplainFormat::Graphviz => "graphviz", |
| }; |
| write!(f, "{s}") |
| } |
| } |
| |
| impl ConfigField for ExplainFormat { |
| fn visit<V: Visit>(&self, v: &mut V, key: &str, description: &'static str) { |
| v.some(key, self, description) |
| } |
| |
| fn set(&mut self, _: &str, value: &str) -> Result<()> { |
| *self = ExplainFormat::from_str(value)?; |
| Ok(()) |
| } |
| } |
| |
| /// Verbosity levels controlling how `EXPLAIN ANALYZE` renders metrics |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub enum ExplainAnalyzeLevel { |
| /// Show a compact view containing high-level metrics |
| Summary, |
| /// Show a developer-focused view with per-operator details |
| Dev, |
| // When adding new enum, update the error message in `from_str()` accordingly. |
| } |
| |
| impl FromStr for ExplainAnalyzeLevel { |
| type Err = DataFusionError; |
| |
| fn from_str(level: &str) -> Result<Self, Self::Err> { |
| match level.to_lowercase().as_str() { |
| "summary" => Ok(ExplainAnalyzeLevel::Summary), |
| "dev" => Ok(ExplainAnalyzeLevel::Dev), |
| other => Err(DataFusionError::Configuration(format!( |
| "Invalid explain analyze level. Expected 'summary' or 'dev'. Got '{other}'" |
| ))), |
| } |
| } |
| } |
| |
| impl Display for ExplainAnalyzeLevel { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| let s = match self { |
| ExplainAnalyzeLevel::Summary => "summary", |
| ExplainAnalyzeLevel::Dev => "dev", |
| }; |
| write!(f, "{s}") |
| } |
| } |
| |
| impl ConfigField for ExplainAnalyzeLevel { |
| fn visit<V: Visit>(&self, v: &mut V, key: &str, description: &'static str) { |
| v.some(key, self, description) |
| } |
| |
| fn set(&mut self, _: &str, value: &str) -> Result<()> { |
| *self = ExplainAnalyzeLevel::from_str(value)?; |
| Ok(()) |
| } |
| } |