andygrove opened a new issue, #7049:
URL: https://github.com/apache/datafusion-comet/issues/7049

   ### What is the problem the feature request solves?
   
   On Spark 3.5 and later, when a nullable input feeds a `NOT NULL` column of a 
V2 table, Spark wraps the value in `AssertNotNull`, which checks each row at 
run time. That applies to top-level columns and to required fields inside a 
struct, where Spark rebuilds the struct as `named_struct('a', 
assertnotnull(s.a), ...)`. Spark 3.4 rejects such writes at analysis instead 
(`Cannot write nullable values to non-null column`).
   
   Comet has no native `AssertNotNull`, so the projection that carries it stays 
in Spark. When nothing separates that projection from the writer, 
`CometIcebergWriteExec` gets no Arrow input and the whole write falls back to 
iceberg-java:
   
   ```
   Cannot perform native operation because input is not in Arrow format; 
assertnotnull is not supported; unsupported arguments for CreateNamedStruct
   ```
   
   That is the case for unpartitioned tables and for 
`write.distribution-mode=none`. Iceberg requires identifier fields to be `NOT 
NULL`, so a table keyed for upserts hits it on every `INSERT ... SELECT` from a 
nullable source. When an exchange does sit between the projection and the 
writer, as with a partitioned table's default hash distribution or the 
rebalance in a copy-on-write `UPDATE`, the write stays native: the Spark 
projection checks the rows and the columnar shuffle hands Arrow batches to the 
writer.
   
   Measured on `main` at c230bf4e98 with a Parquet source whose columns are 
nullable but hold no NULLs, on Spark 3.5.9 (Iceberg 1.8.1) and 4.1.3 (Iceberg 
1.11.0). The bucketed tables ran on 4.1.3 only.
   
   | Statement | `id INT NOT NULL` | `s STRUCT<a: INT NOT NULL, b: STRING>` | 
`id INT NOT NULL`, `bucket(4, id)` | `id INT NOT NULL`, `bucket(4, id)`, 
`write.distribution-mode=none` |
   | --- | --- | --- | --- | --- |
   | `INSERT INTO t SELECT * FROM src` | falls back | falls back | native | 
falls back |
   | `spark.table("src").writeTo("t").append()` | falls back | falls back | 
native | falls back |
   | `INSERT INTO t VALUES (...)` | native | native | native | native |
   | `INSERT INTO t SELECT coalesce(id, -1), ...` | native | native | native | 
native |
   | copy-on-write `UPDATE` | native | native | native | native |
   
   A `NOT NULL` struct column (`s STRUCT<...> NOT NULL`) behaves like the 
nested case. When the input does hold a NULL, the fallback raises Spark's 
error, as it should.
   
   ### Describe the potential solution
   
   A native `AssertNotNull` that passes its child through and fails on the 
first NULL with Spark's error, where `<path>` is the walked type path (for 
example `s` then `a`):
   
   - Spark 4.x: `SparkRuntimeException` `[NOT_NULL_ASSERT_VIOLATION] NULL value 
appeared in non-nullable field: <path>`.
   - Spark 3.5: `java.lang.NullPointerException: Null value appeared in 
non-nullable field: <path>`.
   
   The `CreateNamedStruct` reason in the nested case comes from the same 
expression, because its argument is the `AssertNotNull`.
   
   ### Additional context
   
   Found in a nested-type audit of the native writers. Native Iceberg writes 
are on by default in 1.2.0 (#6664). V1 file tables never have `NOT NULL` 
columns, so the native Parquet writer doesn't see `AssertNotNull`. Until there 
is a native one, writes stay native when the input is not nullable (for example 
through `coalesce`) or when an exchange sits in front of the writer.
   
   Part of #5649.
   


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