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

   ## Which issue does this PR close?
   
   Closes #6087.
   
   ## Rationale for this change
   
   `ArrowWriter.writeColumns` drives its loop from the width of the input 
`ColumnarBatch` while
   indexing the writer's fields, which are built from the schema the batch is 
being written under:
   
   ```scala
   while (columnIndex < input.numCols()) {
     fields(columnIndex).writeColumnSlice(input.column(columnIndex), startRow, 
numRows)
   ```
   
   That assumes every producer hands over a batch exactly as wide as the 
schema. Iceberg's vectorized
   reader does not. `BaseBatchReader.BatchDeleteFilter.filterBatch` reads with
   `deletes.requiredSchema()`, which carries `_pos` after the projected columns 
when a data file has
   position deletes, and it only trims the extras back with 
`ColumnarBatchUtil.removeExtraColumns`
   when the file also has equality deletes. A merge-on-read UPDATE writes 
position deletes and no
   equality deletes, so the extra column survives into the batch.
   
   Caching such a relation crashes. `InMemoryRelation` passes 
`cachedPlan.output` as the cache schema,
   and because `supportsColumnarInput` is true Spark strips the `ColumnarToRow` 
above the cached plan,
   so the serializer receives the scan's batches unaltered. Caching `SELECT 
name FROM t` over
   `id INT, name STRING` then gives the writer a 2-column batch and 1 field:
   
   ```
   java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
     at ArrowWriter.writeColumns(ArrowWriters.scala:171)
     at 
CometArrowConverters$.columnarBatchToArrowBatch(CometArrowConverters.scala:102)
     at 
ArrowCachedBatchSerializer.$anonfun$encodeBatches$1(ArrowCachedBatchSerializer.scala:231)
   ```
   
   Comet's own row path already does the right thing: `ArrowWriter.write(row)` 
loops `fields.length`.
   So does Spark's `ArrowCachedBatchSerializer` (SPARK-57268), which converts a 
non-Arrow batch
   through `batch.rowIterator()` and the same schema-driven writer. 
`writeColumns` was the one path
   that trusted the batch instead of the schema.
   
   ## What changes are included in this PR?
   
   `writeColumns` is now driven by the writer's fields rather than the input's 
width, so trailing
   columns the schema does not describe are ignored. Those are exactly the 
columns Iceberg itself
   discards when it trims, since `removeExtraColumns` keeps the leading 
`expectedSchema` prefix.
   
   The opposite mismatch is a real contract violation and stays fatal, but it 
now fails with a message
   that names both widths instead of an `ArrayIndexOutOfBoundsException` from 
inside the write loop.
   
   The direct-write path needed no change: `CachedBatchIpc.matchesReaderLayout` 
already requires
   `batch.numCols() == readerFields.length`, so a wider batch declines it and 
falls into the
   conversion path this PR fixes.
   
   ## How are these changes tested?
   
   Two tests in `CometArrowStreamSuite`, which covers this boundary contract 
directly: a batch wider
   than the schema is written as the schema describes it, with the trailing 
column ignored and the
   projected values intact; a batch narrower than the schema is refused, with 
the allocator left at
   zero so the failure does not leak the partially built root.
   


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