blob: 764190e1189bfe8335bedb4f9ba1f6e02a9df15a [file]
// 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(())
}
}