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 a38ac202f8 feat: show statistics in explain verbose (#8113)
a38ac202f8 is described below
commit a38ac202f8db5bb7e184b57a59875b05990ee7c3
Author: Nga Tran <[email protected]>
AuthorDate: Mon Nov 13 17:33:25 2023 -0500
feat: show statistics in explain verbose (#8113)
* feat: show statistics in explain verbose
* chore: address review comments
* chore: address review comments
* fix: add new enum types in pbjson
* fix: add new types into message proto
* Update explain plan
---------
Co-authored-by: Andrew Lamb <[email protected]>
---
datafusion/common/src/display/mod.rs | 8 +++
datafusion/core/src/physical_planner.rs | 38 ++++++++++++--
datafusion/proto/proto/datafusion.proto | 2 +
datafusion/proto/src/generated/pbjson.rs | 26 +++++++++
datafusion/proto/src/generated/prost.rs | 6 ++-
datafusion/proto/src/logical_plan/from_proto.rs | 5 +-
datafusion/proto/src/logical_plan/to_proto.rs | 9 +++-
datafusion/sqllogictest/test_files/explain.slt | 70 ++++++++++++++++++++++++-
8 files changed, 154 insertions(+), 10 deletions(-)
diff --git a/datafusion/common/src/display/mod.rs
b/datafusion/common/src/display/mod.rs
index 766b37ce28..4d1d48bf9f 100644
--- a/datafusion/common/src/display/mod.rs
+++ b/datafusion/common/src/display/mod.rs
@@ -47,6 +47,8 @@ pub enum PlanType {
FinalLogicalPlan,
/// The initial physical plan, prepared for execution
InitialPhysicalPlan,
+ /// The initial physical plan with stats, prepared for execution
+ InitialPhysicalPlanWithStats,
/// The ExecutionPlan which results from applying an optimizer pass
OptimizedPhysicalPlan {
/// The name of the optimizer which produced this plan
@@ -54,6 +56,8 @@ pub enum PlanType {
},
/// The final, fully optimized physical which would be executed
FinalPhysicalPlan,
+ /// The final with stats, fully optimized physical which would be executed
+ FinalPhysicalPlanWithStats,
}
impl Display for PlanType {
@@ -69,10 +73,14 @@ impl Display for PlanType {
}
PlanType::FinalLogicalPlan => write!(f, "logical_plan"),
PlanType::InitialPhysicalPlan => write!(f,
"initial_physical_plan"),
+ PlanType::InitialPhysicalPlanWithStats => {
+ write!(f, "initial_physical_plan_with_stats")
+ }
PlanType::OptimizedPhysicalPlan { optimizer_name } => {
write!(f, "physical_plan after {optimizer_name}")
}
PlanType::FinalPhysicalPlan => write!(f, "physical_plan"),
+ PlanType::FinalPhysicalPlanWithStats => write!(f,
"physical_plan_with_stats"),
}
}
}
diff --git a/datafusion/core/src/physical_planner.rs
b/datafusion/core/src/physical_planner.rs
index 9f9b529ace..9c1d978acc 100644
--- a/datafusion/core/src/physical_planner.rs
+++ b/datafusion/core/src/physical_planner.rs
@@ -1893,12 +1893,25 @@ impl DefaultPhysicalPlanner {
.await
{
Ok(input) => {
+ // This plan will includes statistics if
show_statistics is on
stringified_plans.push(
displayable(input.as_ref())
.set_show_statistics(config.show_statistics)
.to_stringified(e.verbose,
InitialPhysicalPlan),
);
+ // If the show_statisitcs is off, add another line to
show statsitics in the case of explain verbose
+ if e.verbose && !config.show_statistics {
+ stringified_plans.push(
+ displayable(input.as_ref())
+ .set_show_statistics(true)
+ .to_stringified(
+ e.verbose,
+ InitialPhysicalPlanWithStats,
+ ),
+ );
+ }
+
match self.optimize_internal(
input,
session_state,
@@ -1912,11 +1925,26 @@ impl DefaultPhysicalPlanner {
);
},
) {
- Ok(input) => stringified_plans.push(
- displayable(input.as_ref())
-
.set_show_statistics(config.show_statistics)
- .to_stringified(e.verbose,
FinalPhysicalPlan),
- ),
+ Ok(input) => {
+ // This plan will includes statistics if
show_statistics is on
+ stringified_plans.push(
+ displayable(input.as_ref())
+
.set_show_statistics(config.show_statistics)
+ .to_stringified(e.verbose,
FinalPhysicalPlan),
+ );
+
+ // If the show_statisitcs is off, add another
line to show statsitics in the case of explain verbose
+ if e.verbose && !config.show_statistics {
+ stringified_plans.push(
+ displayable(input.as_ref())
+ .set_show_statistics(true)
+ .to_stringified(
+ e.verbose,
+ FinalPhysicalPlanWithStats,
+ ),
+ );
+ }
+ }
Err(DataFusionError::Context(optimizer_name, e))
=> {
let plan_type = OptimizedPhysicalPlan {
optimizer_name };
stringified_plans
diff --git a/datafusion/proto/proto/datafusion.proto
b/datafusion/proto/proto/datafusion.proto
index 793378a1ea..5d7c570bc1 100644
--- a/datafusion/proto/proto/datafusion.proto
+++ b/datafusion/proto/proto/datafusion.proto
@@ -1085,8 +1085,10 @@ message PlanType {
OptimizedLogicalPlanType OptimizedLogicalPlan = 2;
EmptyMessage FinalLogicalPlan = 3;
EmptyMessage InitialPhysicalPlan = 4;
+ EmptyMessage InitialPhysicalPlanWithStats = 9;
OptimizedPhysicalPlanType OptimizedPhysicalPlan = 5;
EmptyMessage FinalPhysicalPlan = 6;
+ EmptyMessage FinalPhysicalPlanWithStats = 10;
}
}
diff --git a/datafusion/proto/src/generated/pbjson.rs
b/datafusion/proto/src/generated/pbjson.rs
index a78da2a51c..12fa73205d 100644
--- a/datafusion/proto/src/generated/pbjson.rs
+++ b/datafusion/proto/src/generated/pbjson.rs
@@ -19295,12 +19295,18 @@ impl serde::Serialize for PlanType {
plan_type::PlanTypeEnum::InitialPhysicalPlan(v) => {
struct_ser.serialize_field("InitialPhysicalPlan", v)?;
}
+ plan_type::PlanTypeEnum::InitialPhysicalPlanWithStats(v) => {
+ struct_ser.serialize_field("InitialPhysicalPlanWithStats",
v)?;
+ }
plan_type::PlanTypeEnum::OptimizedPhysicalPlan(v) => {
struct_ser.serialize_field("OptimizedPhysicalPlan", v)?;
}
plan_type::PlanTypeEnum::FinalPhysicalPlan(v) => {
struct_ser.serialize_field("FinalPhysicalPlan", v)?;
}
+ plan_type::PlanTypeEnum::FinalPhysicalPlanWithStats(v) => {
+ struct_ser.serialize_field("FinalPhysicalPlanWithStats",
v)?;
+ }
}
}
struct_ser.end()
@@ -19319,8 +19325,10 @@ impl<'de> serde::Deserialize<'de> for PlanType {
"OptimizedLogicalPlan",
"FinalLogicalPlan",
"InitialPhysicalPlan",
+ "InitialPhysicalPlanWithStats",
"OptimizedPhysicalPlan",
"FinalPhysicalPlan",
+ "FinalPhysicalPlanWithStats",
];
#[allow(clippy::enum_variant_names)]
@@ -19331,8 +19339,10 @@ impl<'de> serde::Deserialize<'de> for PlanType {
OptimizedLogicalPlan,
FinalLogicalPlan,
InitialPhysicalPlan,
+ InitialPhysicalPlanWithStats,
OptimizedPhysicalPlan,
FinalPhysicalPlan,
+ FinalPhysicalPlanWithStats,
}
impl<'de> serde::Deserialize<'de> for GeneratedField {
fn deserialize<D>(deserializer: D) ->
std::result::Result<GeneratedField, D::Error>
@@ -19360,8 +19370,10 @@ impl<'de> serde::Deserialize<'de> for PlanType {
"OptimizedLogicalPlan" =>
Ok(GeneratedField::OptimizedLogicalPlan),
"FinalLogicalPlan" =>
Ok(GeneratedField::FinalLogicalPlan),
"InitialPhysicalPlan" =>
Ok(GeneratedField::InitialPhysicalPlan),
+ "InitialPhysicalPlanWithStats" =>
Ok(GeneratedField::InitialPhysicalPlanWithStats),
"OptimizedPhysicalPlan" =>
Ok(GeneratedField::OptimizedPhysicalPlan),
"FinalPhysicalPlan" =>
Ok(GeneratedField::FinalPhysicalPlan),
+ "FinalPhysicalPlanWithStats" =>
Ok(GeneratedField::FinalPhysicalPlanWithStats),
_ => Err(serde::de::Error::unknown_field(value,
FIELDS)),
}
}
@@ -19424,6 +19436,13 @@ impl<'de> serde::Deserialize<'de> for PlanType {
return
Err(serde::de::Error::duplicate_field("InitialPhysicalPlan"));
}
plan_type_enum__ =
map_.next_value::<::std::option::Option<_>>()?.map(plan_type::PlanTypeEnum::InitialPhysicalPlan)
+;
+ }
+ GeneratedField::InitialPhysicalPlanWithStats => {
+ if plan_type_enum__.is_some() {
+ return
Err(serde::de::Error::duplicate_field("InitialPhysicalPlanWithStats"));
+ }
+ plan_type_enum__ =
map_.next_value::<::std::option::Option<_>>()?.map(plan_type::PlanTypeEnum::InitialPhysicalPlanWithStats)
;
}
GeneratedField::OptimizedPhysicalPlan => {
@@ -19438,6 +19457,13 @@ impl<'de> serde::Deserialize<'de> for PlanType {
return
Err(serde::de::Error::duplicate_field("FinalPhysicalPlan"));
}
plan_type_enum__ =
map_.next_value::<::std::option::Option<_>>()?.map(plan_type::PlanTypeEnum::FinalPhysicalPlan)
+;
+ }
+ GeneratedField::FinalPhysicalPlanWithStats => {
+ if plan_type_enum__.is_some() {
+ return
Err(serde::de::Error::duplicate_field("FinalPhysicalPlanWithStats"));
+ }
+ plan_type_enum__ =
map_.next_value::<::std::option::Option<_>>()?.map(plan_type::PlanTypeEnum::FinalPhysicalPlanWithStats)
;
}
}
diff --git a/datafusion/proto/src/generated/prost.rs
b/datafusion/proto/src/generated/prost.rs
index 7b7b0afb92..23be5d9088 100644
--- a/datafusion/proto/src/generated/prost.rs
+++ b/datafusion/proto/src/generated/prost.rs
@@ -1423,7 +1423,7 @@ pub struct OptimizedPhysicalPlanType {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PlanType {
- #[prost(oneof = "plan_type::PlanTypeEnum", tags = "1, 7, 8, 2, 3, 4, 5,
6")]
+ #[prost(oneof = "plan_type::PlanTypeEnum", tags = "1, 7, 8, 2, 3, 4, 9, 5,
6, 10")]
pub plan_type_enum: ::core::option::Option<plan_type::PlanTypeEnum>,
}
/// Nested message and enum types in `PlanType`.
@@ -1443,10 +1443,14 @@ pub mod plan_type {
FinalLogicalPlan(super::EmptyMessage),
#[prost(message, tag = "4")]
InitialPhysicalPlan(super::EmptyMessage),
+ #[prost(message, tag = "9")]
+ InitialPhysicalPlanWithStats(super::EmptyMessage),
#[prost(message, tag = "5")]
OptimizedPhysicalPlan(super::OptimizedPhysicalPlanType),
#[prost(message, tag = "6")]
FinalPhysicalPlan(super::EmptyMessage),
+ #[prost(message, tag = "10")]
+ FinalPhysicalPlanWithStats(super::EmptyMessage),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
diff --git a/datafusion/proto/src/logical_plan/from_proto.rs
b/datafusion/proto/src/logical_plan/from_proto.rs
index f7e38757e9..0ecbe05e79 100644
--- a/datafusion/proto/src/logical_plan/from_proto.rs
+++ b/datafusion/proto/src/logical_plan/from_proto.rs
@@ -19,7 +19,8 @@ use crate::protobuf::{
self,
plan_type::PlanTypeEnum::{
AnalyzedLogicalPlan, FinalAnalyzedLogicalPlan, FinalLogicalPlan,
- FinalPhysicalPlan, InitialLogicalPlan, InitialPhysicalPlan,
OptimizedLogicalPlan,
+ FinalPhysicalPlan, FinalPhysicalPlanWithStats, InitialLogicalPlan,
+ InitialPhysicalPlan, InitialPhysicalPlanWithStats,
OptimizedLogicalPlan,
OptimizedPhysicalPlan,
},
AnalyzedLogicalPlanType, CubeNode, GroupingSetNode,
OptimizedLogicalPlanType,
@@ -406,12 +407,14 @@ impl From<&protobuf::StringifiedPlan> for StringifiedPlan
{
}
FinalLogicalPlan(_) => PlanType::FinalLogicalPlan,
InitialPhysicalPlan(_) => PlanType::InitialPhysicalPlan,
+ InitialPhysicalPlanWithStats(_) =>
PlanType::InitialPhysicalPlanWithStats,
OptimizedPhysicalPlan(OptimizedPhysicalPlanType {
optimizer_name }) => {
PlanType::OptimizedPhysicalPlan {
optimizer_name: optimizer_name.clone(),
}
}
FinalPhysicalPlan(_) => PlanType::FinalPhysicalPlan,
+ FinalPhysicalPlanWithStats(_) =>
PlanType::FinalPhysicalPlanWithStats,
},
plan: Arc::new(stringified_plan.plan.clone()),
}
diff --git a/datafusion/proto/src/logical_plan/to_proto.rs
b/datafusion/proto/src/logical_plan/to_proto.rs
index 2bb7f89c7d..4c81ab954a 100644
--- a/datafusion/proto/src/logical_plan/to_proto.rs
+++ b/datafusion/proto/src/logical_plan/to_proto.rs
@@ -24,7 +24,8 @@ use crate::protobuf::{
arrow_type::ArrowTypeEnum,
plan_type::PlanTypeEnum::{
AnalyzedLogicalPlan, FinalAnalyzedLogicalPlan, FinalLogicalPlan,
- FinalPhysicalPlan, InitialLogicalPlan, InitialPhysicalPlan,
OptimizedLogicalPlan,
+ FinalPhysicalPlan, FinalPhysicalPlanWithStats, InitialLogicalPlan,
+ InitialPhysicalPlan, InitialPhysicalPlanWithStats,
OptimizedLogicalPlan,
OptimizedPhysicalPlan,
},
AnalyzedLogicalPlanType, CubeNode, EmptyMessage, GroupingSetNode,
LogicalExprList,
@@ -352,6 +353,12 @@ impl From<&StringifiedPlan> for protobuf::StringifiedPlan {
PlanType::FinalPhysicalPlan => Some(protobuf::PlanType {
plan_type_enum: Some(FinalPhysicalPlan(EmptyMessage {})),
}),
+ PlanType::InitialPhysicalPlanWithStats =>
Some(protobuf::PlanType {
+ plan_type_enum:
Some(InitialPhysicalPlanWithStats(EmptyMessage {})),
+ }),
+ PlanType::FinalPhysicalPlanWithStats =>
Some(protobuf::PlanType {
+ plan_type_enum:
Some(FinalPhysicalPlanWithStats(EmptyMessage {})),
+ }),
},
plan: stringified_plan.plan.to_string(),
}
diff --git a/datafusion/sqllogictest/test_files/explain.slt
b/datafusion/sqllogictest/test_files/explain.slt
index 1db24efd9b..9726c35a31 100644
--- a/datafusion/sqllogictest/test_files/explain.slt
+++ b/datafusion/sqllogictest/test_files/explain.slt
@@ -245,6 +245,7 @@ logical_plan after eliminate_projection SAME TEXT AS ABOVE
logical_plan after push_down_limit SAME TEXT AS ABOVE
logical_plan TableScan: simple_explain_test projection=[a, b, c]
initial_physical_plan CsvExec: file_groups={1 group:
[[WORKSPACE_ROOT/datafusion/core/tests/data/example.csv]]}, projection=[a, b,
c], has_header=true
+initial_physical_plan_with_stats CsvExec: file_groups={1 group:
[[WORKSPACE_ROOT/datafusion/core/tests/data/example.csv]]}, projection=[a, b,
c], has_header=true, statistics=[Rows=Absent, Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:)]]
physical_plan after OutputRequirements
OutputRequirementExec
--CsvExec: file_groups={1 group:
[[WORKSPACE_ROOT/datafusion/core/tests/data/example.csv]]}, projection=[a, b,
c], has_header=true
@@ -260,6 +261,7 @@ physical_plan after PipelineChecker SAME TEXT AS ABOVE
physical_plan after LimitAggregation SAME TEXT AS ABOVE
physical_plan after ProjectionPushdown SAME TEXT AS ABOVE
physical_plan CsvExec: file_groups={1 group:
[[WORKSPACE_ROOT/datafusion/core/tests/data/example.csv]]}, projection=[a, b,
c], has_header=true
+physical_plan_with_stats CsvExec: file_groups={1 group:
[[WORKSPACE_ROOT/datafusion/core/tests/data/example.csv]]}, projection=[a, b,
c], has_header=true, statistics=[Rows=Absent, Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:)]]
### tests for EXPLAIN with display statistics enabled
@@ -291,8 +293,72 @@ physical_plan
GlobalLimitExec: skip=0, fetch=10, statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
--ParquetExec: file_groups={1 group:
[[WORKSPACE_ROOT/parquet-testing/data/alltypes_plain.parquet]]},
projection=[id, bool_col, tinyint_col, smallint_col, int_col, bigint_col,
float_col, double_col, date_string_col, string_col, timestamp_col], limit=10,
statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
-statement ok
-set datafusion.execution.collect_statistics = false;
+# explain verbose with both collect & show statistics on
+query TT
+EXPLAIN VERBOSE SELECT * FROM alltypes_plain limit 10;
+----
+initial_physical_plan
+GlobalLimitExec: skip=0, fetch=10, statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
+--ParquetExec: file_groups={1 group:
[[WORKSPACE_ROOT/parquet-testing/data/alltypes_plain.parquet]]},
projection=[id, bool_col, tinyint_col, smallint_col, int_col, bigint_col,
float_col, double_col, date_string_col, string_col, timestamp_col], limit=10,
statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
+physical_plan after OutputRequirements
+OutputRequirementExec, statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
+--GlobalLimitExec: skip=0, fetch=10, statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
+----ParquetExec: file_groups={1 group:
[[WORKSPACE_ROOT/parquet-testing/data/alltypes_plain.parquet]]},
projection=[id, bool_col, tinyint_col, smallint_col, int_col, bigint_col,
float_col, double_col, date_string_col, string_col, timestamp_col], limit=10,
statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
+physical_plan after aggregate_statistics SAME TEXT AS ABOVE
+physical_plan after join_selection SAME TEXT AS ABOVE
+physical_plan after LimitedDistinctAggregation SAME TEXT AS ABOVE
+physical_plan after EnforceDistribution SAME TEXT AS ABOVE
+physical_plan after CombinePartialFinalAggregate SAME TEXT AS ABOVE
+physical_plan after EnforceSorting SAME TEXT AS ABOVE
+physical_plan after coalesce_batches SAME TEXT AS ABOVE
+physical_plan after OutputRequirements
+GlobalLimitExec: skip=0, fetch=10, statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
+--ParquetExec: file_groups={1 group:
[[WORKSPACE_ROOT/parquet-testing/data/alltypes_plain.parquet]]},
projection=[id, bool_col, tinyint_col, smallint_col, int_col, bigint_col,
float_col, double_col, date_string_col, string_col, timestamp_col], limit=10,
statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
+physical_plan after PipelineChecker SAME TEXT AS ABOVE
+physical_plan after LimitAggregation SAME TEXT AS ABOVE
+physical_plan after ProjectionPushdown SAME TEXT AS ABOVE
+physical_plan
+GlobalLimitExec: skip=0, fetch=10, statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
+--ParquetExec: file_groups={1 group:
[[WORKSPACE_ROOT/parquet-testing/data/alltypes_plain.parquet]]},
projection=[id, bool_col, tinyint_col, smallint_col, int_col, bigint_col,
float_col, double_col, date_string_col, string_col, timestamp_col], limit=10,
statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
+
statement ok
set datafusion.explain.show_statistics = false;
+
+# explain verbose with collect on and & show statistics off: still has stats
+query TT
+EXPLAIN VERBOSE SELECT * FROM alltypes_plain limit 10;
+----
+initial_physical_plan
+GlobalLimitExec: skip=0, fetch=10
+--ParquetExec: file_groups={1 group:
[[WORKSPACE_ROOT/parquet-testing/data/alltypes_plain.parquet]]},
projection=[id, bool_col, tinyint_col, smallint_col, int_col, bigint_col,
float_col, double_col, date_string_col, string_col, timestamp_col], limit=10
+initial_physical_plan_with_stats
+GlobalLimitExec: skip=0, fetch=10, statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
+--ParquetExec: file_groups={1 group:
[[WORKSPACE_ROOT/parquet-testing/data/alltypes_plain.parquet]]},
projection=[id, bool_col, tinyint_col, smallint_col, int_col, bigint_col,
float_col, double_col, date_string_col, string_col, timestamp_col], limit=10,
statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
+physical_plan after OutputRequirements
+OutputRequirementExec
+--GlobalLimitExec: skip=0, fetch=10
+----ParquetExec: file_groups={1 group:
[[WORKSPACE_ROOT/parquet-testing/data/alltypes_plain.parquet]]},
projection=[id, bool_col, tinyint_col, smallint_col, int_col, bigint_col,
float_col, double_col, date_string_col, string_col, timestamp_col], limit=10
+physical_plan after aggregate_statistics SAME TEXT AS ABOVE
+physical_plan after join_selection SAME TEXT AS ABOVE
+physical_plan after LimitedDistinctAggregation SAME TEXT AS ABOVE
+physical_plan after EnforceDistribution SAME TEXT AS ABOVE
+physical_plan after CombinePartialFinalAggregate SAME TEXT AS ABOVE
+physical_plan after EnforceSorting SAME TEXT AS ABOVE
+physical_plan after coalesce_batches SAME TEXT AS ABOVE
+physical_plan after OutputRequirements
+GlobalLimitExec: skip=0, fetch=10
+--ParquetExec: file_groups={1 group:
[[WORKSPACE_ROOT/parquet-testing/data/alltypes_plain.parquet]]},
projection=[id, bool_col, tinyint_col, smallint_col, int_col, bigint_col,
float_col, double_col, date_string_col, string_col, timestamp_col], limit=10
+physical_plan after PipelineChecker SAME TEXT AS ABOVE
+physical_plan after LimitAggregation SAME TEXT AS ABOVE
+physical_plan after ProjectionPushdown SAME TEXT AS ABOVE
+physical_plan
+GlobalLimitExec: skip=0, fetch=10
+--ParquetExec: file_groups={1 group:
[[WORKSPACE_ROOT/parquet-testing/data/alltypes_plain.parquet]]},
projection=[id, bool_col, tinyint_col, smallint_col, int_col, bigint_col,
float_col, double_col, date_string_col, string_col, timestamp_col], limit=10
+physical_plan_with_stats
+GlobalLimitExec: skip=0, fetch=10, statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
+--ParquetExec: file_groups={1 group:
[[WORKSPACE_ROOT/parquet-testing/data/alltypes_plain.parquet]]},
projection=[id, bool_col, tinyint_col, smallint_col, int_col, bigint_col,
float_col, double_col, date_string_col, string_col, timestamp_col], limit=10,
statistics=[Rows=Exact(8), Bytes=Absent,
[(Col[0]:),(Col[1]:),(Col[2]:),(Col[3]:),(Col[4]:),(Col[5]:),(Col[6]:),(Col[7]:),(Col[8]:),(Col[9]:),(Col[10]:)]]
+
+
+statement ok
+set datafusion.execution.collect_statistics = false;