HyukjinKwon opened a new pull request, #59933:
URL: https://github.com/apache/spark/pull/59933

   ### What changes were proposed in this pull request?
   
   A follow-up to SPARK-60101 (#59424), discussed there with @zhouyuan and 
@zhztheplayer. When the query of a batch or micro-batch write to a native data 
source produces columnar batches, the data writers now get the batches. Before, 
a `ColumnarToRowExec` converted them to rows first. A scan of a native data 
source is one such query. This PR is based on #59931 (version 1 of the 
interface), whose commit it includes until that PR is merged.
   
   - **Planning and execution.** An internal mixin, `SupportsColumnarInput` in 
`org.apache.spark.sql.internal.connector`, marks:
     - a `Write`, for appends and overwrites;
     - the `StreamingWrite` of a micro-batch.
   
     Its data writers are `ColumnarInputDataWriter`s, which also take columnar 
batches.
     - `V2TableWriteExec.supportsColumnarInput` tells whether a write node 
writes the batches of its query. Row-level operations do not: their tasks 
project the rows.
     - `ApplyColumnarRulesAndInsertTransitions` then inserts no transition to 
rows above a columnar query, as it already does for planned V1 writes.
     - `writeWithV2` runs `DataWritingSparkTask.runColumnar`, which shares the 
commit protocol of `WritingSparkTask` (moved into `writeAndCommit`). It 
releases each batch with `closeIfFreeable` once written, as `ColumnarToRowExec` 
does.
   - **Copying the batches.** The data writer of the columnar adapter 
(`ArrowBatchingDataWriter`) copies the Arrow vectors of a batch buffer by 
buffer with `VectorAppender` when they are physically congruent with the Arrow 
schema of the write (`ArrowUtils.isCompatibleWithDeclaredField`), as the 
vectors of a native scan are. It converts other columns value by value with 
`ArrowWriter`, without going through `UnsafeRow`s. Either way, the batches end 
at `spark.sql.execution.arrow.maxRecordsPerBatch` rows, or once they hold 
`spark.sql.execution.arrow.maxBytesPerBatch` bytes, as the header promises. A 
batch is copied rather than passed as it is for two reasons:
     - The native data writer may move the arrays that it gets, to keep them 
after the call.
     - The producer of a `ColumnarBatch` may overwrite its vectors once the 
batch is consumed, as `ArrowWriter.reset()` does.
   
     Arrow vectors are sliced before they are copied, because `VectorAppender` 
assumes that the offsets of a vector start at 0, which they need not in an 
array imported through the C data interface.
   
   Under adaptive query execution, the query of a write is an 
`AdaptiveSparkPlanExec`, which is not columnar, so the batches only reach the 
write when the query has no shuffle, as for a plain copy from one native data 
source to another. A columnar rule can mark the adaptive plan columnar, as 
`CachedBatchSerializer.convertToColumnarPlanIfPossible` does.
   
   ### Why are the changes needed?
   
   The batches of a native scan were converted to rows by `ColumnarToRowExec`, 
and then back to Arrow by the data writer. With this change, a copy from one 
native data source to another stays in Arrow, at the cost of a copy of the 
buffers. A native engine can also write its batches to a native sink without 
converting them to rows.
   
   ### Does this PR introduce _any_ user-facing change?
   
   No. The written data is the same. The plan of such a write no longer has a 
`ColumnarToRow` node.
   
   ### How was this patch tested?
   
   New tests in `ColumnarDataSourceSuite`:
   - "write the columnar batches of a query" checks the plan and the batch 
sizes for on-heap and Arrow batches. It also checks a data writer that keeps 
the data of its batches, through the C data interface, while the scan 
overwrites its vectors with each batch.
   - "copy the Arrow columns of every type, or convert the columns of a batch" 
covers every type that Spark converts to Arrow, nulls, slices at offsets that 
are not aligned to bytes, the limits in rows and in bytes, Arrow types other 
than those of the schema, and on-heap vectors. Each case checks the data that 
the data writer kept after the producer overwrote the batch.
   
   The plans of the batch and streaming writes of `NativeDataSourceSuite`, and 
of "streaming read and write" in `ColumnarDataSourceSuite`, are now also 
checked.
   
   Replacing the copy with the slices of the batch makes the tests of kept data 
fail. `DataSourceV2Suite`, `SparkSessionExtensionSuite`, `ColumnarRulesSuite`, 
`DeltaBasedUpdateTableSuite`, `DeltaBasedMergeIntoTableSuite`, 
`StreamingDataSourceV2Suite` and `V1WriteFallbackSuite` pass.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Claude Opus 5.5)
   
   This pull request and its description were written by Isaac.
   


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