comphead opened a new pull request, #6004:
URL: https://github.com/apache/datafusion-comet/pull/6004

   ## Which issue does this PR close?
   
   Closes #5801.
   
   Also closes the duplicate-name half of #5605 in the two places it was still 
reachable (any operator, and the Arrow cache serializer), so I have referenced 
rather than closed that one.
   
   This does **not** close #5783. See the note at the end for why a plan-time 
check cannot reach it.
   
   ## Rationale for this change
   
   Two separate things make a struct's children impossible to resolve 
one-to-one, and Comet handled each of them in only part of the code.
   
   **Duplicate child names.** Java Arrow keys a struct vector's children by 
name, so `struct<a, a>` loses a child on the way back across the C data 
interface (#5605). `DataTypeSupport` declines the shape, which covers the scan 
type checker, the native shuffle predicate and the two row-conversion sinks. 
Nothing covers the remaining operators, and nothing covered the Arrow cache 
serializer: `ArrowCachedBatchSerializer.supportsType` accepted `struct<a, a>`, 
so
   
   ```scala
   spark
     .sql("SELECT id AS key, named_struct('a', id, 'a', id + 1) AS st FROM 
range(1000)")
     .createOrReplaceTempView("t")
   spark.catalog.cacheTable("t")
   ```
   
   stored the relation as a `CometCachedBatch`, a format the native scan over 
that cache can never import back.
   
   **Duplicate Parquet field ids.** Under 
`spark.sql.parquet.fieldId.read.enabled` Spark resolves each requested field to 
the one Parquet field carrying its id, and raises 
`FOUND_DUPLICATE_FIELD_IN_FIELD_ID_LOOKUP_MODE` when more than one answers. 
Comet never looked at field ids at all. DataFusion 55's opener skips the 
expression adapter when the file's physical schema compares equal to the 
logical schema and no predicate is pushed, so the file in #5801 was read 
positionally and Comet returned rows where Spark raises.
   
   ## What changes are included in this PR?
   
   1. **`DataTypeSupport`** gains `hasDuplicateFieldNames` plus two recursive 
checks, `findDuplicateStructFieldNames` and `findDuplicateStructFieldIds`, that 
descend through structs, arrays and maps and describe the first offending 
struct with its path. The existing trait check now calls the shared predicate, 
so the definition of "duplicate" lives in one place.
   
      Only nested structs are inspected. A plan's top-level output attributes 
routinely repeat a name (a self-join gives two `id` columns, told apart by 
expression id) and Comet matches top-level columns positionally, so those are 
not duplicates in this sense.
   
   2. **`CometExecRule.tryConvertToComet`** declines any operator whose own 
output, or a data-producing child's output, carries a duplicate-named struct. 
This sits beside the existing `VariantType` gate, on the one path every 
operator conversion takes, so the invariant holds for every serde rather than 
for the four places that happened to check.
   
   3. **`CometScanTypeChecker`** declines a scan whose requested schema repeats 
a Parquet field id, when field id matching is on. The read goes back to Spark, 
which raises the ambiguity error. With field id matching off the two fields are 
told apart by name and the scan stays native.
   
   4. **`ArrowCachedBatchSerializer.supportsType`** rejects duplicate child 
names, so such a relation is cached in Spark's default format instead of a 
Comet format that can never be read back natively.
   
   ## How are these changes tested?
   
   New and updated tests, all on the Spark 4.1 profile:
   
   - **`DataTypeSupportSuite`** (new): both recursive checks at every nesting 
level (struct child, array element, map key, map value, two levels deep), the 
accepted cases including names that differ only by case, a non-integral field 
id, and that the name check and the id check are independent of each other.
   - **`CometNativeReaderSuite`**: #5801 end to end. A Parquet file written 
with no key-value metadata and schema `s<x id=1, y id=1>`, read back with that 
same schema. Comet now raises Spark's `Found duplicate field(s) "1": [x, y] in 
id mapping mode` and the plan carries no `CometNativeScanExec`. With 
`spark.sql.parquet.fieldId.read.enabled=false` the scan stays native and 
returns the row, which pins that the new gate is scoped to field id matching.
   - **`CometInMemoryCacheSuite`**: a relation with `named_struct('a', id, 'a', 
id + 1)` is cached as `DefaultCachedBatch`, reads back correctly, and produces 
no `CometInMemoryTableScan`. This test fails on `main`, where the relation is 
cached in Comet's Arrow format.
   
   Existing suites run locally and green: `CometExecRuleSuite`, 
`CometScanRuleSuite`, `CometNativeShuffleSuite`, `CometInMemoryCacheSuite`, 
`CometNativeReaderSuite`, `CometShuffleSuite`, `DisableAQECometShuffleSuite`, 
`ParquetReadV1Suite`, `CometFuzzTestSuite`. `CometExpressionSuite` has two 
pre-existing `DatePart`/`dayofweek` failures in my environment that reproduce 
unchanged on the base commit and are unrelated to this change.
   
   Applying `run-spark-4.1-tests`, since this touches the conversion path every 
plan goes through.
   
   ## Why this does not close #5783
   
   #5783 is the other half of the duplicate-name story: a Parquet file whose 
struct has two byte-identical child names, read with a declared schema naming 
one of them.
   
   ```scala
   spark.range(3).selectExpr("named_struct('dup', id, 'dup', id + 100) as 
s").write.parquet(p)
   spark.read.schema("s struct<dup: bigint>").parquet(p).collect()  // Spark 3 
rows, Comet 6
   ```
   
   The duplicate exists only in the file. Spark's analyzer rejects a declared 
read schema that repeats a nested name with `COLUMN_ALREADY_EXISTS`, and its 
Parquet schema inference rejects such a file for the same reason, so the 
requested schema Comet sees is `struct<dup>` with nothing in it to detect. I 
confirmed this against all three shapes in the issue: none of them puts the 
duplicate anywhere a planning rule can see it, so no plan-time fallback reaches 
them. That fix belongs in the native reader, which is what #5786 and #5654 are 
for.
   


-- 
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