use std::fmt;
use arrow_schema::SchemaRef;
use datafusion_common::display::StringifiedPlan;
use datafusion_physical_expr::PhysicalSortExpr;
use super::{accept, ExecutionPlan, ExecutionPlanVisitor};
use datafusion_common::display::GraphvizBuilder;
#[derive(Debug, Clone, Copy)]
pub enum DisplayFormatType {
Default,
Verbose,
}
pub struct DisplayableExecutionPlan<'a> {
inner: &'a dyn ExecutionPlan,
show_metrics: ShowMetrics,
show_statistics: bool,
}
impl<'a> DisplayableExecutionPlan<'a> {
pub fn new(inner: &'a dyn ExecutionPlan) -> Self {
Self {
inner,
show_metrics: ShowMetrics::None,
show_statistics: false,
}
}
pub fn with_metrics(inner: &'a dyn ExecutionPlan) -> Self {
Self {
inner,
show_metrics: ShowMetrics::Aggregated,
show_statistics: false,
}
}
pub fn with_full_metrics(inner: &'a dyn ExecutionPlan) -> Self {
Self {
inner,
show_metrics: ShowMetrics::Full,
show_statistics: false,
}
}
pub fn set_show_statistics(mut self, show_statistics: bool) -> Self {
self.show_statistics = show_statistics;
self
}
pub fn indent(&self, verbose: bool) -> impl fmt::Display + 'a {
let format_type = if verbose {
DisplayFormatType::Verbose
} else {
DisplayFormatType::Default
};
struct Wrapper<'a> {
format_type: DisplayFormatType,
plan: &'a dyn ExecutionPlan,
show_metrics: ShowMetrics,
show_statistics: bool,
}
impl<'a> fmt::Display for Wrapper<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut visitor = IndentVisitor {
t: self.format_type,
f,
indent: 0,
show_metrics: self.show_metrics,
show_statistics: self.show_statistics,
};
accept(self.plan, &mut visitor)
}
}
Wrapper {
format_type,
plan: self.inner,
show_metrics: self.show_metrics,
show_statistics: self.show_statistics,
}
}
pub fn graphviz(&self) -> impl fmt::Display + 'a {
struct Wrapper<'a> {
plan: &'a dyn ExecutionPlan,
show_metrics: ShowMetrics,
show_statistics: bool,
}
impl<'a> fmt::Display for Wrapper<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let t = DisplayFormatType::Default;
let mut visitor = GraphvizVisitor {
f,
t,
show_metrics: self.show_metrics,
show_statistics: self.show_statistics,
graphviz_builder: GraphvizBuilder::default(),
parents: Vec::new(),
};
visitor.start_graph()?;
accept(self.plan, &mut visitor)?;
visitor.end_graph()?;
Ok(())
}
}
Wrapper {
plan: self.inner,
show_metrics: self.show_metrics,
show_statistics: self.show_statistics,
}
}
pub fn one_line(&self) -> impl fmt::Display + 'a {
struct Wrapper<'a> {
plan: &'a dyn ExecutionPlan,
show_metrics: ShowMetrics,
show_statistics: bool,
}
impl<'a> fmt::Display for Wrapper<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut visitor = IndentVisitor {
f,
t: DisplayFormatType::Default,
indent: 0,
show_metrics: self.show_metrics,
show_statistics: self.show_statistics,
};
visitor.pre_visit(self.plan)?;
Ok(())
}
}
Wrapper {
plan: self.inner,
show_metrics: self.show_metrics,
show_statistics: self.show_statistics,
}
}
pub fn to_stringified(
&self,
verbose: bool,
plan_type: crate::logical_expr::PlanType,
) -> StringifiedPlan {
StringifiedPlan::new(plan_type, self.indent(verbose).to_string())
}
}
#[derive(Debug, Clone, Copy)]
enum ShowMetrics {
None,
Aggregated,
Full,
}
struct IndentVisitor<'a, 'b> {
t: DisplayFormatType,
f: &'a mut fmt::Formatter<'b>,
indent: usize,
show_metrics: ShowMetrics,
show_statistics: bool,
}
impl<'a, 'b> ExecutionPlanVisitor for IndentVisitor<'a, 'b> {
type Error = fmt::Error;
fn pre_visit(&mut self, plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
write!(self.f, "{:indent$}", "", indent = self.indent * 2)?;
plan.fmt_as(self.t, self.f)?;
match self.show_metrics {
ShowMetrics::None => {}
ShowMetrics::Aggregated => {
if let Some(metrics) = plan.metrics() {
let metrics = metrics
.aggregate_by_name()
.sorted_for_display()
.timestamps_removed();
write!(self.f, ", metrics=[{metrics}]")?;
} else {
write!(self.f, ", metrics=[]")?;
}
}
ShowMetrics::Full => {
if let Some(metrics) = plan.metrics() {
write!(self.f, ", metrics=[{metrics}]")?;
} else {
write!(self.f, ", metrics=[]")?;
}
}
}
if self.show_statistics {
write!(self.f, ", statistics=[{}]", plan.statistics())?;
}
writeln!(self.f)?;
self.indent += 1;
Ok(true)
}
fn post_visit(&mut self, _plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
self.indent -= 1;
Ok(true)
}
}
struct GraphvizVisitor<'a, 'b> {
f: &'a mut fmt::Formatter<'b>,
t: DisplayFormatType,
show_metrics: ShowMetrics,
show_statistics: bool,
graphviz_builder: GraphvizBuilder,
parents: Vec<usize>,
}
impl GraphvizVisitor<'_, '_> {
fn start_graph(&mut self) -> fmt::Result {
self.graphviz_builder.start_graph(self.f)
}
fn end_graph(&mut self) -> fmt::Result {
self.graphviz_builder.end_graph(self.f)
}
}
impl ExecutionPlanVisitor for GraphvizVisitor<'_, '_> {
type Error = fmt::Error;
fn pre_visit(
&mut self,
plan: &dyn ExecutionPlan,
) -> datafusion_common::Result<bool, Self::Error> {
let id = self.graphviz_builder.next_id();
struct Wrapper<'a>(&'a dyn ExecutionPlan, DisplayFormatType);
impl<'a> std::fmt::Display for Wrapper<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt_as(self.1, f)
}
}
let label = { format!("{}", Wrapper(plan, self.t)) };
let metrics = match self.show_metrics {
ShowMetrics::None => "".to_string(),
ShowMetrics::Aggregated => {
if let Some(metrics) = plan.metrics() {
let metrics = metrics
.aggregate_by_name()
.sorted_for_display()
.timestamps_removed();
format!("metrics=[{metrics}]")
} else {
"metrics=[]".to_string()
}
}
ShowMetrics::Full => {
if let Some(metrics) = plan.metrics() {
format!("metrics=[{metrics}]")
} else {
"metrics=[]".to_string()
}
}
};
let statistics = if self.show_statistics {
format!("statistics=[{}]", plan.statistics())
} else {
"".to_string()
};
let delimiter = if !metrics.is_empty() && !statistics.is_empty() {
", "
} else {
""
};
self.graphviz_builder.add_node(
self.f,
id,
&label,
Some(&format!("{}{}{}", metrics, delimiter, statistics)),
)?;
if let Some(parent_node_id) = self.parents.last() {
self.graphviz_builder
.add_edge(self.f, *parent_node_id, id)?;
}
self.parents.push(id);
Ok(true)
}
fn post_visit(&mut self, _plan: &dyn ExecutionPlan) -> Result<bool, Self::Error> {
self.parents.pop();
Ok(true)
}
}
pub trait DisplayAs {
fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result;
}
pub struct DefaultDisplay<T>(pub T);
impl<T: DisplayAs> fmt::Display for DefaultDisplay<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt_as(DisplayFormatType::Default, f)
}
}
pub struct VerboseDisplay<T>(pub T);
impl<T: DisplayAs> fmt::Display for VerboseDisplay<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt_as(DisplayFormatType::Verbose, f)
}
}
#[derive(Debug)]
pub struct ProjectSchemaDisplay<'a>(pub &'a SchemaRef);
impl<'a> fmt::Display for ProjectSchemaDisplay<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let parts: Vec<_> = self
.0
.fields()
.iter()
.map(|x| x.name().to_owned())
.collect::<Vec<String>>();
write!(f, "[{}]", parts.join(", "))
}
}
#[derive(Debug)]
pub struct OutputOrderingDisplay<'a>(pub &'a [PhysicalSortExpr]);
impl<'a> fmt::Display for OutputOrderingDisplay<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[")?;
for (i, e) in self.0.iter().enumerate() {
if i > 0 {
write!(f, ", ")?
}
write!(f, "{e}")?;
}
write!(f, "]")
}
}