This is an automated email from the ASF dual-hosted git repository.
alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-datafusion.git
The following commit(s) were added to refs/heads/main by this push:
new ee42e8e6c3 feat: Add graphviz display format for execution plan.
(#6726)
ee42e8e6c3 is described below
commit ee42e8e6c3d6c6c23bdce8f6c367020b7873bff2
Author: Renjie Liu <[email protected]>
AuthorDate: Fri Jul 7 21:02:16 2023 +0800
feat: Add graphviz display format for execution plan. (#6726)
* Implement graphviz format for execution plan
* Update cargo.lock
* fix ci
* fix test
* Fix comment
* Resolve conflicts with main
---
datafusion-cli/Cargo.lock | 32 +++---
datafusion/common/src/display/graphviz.rs | 105 +++++++++++++++++
.../common/src/{display.rs => display/mod.rs} | 3 +
datafusion/core/src/physical_plan/display.rs | 127 +++++++++++++++++++++
datafusion/core/src/physical_plan/streaming.rs | 3 +-
datafusion/core/src/physical_planner.rs | 30 +++++
datafusion/core/tests/sql/explain_analyze.rs | 16 ++-
datafusion/expr/src/logical_plan/display.rs | 58 +++-------
datafusion/expr/src/logical_plan/plan.rs | 72 +++++++-----
9 files changed, 350 insertions(+), 96 deletions(-)
diff --git a/datafusion-cli/Cargo.lock b/datafusion-cli/Cargo.lock
index 9265f54e8a..149c6e2c5b 100644
--- a/datafusion-cli/Cargo.lock
+++ b/datafusion-cli/Cargo.lock
@@ -710,9 +710,9 @@ dependencies = [
[[package]]
name = "blake3"
-version = "1.4.0"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "729b71f35bd3fa1a4c86b85d32c8b9069ea7fe14f7a53cfabb65f62d4265b888"
+checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5"
dependencies = [
"arrayref",
"arrayvec",
@@ -947,9 +947,9 @@ dependencies = [
[[package]]
name = "constant_time_eq"
-version = "0.2.6"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6"
+checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
[[package]]
name = "core-foundation"
@@ -2779,9 +2779,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
-version = "0.101.0"
+version = "0.101.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89efed4bd0af2a8de0feb22ba38030244c93db56112b8aa67d27022286852b1c"
+checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e"
dependencies = [
"ring",
"untrusted",
@@ -2893,18 +2893,18 @@ checksum =
"63134939175b3131fe4d2c131b103fd42f25ccca89423d43b5e4f267920ccf03"
[[package]]
name = "serde"
-version = "1.0.166"
+version = "1.0.167"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8"
+checksum = "7daf513456463b42aa1d94cff7e0c24d682b429f020b9afa4f5ba5c40a22b237"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.166"
+version = "1.0.167"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6"
+checksum = "b69b106b68bc8054f0e974e70d19984040f8a5cf9215ca82626ea4853f82c4b9"
dependencies = [
"proc-macro2",
"quote",
@@ -2962,9 +2962,9 @@ dependencies = [
[[package]]
name = "smallvec"
-version = "1.10.0"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
[[package]]
name = "snafu"
@@ -3155,18 +3155,18 @@ checksum =
"222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]]
name = "thiserror"
-version = "1.0.41"
+version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c16a64ba9387ef3fdae4f9c1a7f07a0997fce91985c0336f1ddc1822b3b37802"
+checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.41"
+version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d14928354b01c4d6a4f0e549069adef399a284e7995c7ccca94e8a07a5346c59"
+checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
dependencies = [
"proc-macro2",
"quote",
diff --git a/datafusion/common/src/display/graphviz.rs
b/datafusion/common/src/display/graphviz.rs
new file mode 100644
index 0000000000..f84490cd3e
--- /dev/null
+++ b/datafusion/common/src/display/graphviz.rs
@@ -0,0 +1,105 @@
+// 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.
+
+//! Logic related to creating DOT language graphs.
+
+use std::fmt;
+
+#[derive(Default)]
+pub struct GraphvizBuilder {
+ id_gen: usize,
+}
+
+impl GraphvizBuilder {
+ // Generate next id in graphviz.
+ pub fn next_id(&mut self) -> usize {
+ self.id_gen += 1;
+ self.id_gen
+ }
+
+ // Write out the start of whole graph.
+ pub fn start_graph(&mut self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(
+ f,
+ r#"
+// Begin DataFusion GraphViz Plan,
+// display it online here: https://dreampuf.github.io/GraphvizOnline
+"#
+ )?;
+ writeln!(f, "digraph {{")
+ }
+
+ pub fn end_graph(&mut self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, "}}")?;
+ writeln!(f, "// End DataFusion GraphViz Plan")
+ }
+
+ // write out the start of the subgraph cluster
+ pub fn start_cluster(&mut self, f: &mut fmt::Formatter, title: &str) ->
fmt::Result {
+ writeln!(f, " subgraph cluster_{}", self.next_id())?;
+ writeln!(f, " {{")?;
+ writeln!(f, " graph[label={}]", Self::quoted(title))
+ }
+
+ // write out the end of the subgraph cluster
+ pub fn end_cluster(&mut self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, " }}")
+ }
+
+ /// makes a quoted string suitable for inclusion in a graphviz chart
+ pub fn quoted(label: &str) -> String {
+ let label = label.replace('"', "_");
+ format!("\"{label}\"")
+ }
+
+ pub fn add_node(
+ &self,
+ f: &mut fmt::Formatter,
+ id: usize,
+ label: &str,
+ tooltip: Option<&str>,
+ ) -> fmt::Result {
+ if let Some(tooltip) = tooltip {
+ writeln!(
+ f,
+ " {}[shape=box label={}, tooltip={}]",
+ id,
+ GraphvizBuilder::quoted(label),
+ GraphvizBuilder::quoted(tooltip),
+ )
+ } else {
+ writeln!(
+ f,
+ " {}[shape=box label={}]",
+ id,
+ GraphvizBuilder::quoted(label),
+ )
+ }
+ }
+
+ pub fn add_edge(
+ &self,
+ f: &mut fmt::Formatter,
+ from_id: usize,
+ to_id: usize,
+ ) -> fmt::Result {
+ writeln!(
+ f,
+ " {from_id} -> {to_id} [arrowhead=none, arrowtail=normal,
dir=back]"
+ )
+ }
+}
diff --git a/datafusion/common/src/display.rs
b/datafusion/common/src/display/mod.rs
similarity index 99%
rename from datafusion/common/src/display.rs
rename to datafusion/common/src/display/mod.rs
index 79de9bc031..766b37ce28 100644
--- a/datafusion/common/src/display.rs
+++ b/datafusion/common/src/display/mod.rs
@@ -17,6 +17,9 @@
//! Types for plan display
+mod graphviz;
+pub use graphviz::*;
+
use std::{
fmt::{self, Display, Formatter},
sync::Arc,
diff --git a/datafusion/core/src/physical_plan/display.rs
b/datafusion/core/src/physical_plan/display.rs
index 674cbcf247..63b4f0c28c 100644
--- a/datafusion/core/src/physical_plan/display.rs
+++ b/datafusion/core/src/physical_plan/display.rs
@@ -20,10 +20,12 @@
//! format
use std::fmt;
+use std::fmt::Formatter;
use datafusion_common::display::StringifiedPlan;
use super::{accept, ExecutionPlan, ExecutionPlanVisitor};
+use datafusion_common::display::GraphvizBuilder;
/// Options for controlling how each [`ExecutionPlan`] should format itself
#[derive(Debug, Clone, Copy)]
@@ -110,6 +112,49 @@ impl<'a> DisplayableExecutionPlan<'a> {
}
}
+ /// Returns a `format`able structure that produces graphviz format for
execution plan, which can
+ /// be directly visualized
[here](https://dreampuf.github.io/GraphvizOnline).
+ ///
+ /// An example is
+ /// ```dot
+ /// strict digraph dot_plan {
+ // 0[label="ProjectionExec: expr=[id@0 + 2 as employee.id +
Int32(2)]",tooltip=""]
+ // 1[label="EmptyExec: produce_one_row=false",tooltip=""]
+ // 0 -> 1
+ // }
+ /// ```
+ pub fn graphviz(&self) -> impl fmt::Display + 'a {
+ struct Wrapper<'a> {
+ plan: &'a dyn ExecutionPlan,
+ show_metrics: ShowMetrics,
+ }
+ 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,
+ 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,
+ }
+ }
+
/// Return a single-line summary of the root of the plan
/// Example: `ProjectionExec: expr=[a@0 as a]`.
pub fn one_line(&self) -> impl fmt::Display + 'a {
@@ -209,6 +254,88 @@ impl<'a, 'b> ExecutionPlanVisitor for IndentVisitor<'a,
'b> {
}
}
+struct GraphvizVisitor<'a, 'b> {
+ f: &'a mut Formatter<'b>,
+ /// How to format each node
+ t: DisplayFormatType,
+ /// How to show metrics
+ show_metrics: ShowMetrics,
+ graphviz_builder: GraphvizBuilder,
+ /// Used to record parent node ids when visiting a plan.
+ 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 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()
+ }
+ }
+ };
+
+ self.graphviz_builder
+ .add_node(self.f, id, &label, Some(&metrics))?;
+
+ 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)
+ }
+}
+
/// Trait for types which could have additional details when formatted in
`Verbose` mode
pub trait DisplayAs {
/// Format according to `DisplayFormatType`, used when verbose
representation looks
diff --git a/datafusion/core/src/physical_plan/streaming.rs
b/datafusion/core/src/physical_plan/streaming.rs
index 97b244d1ac..f06f61ca1e 100644
--- a/datafusion/core/src/physical_plan/streaming.rs
+++ b/datafusion/core/src/physical_plan/streaming.rs
@@ -28,12 +28,11 @@ use datafusion_common::{DataFusionError, Result,
Statistics};
use datafusion_physical_expr::{LexOrdering, PhysicalSortExpr};
use log::debug;
-use crate::datasource::physical_plan::{OutputOrderingDisplay,
ProjectSchemaDisplay};
use crate::physical_plan::stream::RecordBatchStreamAdapter;
use crate::physical_plan::{ExecutionPlan, Partitioning,
SendableRecordBatchStream};
use datafusion_execution::TaskContext;
-use super::{DisplayAs, DisplayFormatType};
+use super::DisplayAs;
/// A partition that can be converted into a [`SendableRecordBatchStream`]
pub trait PartitionStream: Send + Sync {
diff --git a/datafusion/core/src/physical_planner.rs
b/datafusion/core/src/physical_planner.rs
index 92c7604c37..de34353d59 100644
--- a/datafusion/core/src/physical_planner.rs
+++ b/datafusion/core/src/physical_planner.rs
@@ -2610,4 +2610,34 @@ mod tests {
ctx.read_csv(path, options).await?.into_optimized_plan()?,
))
}
+
+ #[tokio::test]
+ async fn test_display_plan_in_graphviz_format() {
+ let schema = Schema::new(vec![Field::new("id", DataType::Int32,
false)]);
+
+ let logical_plan = scan_empty(Some("employee"), &schema, None)
+ .unwrap()
+ .project(vec![col("id") + lit(2)])
+ .unwrap()
+ .build()
+ .unwrap();
+
+ let plan = plan(&logical_plan).await.unwrap();
+
+ let expected_graph = r###"
+// Begin DataFusion GraphViz Plan,
+// display it online here: https://dreampuf.github.io/GraphvizOnline
+
+digraph {
+ 1[shape=box label="ProjectionExec: expr=[id@0 + 2 as employee.id +
Int32(2)]", tooltip=""]
+ 2[shape=box label="EmptyExec: produce_one_row=false", tooltip=""]
+ 1 -> 2 [arrowhead=none, arrowtail=normal, dir=back]
+}
+// End DataFusion GraphViz Plan
+"###;
+
+ let generated_graph = format!("{}", displayable(&*plan).graphviz());
+
+ assert_eq!(expected_graph, generated_graph);
+ }
}
diff --git a/datafusion/core/tests/sql/explain_analyze.rs
b/datafusion/core/tests/sql/explain_analyze.rs
index e0130cb09c..9125dc0ba7 100644
--- a/datafusion/core/tests/sql/explain_analyze.rs
+++ b/datafusion/core/tests/sql/explain_analyze.rs
@@ -210,7 +210,9 @@ async fn csv_explain_plans() {
//
// verify the grahviz format of the plan
let expected = vec![
- "// Begin DataFusion GraphViz Plan (see https://graphviz.org)",
+ "// Begin DataFusion GraphViz Plan,",
+ "// display it online here: https://dreampuf.github.io/GraphvizOnline",
+ "",
"digraph {",
" subgraph cluster_1",
" {",
@@ -282,7 +284,9 @@ async fn csv_explain_plans() {
//
// verify the grahviz format of the plan
let expected = vec![
- "// Begin DataFusion GraphViz Plan (see https://graphviz.org)",
+ "// Begin DataFusion GraphViz Plan,",
+ "// display it online here: https://dreampuf.github.io/GraphvizOnline",
+ "",
"digraph {",
" subgraph cluster_1",
" {",
@@ -427,7 +431,9 @@ async fn csv_explain_verbose_plans() {
//
// verify the grahviz format of the plan
let expected = vec![
- "// Begin DataFusion GraphViz Plan (see https://graphviz.org)",
+ "// Begin DataFusion GraphViz Plan,",
+ "// display it online here: https://dreampuf.github.io/GraphvizOnline",
+ "",
"digraph {",
" subgraph cluster_1",
" {",
@@ -499,7 +505,9 @@ async fn csv_explain_verbose_plans() {
//
// verify the grahviz format of the plan
let expected = vec![
- "// Begin DataFusion GraphViz Plan (see https://graphviz.org)",
+ "// Begin DataFusion GraphViz Plan,",
+ "// display it online here: https://dreampuf.github.io/GraphvizOnline",
+ "",
"digraph {",
" subgraph cluster_1",
" {",
diff --git a/datafusion/expr/src/logical_plan/display.rs
b/datafusion/expr/src/logical_plan/display.rs
index c82689b2cc..112dbf74db 100644
--- a/datafusion/expr/src/logical_plan/display.rs
+++ b/datafusion/expr/src/logical_plan/display.rs
@@ -18,6 +18,7 @@
use crate::LogicalPlan;
use arrow::datatypes::Schema;
+use datafusion_common::display::GraphvizBuilder;
use datafusion_common::tree_node::{TreeNodeVisitor, VisitRecursion};
use datafusion_common::DataFusionError;
use std::fmt;
@@ -123,37 +124,6 @@ pub fn display_schema(schema: &Schema) -> impl
fmt::Display + '_ {
Wrapper(schema)
}
-/// Logic related to creating DOT language graphs.
-#[derive(Default)]
-struct GraphvizBuilder {
- id_gen: usize,
-}
-
-impl GraphvizBuilder {
- fn next_id(&mut self) -> usize {
- self.id_gen += 1;
- self.id_gen
- }
-
- // write out the start of the subgraph cluster
- fn start_cluster(&mut self, f: &mut fmt::Formatter, title: &str) ->
fmt::Result {
- writeln!(f, " subgraph cluster_{}", self.next_id())?;
- writeln!(f, " {{")?;
- writeln!(f, " graph[label={}]", Self::quoted(title))
- }
-
- // write out the end of the subgraph cluster
- fn end_cluster(&mut self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f, " }}")
- }
-
- /// makes a quoted string suitable for inclusion in a graphviz chart
- fn quoted(label: &str) -> String {
- let label = label.replace('"', "_");
- format!("\"{label}\"")
- }
-}
-
/// Formats plans for graphical display using the `DOT` language. This
/// format can be visualized using software from
/// [`graphviz`](https://graphviz.org/)
@@ -190,6 +160,14 @@ impl<'a, 'b> GraphvizVisitor<'a, 'b> {
pub fn post_visit_plan(&mut self) -> fmt::Result {
self.graphviz_builder.end_cluster(self.f)
}
+
+ pub fn start_graph(&mut self) -> fmt::Result {
+ self.graphviz_builder.start_graph(self.f)
+ }
+
+ pub fn end_graph(&mut self) -> fmt::Result {
+ self.graphviz_builder.end_graph(self.f)
+ }
}
impl<'a, 'b> TreeNodeVisitor for GraphvizVisitor<'a, 'b> {
@@ -213,22 +191,16 @@ impl<'a, 'b> TreeNodeVisitor for GraphvizVisitor<'a, 'b> {
format!("{}", plan.display())
};
- writeln!(
- self.f,
- " {}[shape=box label={}]",
- id,
- GraphvizBuilder::quoted(&label)
- )
- .map_err(|_e| DataFusionError::Internal("Fail to
format".to_string()))?;
+ self.graphviz_builder
+ .add_node(self.f, id, &label, None)
+ .map_err(|_e| DataFusionError::Internal("Fail to
format".to_string()))?;
// Create an edge to our parent node, if any
// parent_id -> id
if let Some(parent_id) = self.parent_ids.last() {
- writeln!(
- self.f,
- " {parent_id} -> {id} [arrowhead=none, arrowtail=normal,
dir=back]"
- )
- .map_err(|_e| DataFusionError::Internal("Fail to
format".to_string()))?;
+ self.graphviz_builder
+ .add_edge(self.f, *parent_id, id)
+ .map_err(|_e| DataFusionError::Internal("Fail to
format".to_string()))?;
}
self.parent_ids.push(id);
diff --git a/datafusion/expr/src/logical_plan/plan.rs
b/datafusion/expr/src/logical_plan/plan.rs
index e058708701..d9bb255733 100644
--- a/datafusion/expr/src/logical_plan/plan.rs
+++ b/datafusion/expr/src/logical_plan/plan.rs
@@ -898,14 +898,10 @@ impl LogicalPlan {
struct Wrapper<'a>(&'a LogicalPlan);
impl<'a> Display for Wrapper<'a> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- writeln!(
- f,
- "// Begin DataFusion GraphViz Plan (see
https://graphviz.org)"
- )?;
- writeln!(f, "digraph {{")?;
-
let mut visitor = GraphvizVisitor::new(f);
+ visitor.start_graph()?;
+
visitor.pre_visit_plan("LogicalPlan")?;
self.0.visit(&mut visitor).map_err(|_| fmt::Error)?;
visitor.post_visit_plan()?;
@@ -915,8 +911,7 @@ impl LogicalPlan {
self.0.visit(&mut visitor).map_err(|_| fmt::Error)?;
visitor.post_visit_plan()?;
- writeln!(f, "}}")?;
- writeln!(f, "// End DataFusion GraphViz Plan")?;
+ visitor.end_graph()?;
Ok(())
}
}
@@ -1850,31 +1845,46 @@ mod tests {
fn test_display_graphviz() -> Result<()> {
let plan = display_plan()?;
+ let expected_graphviz = r###"
+// Begin DataFusion GraphViz Plan,
+// display it online here: https://dreampuf.github.io/GraphvizOnline
+
+digraph {
+ subgraph cluster_1
+ {
+ graph[label="LogicalPlan"]
+ 2[shape=box label="Projection: employee_csv.id"]
+ 3[shape=box label="Filter: employee_csv.state IN (<subquery>)"]
+ 2 -> 3 [arrowhead=none, arrowtail=normal, dir=back]
+ 4[shape=box label="Subquery:"]
+ 3 -> 4 [arrowhead=none, arrowtail=normal, dir=back]
+ 5[shape=box label="TableScan: employee_csv projection=[state]"]
+ 4 -> 5 [arrowhead=none, arrowtail=normal, dir=back]
+ 6[shape=box label="TableScan: employee_csv projection=[id, state]"]
+ 3 -> 6 [arrowhead=none, arrowtail=normal, dir=back]
+ }
+ subgraph cluster_7
+ {
+ graph[label="Detailed LogicalPlan"]
+ 8[shape=box label="Projection: employee_csv.id\nSchema: [id:Int32]"]
+ 9[shape=box label="Filter: employee_csv.state IN (<subquery>)\nSchema:
[id:Int32, state:Utf8]"]
+ 8 -> 9 [arrowhead=none, arrowtail=normal, dir=back]
+ 10[shape=box label="Subquery:\nSchema: [state:Utf8]"]
+ 9 -> 10 [arrowhead=none, arrowtail=normal, dir=back]
+ 11[shape=box label="TableScan: employee_csv projection=[state]\nSchema:
[state:Utf8]"]
+ 10 -> 11 [arrowhead=none, arrowtail=normal, dir=back]
+ 12[shape=box label="TableScan: employee_csv projection=[id,
state]\nSchema: [id:Int32, state:Utf8]"]
+ 9 -> 12 [arrowhead=none, arrowtail=normal, dir=back]
+ }
+}
+// End DataFusion GraphViz Plan
+"###;
+
// just test for a few key lines in the output rather than the
// whole thing to make test mainteance easier.
let graphviz = format!("{}", plan.display_graphviz());
- assert!(
- graphviz.contains(
- r#"// Begin DataFusion GraphViz Plan (see
https://graphviz.org)"#
- ),
- "\n{}",
- plan.display_graphviz()
- );
- assert!(
- graphviz.contains(
- r#"[shape=box label="TableScan: employee_csv projection=[id,
state]"]"#
- ),
- "\n{}",
- plan.display_graphviz()
- );
- assert!(graphviz.contains(r#"[shape=box label="TableScan: employee_csv
projection=[id, state]\nSchema: [id:Int32, state:Utf8]"]"#),
- "\n{}", plan.display_graphviz());
- assert!(
- graphviz.contains(r#"// End DataFusion GraphViz Plan"#),
- "\n{}",
- plan.display_graphviz()
- );
+ assert_eq!(expected_graphviz, graphviz);
Ok(())
}
@@ -1895,7 +1905,7 @@ mod tests {
_ => {
return Err(DataFusionError::NotImplemented(
"unknown plan type".to_string(),
- ))
+ ));
}
};
@@ -1911,7 +1921,7 @@ mod tests {
_ => {
return Err(DataFusionError::NotImplemented(
"unknown plan type".to_string(),
- ))
+ ));
}
};