From 36087823fcf21394bc3141790aa98026e81e6a0c Mon Sep 17 00:00:00 2001 From: zhuqi-lucas <821684824@qq.com> Date: Thu, 27 Feb 2025 22:47:41 +0800 Subject: [PATCH 1/8] BUG: schema_force_view_type configuration not working for CREATE EXTERNAL TABLE --- .../src/datasource/file_format/parquet.rs | 12 ---------- .../src/datasource/listing_table_factory.rs | 23 ++++++++++++++++++- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/datafusion/core/src/datasource/file_format/parquet.rs b/datafusion/core/src/datasource/file_format/parquet.rs index 98aa24ad00cb..2c2f0cef553e 100644 --- a/datafusion/core/src/datasource/file_format/parquet.rs +++ b/datafusion/core/src/datasource/file_format/parquet.rs @@ -362,18 +362,6 @@ impl FileFormat for ParquetFormat { Schema::try_merge(schemas) }?; - let schema = if self.binary_as_string() { - transform_binary_to_string(&schema) - } else { - schema - }; - - let schema = if self.force_view_types() { - transform_schema_to_view(&schema) - } else { - schema - }; - Ok(Arc::new(schema)) } diff --git a/datafusion/core/src/datasource/listing_table_factory.rs b/datafusion/core/src/datasource/listing_table_factory.rs index 636d1623c5e9..9b1a87db3ea0 100644 --- a/datafusion/core/src/datasource/listing_table_factory.rs +++ b/datafusion/core/src/datasource/listing_table_factory.rs @@ -34,6 +34,9 @@ use datafusion_expr::CreateExternalTable; use async_trait::async_trait; use datafusion_catalog::Session; +use crate::datasource::file_format::{transform_binary_to_string, transform_schema_to_view}; +use crate::datasource::file_format::parquet::ParquetFormat; +use crate::datasource::physical_plan::ParquetSource; /// A `TableProviderFactory` capable of creating new `ListingTable`s #[derive(Debug, Default)] @@ -120,7 +123,7 @@ impl TableProviderFactory for ListingTableFactory { .validate_partitions(session_state, &table_path) .await?; - let resolved_schema = match provided_schema { + let mut resolved_schema = match provided_schema { // We will need to check the table columns against the schema // this is done so that we can do an ORDER BY for external table creation // specifically for parquet file format. @@ -145,6 +148,24 @@ impl TableProviderFactory for ListingTableFactory { } Some(s) => s, }; + + + if let Some(parquetFmt) = options.format.as_any().downcast_ref::() { + resolved_schema = if parquetFmt.binary_as_string() { + transform_binary_to_string(resolved_schema.as_ref()).into() + } else { + resolved_schema + }; + + resolved_schema = if parquetFmt.force_view_types() { + println!("force view type schema: {:?}", resolved_schema); + transform_schema_to_view(&resolved_schema.as_ref()).into() + } else { + resolved_schema + }; + } + + let config = ListingTableConfig::new(table_path) .with_listing_options(options.with_file_sort_order(cmd.order_exprs.clone())) .with_schema(resolved_schema); From bad38e8db302e1e67f7b6537f0958698f6ef6d7b Mon Sep 17 00:00:00 2001 From: zhuqi-lucas <821684824@qq.com> Date: Thu, 27 Feb 2025 22:52:26 +0800 Subject: [PATCH 2/8] fix import --- .../core/src/datasource/listing_table_factory.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/datafusion/core/src/datasource/listing_table_factory.rs b/datafusion/core/src/datasource/listing_table_factory.rs index 9b1a87db3ea0..a55eb47083eb 100644 --- a/datafusion/core/src/datasource/listing_table_factory.rs +++ b/datafusion/core/src/datasource/listing_table_factory.rs @@ -32,11 +32,11 @@ use datafusion_common::{arrow_datafusion_err, plan_err, DataFusionError, ToDFSch use datafusion_common::{config_datafusion_err, Result}; use datafusion_expr::CreateExternalTable; +use crate::datasource::file_format::parquet::{ + transform_binary_to_string, transform_schema_to_view, ParquetFormat, +}; use async_trait::async_trait; use datafusion_catalog::Session; -use crate::datasource::file_format::{transform_binary_to_string, transform_schema_to_view}; -use crate::datasource::file_format::parquet::ParquetFormat; -use crate::datasource::physical_plan::ParquetSource; /// A `TableProviderFactory` capable of creating new `ListingTable`s #[derive(Debug, Default)] @@ -149,8 +149,8 @@ impl TableProviderFactory for ListingTableFactory { Some(s) => s, }; - - if let Some(parquetFmt) = options.format.as_any().downcast_ref::() { + if let Some(parquetFmt) = options.format.as_any().downcast_ref::() + { resolved_schema = if parquetFmt.binary_as_string() { transform_binary_to_string(resolved_schema.as_ref()).into() } else { @@ -165,7 +165,6 @@ impl TableProviderFactory for ListingTableFactory { }; } - let config = ListingTableConfig::new(table_path) .with_listing_options(options.with_file_sort_order(cmd.order_exprs.clone())) .with_schema(resolved_schema); From 5be3e37e9b5bdb76f502815b016a2bcd2195d974 Mon Sep 17 00:00:00 2001 From: zhuqi-lucas <821684824@qq.com> Date: Thu, 27 Feb 2025 23:42:48 +0800 Subject: [PATCH 3/8] refactor --- .../examples/custom_file_format.rs | 4 ++++ .../core/src/datasource/file_format/arrow.rs | 4 ++++ .../core/src/datasource/file_format/avro.rs | 4 ++++ .../core/src/datasource/file_format/csv.rs | 4 ++++ .../core/src/datasource/file_format/json.rs | 7 +++++++ .../src/datasource/file_format/parquet.rs | 21 +++++++++++++++++++ .../src/datasource/listing_table_factory.rs | 19 +---------------- datafusion/datasource/src/file_format.rs | 6 ++++++ 8 files changed, 51 insertions(+), 18 deletions(-) diff --git a/datafusion-examples/examples/custom_file_format.rs b/datafusion-examples/examples/custom_file_format.rs index 165d82627061..daa8ff6dc124 100644 --- a/datafusion-examples/examples/custom_file_format.rs +++ b/datafusion-examples/examples/custom_file_format.rs @@ -96,6 +96,10 @@ impl FileFormat for TSVFileFormat { .await } + async fn transform_schema(&self, schema: SchemaRef) -> Result { + Ok(schema) + } + async fn infer_stats( &self, state: &dyn Session, diff --git a/datafusion/core/src/datasource/file_format/arrow.rs b/datafusion/core/src/datasource/file_format/arrow.rs index 3614b788af90..10a94a7256fd 100644 --- a/datafusion/core/src/datasource/file_format/arrow.rs +++ b/datafusion/core/src/datasource/file_format/arrow.rs @@ -158,6 +158,10 @@ impl FileFormat for ArrowFormat { Ok(Arc::new(merged_schema)) } + async fn transform_schema(&self, schema: SchemaRef) -> Result { + Ok(schema) + } + async fn infer_stats( &self, _state: &dyn Session, diff --git a/datafusion/core/src/datasource/file_format/avro.rs b/datafusion/core/src/datasource/file_format/avro.rs index e7314e839bf2..a299c22d1a82 100644 --- a/datafusion/core/src/datasource/file_format/avro.rs +++ b/datafusion/core/src/datasource/file_format/avro.rs @@ -136,6 +136,10 @@ impl FileFormat for AvroFormat { Ok(Arc::new(merged_schema)) } + async fn transform_schema(&self, schema: SchemaRef) -> Result { + Ok(schema) + } + async fn infer_stats( &self, _state: &dyn Session, diff --git a/datafusion/core/src/datasource/file_format/csv.rs b/datafusion/core/src/datasource/file_format/csv.rs index 45ad3e8c1c30..5dba0222d63a 100644 --- a/datafusion/core/src/datasource/file_format/csv.rs +++ b/datafusion/core/src/datasource/file_format/csv.rs @@ -400,6 +400,10 @@ impl FileFormat for CsvFormat { Ok(Arc::new(merged_schema)) } + async fn transform_schema(&self, schema: SchemaRef) -> Result { + Ok(schema) + } + async fn infer_stats( &self, _state: &dyn Session, diff --git a/datafusion/core/src/datasource/file_format/json.rs b/datafusion/core/src/datasource/file_format/json.rs index 1a2aaf3af8be..e2787a8a57f8 100644 --- a/datafusion/core/src/datasource/file_format/json.rs +++ b/datafusion/core/src/datasource/file_format/json.rs @@ -246,6 +246,13 @@ impl FileFormat for JsonFormat { Ok(Statistics::new_unknown(&table_schema)) } + async fn transform_schema( + &self, + schema: SchemaRef, + ) -> Result { + Ok(schema) + } + async fn create_physical_plan( &self, _state: &dyn Session, diff --git a/datafusion/core/src/datasource/file_format/parquet.rs b/datafusion/core/src/datasource/file_format/parquet.rs index 2c2f0cef553e..062482b84af5 100644 --- a/datafusion/core/src/datasource/file_format/parquet.rs +++ b/datafusion/core/src/datasource/file_format/parquet.rs @@ -365,6 +365,27 @@ impl FileFormat for ParquetFormat { Ok(Arc::new(schema)) } + async fn transform_schema( + &self, + schema: SchemaRef + ) -> Result { + println!("transform_schema schema {:?}", schema); + let schema = if self.binary_as_string() { + Arc::new(transform_binary_to_string(schema.as_ref())) + } else { + schema + }; + + let schema = if self.force_view_types() { + Arc::new(transform_schema_to_view(&schema)) + } else { + schema + }; + + println!("Done schema {:?}", schema); + Ok(schema) + } + async fn infer_stats( &self, _state: &dyn Session, diff --git a/datafusion/core/src/datasource/listing_table_factory.rs b/datafusion/core/src/datasource/listing_table_factory.rs index a55eb47083eb..0e3325cdb690 100644 --- a/datafusion/core/src/datasource/listing_table_factory.rs +++ b/datafusion/core/src/datasource/listing_table_factory.rs @@ -32,9 +32,6 @@ use datafusion_common::{arrow_datafusion_err, plan_err, DataFusionError, ToDFSch use datafusion_common::{config_datafusion_err, Result}; use datafusion_expr::CreateExternalTable; -use crate::datasource::file_format::parquet::{ - transform_binary_to_string, transform_schema_to_view, ParquetFormat, -}; use async_trait::async_trait; use datafusion_catalog::Session; @@ -149,21 +146,7 @@ impl TableProviderFactory for ListingTableFactory { Some(s) => s, }; - if let Some(parquetFmt) = options.format.as_any().downcast_ref::() - { - resolved_schema = if parquetFmt.binary_as_string() { - transform_binary_to_string(resolved_schema.as_ref()).into() - } else { - resolved_schema - }; - - resolved_schema = if parquetFmt.force_view_types() { - println!("force view type schema: {:?}", resolved_schema); - transform_schema_to_view(&resolved_schema.as_ref()).into() - } else { - resolved_schema - }; - } + resolved_schema = options.format.transform_schema(resolved_schema).await?; let config = ListingTableConfig::new(table_path) .with_listing_options(options.with_file_sort_order(cmd.order_exprs.clone())) diff --git a/datafusion/datasource/src/file_format.rs b/datafusion/datasource/src/file_format.rs index aa0338fab71d..4ad0d7a42889 100644 --- a/datafusion/datasource/src/file_format.rs +++ b/datafusion/datasource/src/file_format.rs @@ -73,6 +73,12 @@ pub trait FileFormat: Send + Sync + fmt::Debug { objects: &[ObjectMeta], ) -> Result; + /// Transform the schema of the provided object. The cost and accuracy of the + async fn transform_schema( + &self, + schema: SchemaRef, + ) -> Result; + /// Infer the statistics for the provided object. The cost and accuracy of the /// estimated statistics might vary greatly between file formats. /// From 7e45eb69f880124da068fca9de95ee6fabb310de Mon Sep 17 00:00:00 2001 From: zhuqi-lucas <821684824@qq.com> Date: Fri, 28 Feb 2025 08:40:49 +0800 Subject: [PATCH 4/8] Fix --- .../core/src/datasource/file_format/json.rs | 5 +---- .../src/datasource/file_format/parquet.rs | 20 ++++++++++++------- .../src/datasource/listing_table_factory.rs | 8 +++++--- datafusion/datasource/src/file_format.rs | 5 +---- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/datafusion/core/src/datasource/file_format/json.rs b/datafusion/core/src/datasource/file_format/json.rs index e2787a8a57f8..47adcc681824 100644 --- a/datafusion/core/src/datasource/file_format/json.rs +++ b/datafusion/core/src/datasource/file_format/json.rs @@ -246,10 +246,7 @@ impl FileFormat for JsonFormat { Ok(Statistics::new_unknown(&table_schema)) } - async fn transform_schema( - &self, - schema: SchemaRef, - ) -> Result { + async fn transform_schema(&self, schema: SchemaRef) -> Result { Ok(schema) } diff --git a/datafusion/core/src/datasource/file_format/parquet.rs b/datafusion/core/src/datasource/file_format/parquet.rs index 062482b84af5..ef56b46148de 100644 --- a/datafusion/core/src/datasource/file_format/parquet.rs +++ b/datafusion/core/src/datasource/file_format/parquet.rs @@ -362,14 +362,22 @@ impl FileFormat for ParquetFormat { Schema::try_merge(schemas) }?; + let schema = if self.binary_as_string() { + transform_binary_to_string(&schema) + } else { + schema + }; + + let schema = if self.force_view_types() { + transform_schema_to_view(&schema) + } else { + schema + }; + Ok(Arc::new(schema)) } - async fn transform_schema( - &self, - schema: SchemaRef - ) -> Result { - println!("transform_schema schema {:?}", schema); + async fn transform_schema(&self, schema: SchemaRef) -> Result { let schema = if self.binary_as_string() { Arc::new(transform_binary_to_string(schema.as_ref())) } else { @@ -381,8 +389,6 @@ impl FileFormat for ParquetFormat { } else { schema }; - - println!("Done schema {:?}", schema); Ok(schema) } diff --git a/datafusion/core/src/datasource/listing_table_factory.rs b/datafusion/core/src/datasource/listing_table_factory.rs index 0e3325cdb690..58b64e30c1e4 100644 --- a/datafusion/core/src/datasource/listing_table_factory.rs +++ b/datafusion/core/src/datasource/listing_table_factory.rs @@ -143,11 +143,13 @@ impl TableProviderFactory for ListingTableFactory { schema } - Some(s) => s, + Some(s) => { + // For provided schema, we also need to transform it, + // and which is also done for the infer schema if it is not provided + options.format.transform_schema(s).await? + } }; - resolved_schema = options.format.transform_schema(resolved_schema).await?; - let config = ListingTableConfig::new(table_path) .with_listing_options(options.with_file_sort_order(cmd.order_exprs.clone())) .with_schema(resolved_schema); diff --git a/datafusion/datasource/src/file_format.rs b/datafusion/datasource/src/file_format.rs index 4ad0d7a42889..e5cb02e9452d 100644 --- a/datafusion/datasource/src/file_format.rs +++ b/datafusion/datasource/src/file_format.rs @@ -74,10 +74,7 @@ pub trait FileFormat: Send + Sync + fmt::Debug { ) -> Result; /// Transform the schema of the provided object. The cost and accuracy of the - async fn transform_schema( - &self, - schema: SchemaRef, - ) -> Result; + async fn transform_schema(&self, schema: SchemaRef) -> Result; /// Infer the statistics for the provided object. The cost and accuracy of the /// estimated statistics might vary greatly between file formats. From b41b4fb1aeb4917c40a77a084ddf3a16eeb4d2b4 Mon Sep 17 00:00:00 2001 From: zhuqi-lucas <821684824@qq.com> Date: Fri, 28 Feb 2025 08:48:08 +0800 Subject: [PATCH 5/8] Add document --- datafusion/core/src/datasource/file_format/parquet.rs | 2 +- datafusion/datasource/src/file_format.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/datafusion/core/src/datasource/file_format/parquet.rs b/datafusion/core/src/datasource/file_format/parquet.rs index ef56b46148de..5394dd8248c8 100644 --- a/datafusion/core/src/datasource/file_format/parquet.rs +++ b/datafusion/core/src/datasource/file_format/parquet.rs @@ -385,7 +385,7 @@ impl FileFormat for ParquetFormat { }; let schema = if self.force_view_types() { - Arc::new(transform_schema_to_view(&schema)) + Arc::new(transform_schema_to_view(schema.as_ref())) } else { schema }; diff --git a/datafusion/datasource/src/file_format.rs b/datafusion/datasource/src/file_format.rs index e5cb02e9452d..77488ad5f2e7 100644 --- a/datafusion/datasource/src/file_format.rs +++ b/datafusion/datasource/src/file_format.rs @@ -73,7 +73,10 @@ pub trait FileFormat: Send + Sync + fmt::Debug { objects: &[ObjectMeta], ) -> Result; - /// Transform the schema of the provided object. The cost and accuracy of the + /// Transform the schema of the provided object. For example for parquet files: + /// 1. Transform a schema so that any binary types are strings + /// 2. Transform a schema to use view types for Utf8 and Binary + /// Other file formats may have other transformations, but currently only for parquet async fn transform_schema(&self, schema: SchemaRef) -> Result; /// Infer the statistics for the provided object. The cost and accuracy of the From a17970089a43e860162de71f2715beee40c7992d Mon Sep 17 00:00:00 2001 From: zhuqi-lucas <821684824@qq.com> Date: Fri, 28 Feb 2025 18:04:12 +0800 Subject: [PATCH 6/8] Need to disable force_view for insert case, because the create external table field will default to utf8view --- datafusion-examples/examples/dataframe.rs | 3 ++- datafusion/core/src/datasource/listing_table_factory.rs | 2 +- datafusion/core/tests/dataframe/mod.rs | 3 ++- datafusion/execution/src/config.rs | 5 +++++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/datafusion-examples/examples/dataframe.rs b/datafusion-examples/examples/dataframe.rs index 6f61c164f41d..7481579dfcfe 100644 --- a/datafusion-examples/examples/dataframe.rs +++ b/datafusion-examples/examples/dataframe.rs @@ -59,7 +59,8 @@ use tempfile::tempdir; #[tokio::main] async fn main() -> Result<()> { // The SessionContext is the main high level API for interacting with DataFusion - let ctx = SessionContext::new(); + let config = SessionConfig::new().with_parquet_force_view_metadata(false); + let ctx = SessionContext::new_with_config(config); read_parquet(&ctx).await?; read_csv(&ctx).await?; read_memory(&ctx).await?; diff --git a/datafusion/core/src/datasource/listing_table_factory.rs b/datafusion/core/src/datasource/listing_table_factory.rs index 58b64e30c1e4..bfaa449df37c 100644 --- a/datafusion/core/src/datasource/listing_table_factory.rs +++ b/datafusion/core/src/datasource/listing_table_factory.rs @@ -120,7 +120,7 @@ impl TableProviderFactory for ListingTableFactory { .validate_partitions(session_state, &table_path) .await?; - let mut resolved_schema = match provided_schema { + let resolved_schema = match provided_schema { // We will need to check the table columns against the schema // this is done so that we can do an ORDER BY for external table creation // specifically for parquet file format. diff --git a/datafusion/core/tests/dataframe/mod.rs b/datafusion/core/tests/dataframe/mod.rs index b134ec54b13d..e306d3b6e6b7 100644 --- a/datafusion/core/tests/dataframe/mod.rs +++ b/datafusion/core/tests/dataframe/mod.rs @@ -2400,7 +2400,8 @@ async fn write_json_with_order() -> Result<()> { #[tokio::test] async fn write_table_with_order() -> Result<()> { let tmp_dir = TempDir::new()?; - let ctx = SessionContext::new(); + let config = SessionConfig::new().with_parquet_force_view_metadata(false); + let ctx = SessionContext::new_with_config(config); let location = tmp_dir.path().join("test_table/"); let mut write_df = ctx diff --git a/datafusion/execution/src/config.rs b/datafusion/execution/src/config.rs index 53646dc5b468..3ece7279f96d 100644 --- a/datafusion/execution/src/config.rs +++ b/datafusion/execution/src/config.rs @@ -441,6 +441,11 @@ impl SessionConfig { self } + pub fn with_parquet_force_view_metadata(mut self, schema_force_view_types: bool) -> Self { + self.options.execution.parquet.schema_force_view_types = schema_force_view_types; + self + } + /// Returns true if the joins will be enforced to output batches of the configured size pub fn enforce_batch_size_in_joins(&self) -> bool { self.options.execution.enforce_batch_size_in_joins From c56e9a51a45660ce78afb76aed1e240cb1483687 Mon Sep 17 00:00:00 2001 From: zhuqi-lucas <821684824@qq.com> Date: Fri, 28 Feb 2025 19:47:57 +0800 Subject: [PATCH 7/8] fix slt --- datafusion/execution/src/config.rs | 5 ++++- .../sqllogictest/test_files/insert_to_external.slt | 9 ++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/datafusion/execution/src/config.rs b/datafusion/execution/src/config.rs index 3ece7279f96d..e177529dfc4f 100644 --- a/datafusion/execution/src/config.rs +++ b/datafusion/execution/src/config.rs @@ -441,7 +441,10 @@ impl SessionConfig { self } - pub fn with_parquet_force_view_metadata(mut self, schema_force_view_types: bool) -> Self { + pub fn with_parquet_force_view_metadata( + mut self, + schema_force_view_types: bool, + ) -> Self { self.options.execution.parquet.schema_force_view_types = schema_force_view_types; self } diff --git a/datafusion/sqllogictest/test_files/insert_to_external.slt b/datafusion/sqllogictest/test_files/insert_to_external.slt index 24982dfc28a7..344c49fa169e 100644 --- a/datafusion/sqllogictest/test_files/insert_to_external.slt +++ b/datafusion/sqllogictest/test_files/insert_to_external.slt @@ -456,13 +456,16 @@ explain insert into table_without_values select c1 from aggregate_test_100 order ---- logical_plan 01)Dml: op=[Insert Into] table=[table_without_values] -02)--Projection: aggregate_test_100.c1 AS c1 +02)--Projection: CAST(aggregate_test_100.c1 AS Utf8View) AS c1 03)----Sort: aggregate_test_100.c1 ASC NULLS LAST 04)------TableScan: aggregate_test_100 projection=[c1] physical_plan 01)DataSinkExec: sink=ParquetSink(file_groups=[]) -02)--SortExec: expr=[c1@0 ASC NULLS LAST], preserve_partitioning=[false] -03)----DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/testing/data/csv/aggregate_test_100.csv]]}, projection=[c1], file_type=csv, has_header=true +02)--CoalescePartitionsExec +03)----ProjectionExec: expr=[CAST(c1@0 AS Utf8View) as c1] +04)------RepartitionExec: partitioning=RoundRobinBatch(8), input_partitions=1 +05)--------SortExec: expr=[c1@0 ASC NULLS LAST], preserve_partitioning=[false] +06)----------DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/testing/data/csv/aggregate_test_100.csv]]}, projection=[c1], file_type=csv, has_header=true query I insert into table_without_values select c1 from aggregate_test_100 order by c1; From 1b92fdebbc6365176b4667a328af43671d091083 Mon Sep 17 00:00:00 2001 From: zhuqi-lucas <821684824@qq.com> Date: Sat, 1 Mar 2025 20:07:43 +0800 Subject: [PATCH 8/8] Fix slt --- datafusion/sqllogictest/test_files/parquet_filter_pushdown.slt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datafusion/sqllogictest/test_files/parquet_filter_pushdown.slt b/datafusion/sqllogictest/test_files/parquet_filter_pushdown.slt index 758113b70835..0f206f88125d 100644 --- a/datafusion/sqllogictest/test_files/parquet_filter_pushdown.slt +++ b/datafusion/sqllogictest/test_files/parquet_filter_pushdown.slt @@ -144,7 +144,7 @@ EXPLAIN select b from t_pushdown where a = 'bar' order by b; ---- logical_plan 01)Sort: t_pushdown.b ASC NULLS LAST -02)--TableScan: t_pushdown projection=[b], full_filters=[t_pushdown.a = Utf8("bar")] +02)--TableScan: t_pushdown projection=[b], full_filters=[t_pushdown.a = Utf8View("bar")] physical_plan 01)SortPreservingMergeExec: [b@0 ASC NULLS LAST] 02)--SortExec: expr=[b@0 ASC NULLS LAST], preserve_partitioning=[true]