Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions datafusion/physical-plan/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,41 @@ use super::{accept, ExecutionPlan, ExecutionPlanVisitor};
#[derive(Debug, Clone, Copy)]
pub enum DisplayFormatType {
/// Default, compact format. Example: `FilterExec: c12 < 10.0`
///
/// This format is designed to provide a detailed textual description
/// of all rele
Default,
/// Verbose, showing all available details
/// Verbose, showing all available details.
///
/// This form is even more detailed than [`Self::Default`]
Verbose,
/// TreeRender, display plan like a tree.
/// TreeRender, displayed in the `tree` explain type.
///
/// This format is inspired by DuckDB's explain plans. The information
/// presented should be "user friendly", and contain only the most relevant
/// information for understanding a plan. It should NOT contain the same level
/// of detail information as the [`Self::Default`] format.
///
/// In this mode, each line contains a key=value pair.
/// Everything before the first `=` is treated as the key, and everything after the
/// first `=` is treated as the value.
///
/// For example, if the output of `TreeRender` is this:
/// ```text
/// partition_sizes=[1]
/// partitions=1
/// ```
///
/// It is rendered in the center of a box in the following way:
///
/// ```text
/// ┌───────────────────────────┐
/// │ DataSourceExec │
/// │ -------------------- │
/// │ partition_sizes: [1] │
/// │ partitions: 1 │
/// └───────────────────────────┘
/// ```
TreeRender,
}

Expand Down
3 changes: 1 addition & 2 deletions datafusion/physical-plan/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,7 @@ impl DisplayAs for FilterExec {
write!(f, "FilterExec: {}{}", self.predicate, display_projections)
}
DisplayFormatType::TreeRender => {
// TODO: collect info
write!(f, "")
write!(f, "predicate={}", self.predicate)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For anyone else following along, this is what it takes to implement the tree format for FilterExec

The rest of the PR is comments and tests

}
}
}
Expand Down
39 changes: 34 additions & 5 deletions datafusion/sqllogictest/test_files/explain_tree.slt
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,16 @@ physical_plan
03)└─────────────┬─────────────┘
04)┌─────────────┴─────────────┐
05)│ FilterExec │
06)└─────────────┬─────────────┘
07)┌─────────────┴─────────────┐
08)│ RepartitionExec
06)│ -------------------- │
07)│ predicate: │
08)│ string_col@1 != foo
09)└─────────────┬─────────────┘
10)┌─────────────┴─────────────┐
11)│ DataSourceExec │
12)└───────────────────────────┘
11)│ RepartitionExec │
12)└─────────────┬─────────────┘
13)┌─────────────┴─────────────┐
14)│ DataSourceExec │
15)└───────────────────────────┘

# Aggregate
query TT
Expand Down Expand Up @@ -185,6 +188,32 @@ physical_plan
26)│ DataSourceExec ││ DataSourceExec │
27)└───────────────────────────┘└───────────────────────────┘

# Long Filter (demonstrate what happens with wrapping)
query TT
explain SELECT int_col FROM table1
WHERE string_col != 'foo' AND string_col != 'bar' AND string_col != 'a really long string constant'
;
----
logical_plan
01)Projection: table1.int_col
02)--Filter: table1.string_col != Utf8("foo") AND table1.string_col != Utf8("bar") AND table1.string_col != Utf8("a really long string constant")
03)----TableScan: table1 projection=[int_col, string_col], partial_filters=[table1.string_col != Utf8("foo"), table1.string_col != Utf8("bar"), table1.string_col != Utf8("a really long string constant")]
physical_plan
01)┌───────────────────────────┐
02)│ CoalesceBatchesExec │
03)└─────────────┬─────────────┘
04)┌─────────────┴─────────────┐
05)│ FilterExec │
06)│ -------------------- │
07)│ predicate: │
08)│string_col@1 != foo AND ...│
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very nice case for // TODO: check every line is less than MAX_LINE_RENDER_SIZE. In DuckDB, we can get the following result:

D explain select * from t1 where c != 'foo' and c != 'bar' and c != 'a really long string constant';

┌─────────────────────────────┐
│┌───────────────────────────┐│
││       Physical Plan       ││
│└───────────────────────────┘│
└─────────────────────────────┘
┌───────────────────────────┐
│         SEQ_SCAN          │
│    ────────────────────   │
│         Table: t1         │
│   Type: Sequential Scan   │
│       Projections: c      │
│                           │
│          Filters:         │
│ c!='foo' AND c!='bar' AND │
│  c!='a really long string │
│          constant'        │
│                           │
│          ~1 Rows          │
└───────────────────────────┘

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try to make it the same as DuckDB later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome -- thank you. Maybe we can file a ticket to track the idea?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

09)└─────────────┬─────────────┘
10)┌─────────────┴─────────────┐
11)│ RepartitionExec │
12)└─────────────┬─────────────┘
13)┌─────────────┴─────────────┐
14)│ DataSourceExec │
15)└───────────────────────────┘


# cleanup
Expand Down