kazuyukitanimura commented on code in PR #6246:
URL: https://github.com/apache/datafusion-comet/pull/6246#discussion_r4299018024


##########
native/core/src/parquet/parquet_exec.rs:
##########
@@ -52,6 +54,81 @@ mod variant_tests;
 /// scan would.
 pub(crate) const METADATA_SIZE_HINT: usize = 512 * 1024;
 
+/// Keep top-level Spark string fields as binary while arrow-rs decodes 
Parquet. arrow-rs only
+/// validates UTF-8 for columns annotated as strings, so decoding an 
unannotated BINARY column that
+/// `spark.sql.parquet.binaryAsString=true` types as a string straight into 
Arrow Utf8 would admit
+/// invalid UTF-8. A projection added at the scan boundary converts the binary 
arrays to valid Utf8
+/// with Spark-compatible replacement semantics.
+fn binary_as_string_read_schema(schema: &SchemaRef) -> SchemaRef {
+    let fields = schema.fields().iter().map(|field| {
+        binary_as_string_read_type(field.data_type()).map_or_else(
+            || Arc::clone(field),
+            |data_type| 
Arc::new(field.as_ref().clone().with_data_type(data_type)),
+        )
+    });
+    Arc::new(Schema::new_with_metadata(
+        fields.collect::<Vec<_>>(),
+        schema.metadata().clone(),
+    ))
+}
+
+/// The type `binary_as_string_read_schema` reads a top-level field of 
`data_type` as, or `None`
+/// when the field is read as declared.
+fn binary_as_string_read_type(data_type: &DataType) -> Option<DataType> {
+    match data_type {
+        DataType::Utf8 => Some(DataType::Binary),
+        DataType::LargeUtf8 => Some(DataType::LargeBinary),
+        _ => None,
+    }
+}
+
+/// Whether `expr` references a column whose folded name is in `folded_names`.
+fn references_any_column(
+    expr: &Arc<dyn PhysicalExpr>,
+    folded_names: &HashSet<String>,
+    case_sensitive: bool,
+) -> Result<bool, ExecutionError> {
+    for column in collect_columns(expr) {
+        if folded_names.contains(&fold_name(column.name(), case_sensitive)?) {
+            return Ok(true);
+        }
+    }
+    Ok(false)
+}
+
+fn binary_as_string_projection(

Review Comment:
   For a file column that is already annotated UTF8, I think this reads Utf8, 
casts to Binary in the adapter, and then casts back to Utf8 in the projection. 
Could you benchmark a scan of annotated string columns with 
binaryAsString=false and true against branch-1.1? If there is a regression, 
deciding per file whether the column is annotated would avoid the double cast.



##########
spark/src/test/scala/org/apache/comet/parquet/ParquetReadSuite.scala:
##########
@@ -218,6 +218,55 @@ abstract class ParquetReadSuite extends CometTestBase {
     }
   }
 
+  test("spark.sql.parquet.binaryAsString uses native scan") {

Review Comment:
   Could you add these cases to the test?
        - A WHERE value = 'one' filter, which exercises the dropped-filter path 
and compares against Spark.
        - An array<string> and a struct<string> built from unannotated binary 
with a malformed byte.
        - A partitioned or multi-file read



##########
native/core/src/parquet/parquet_exec.rs:
##########
@@ -272,7 +381,22 @@ pub(crate) fn init_datasource_exec(
             .filter(|filter| !calls_jvm(filter))
             .collect::<Vec<_>>()
     });
-    let file_source: Arc<dyn FileSource> = match data_filters {
+    // In binaryAsString mode the filters are typed against the Utf8 output 
schema, but the scan
+    // reads the string columns as Binary, so drop the filters that reference 
one for the same
+    // reason. Filters on other columns still prune.
+    let data_filters = match data_filters {

Review Comment:
   Thanks for fixing this. Reading the string columns as Binary and decoding at 
the boundary is the right shape. I'm worried about the pruning we lose. Before 
this PR, a filter like s = 'x' with binaryAsString=true pruned row groups. Now 
it never reaches the reader. Could we keep that pushdown, for example by 
rewriting the filter against the Binary read schema with a binary literal for 
=, IN and IS NULL? If that isn't feasible here, could you show benchmark 
numbers and explain why losing pruning is acceptable for users who turn this 
config on?



##########
native/core/src/parquet/parquet_support.rs:
##########
@@ -330,6 +330,24 @@ fn parquet_convert_array_impl(
 
             Ok(Arc::new(string_array))
         }
+        // Parquet's unannotated BINARY is read as Spark StringType when
+        // spark.sql.parquet.binaryAsString is enabled or a read schema 
declares a string.
+        // Arrow's cast below is safe, so it would turn malformed UTF-8 into 
NULL. Use Spark's
+        // cast so the scan's top-level projection and nested values replace 
malformed input
+        // with U+FFFD, as the JVM's `new String(bytes, UTF_8)` does.
+        (Binary, Utf8) | (LargeBinary, LargeUtf8) => {

Review Comment:
   This arm also changes behavior with binaryAsString=false when a read schema 
declares a string over a binary column. Malformed bytes used to become NULL and 
now become U+FFFD. That looks like the right Spark behavior. Could you say so 
in the PR description, add a test for it, and consider whether this part should 
be backported?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to