yhyyz opened a new issue, #18609:
URL: https://github.com/apache/iceberg/issues/18609

   ### Apache Iceberg version
   
   1.12.0 (latest release)
   
   ### Query engine
   
   Flink
   
   ### Please describe the bug 🐞
   
   ### Summary
   
   The Flink `ConvertEqualityDeletes` maintenance task (added in 1.12.0) fails 
on every conversion cycle when the equality field columns include a `DATE` 
column. No cycle ever commits. Because the converter requires the partition 
source columns to be part of the equality fields, this affects any table 
partitioned by a date column, which is a very common layout for CDC and log 
tables.
   
   ### Environment
   
   - `iceberg-flink-runtime-1.20-1.12.0.jar`
   - Flink 1.20.0
   - Format version 3 table, Parquet data files
   - Writer: Flink upsert sink writing to a `staging` branch
   - Converter: a separate Flink job running `TableMaintenance` + 
`ConvertEqualityDeletes` with `stagingBranch("staging")` and 
`targetBranch("main")`
   
   ### Steps to reproduce
   
   1. Create a v3 table:
   
      ```sql
      CREATE TABLE t (
        id BIGINT,
        d  DATE,
        v  STRING,
        PRIMARY KEY (id, d) NOT ENFORCED
      ) PARTITIONED BY (d)
      WITH ('format-version' = '3', 'write.upsert.enabled' = 'true');
      ```
   
   2. With Flink, write to the `staging` branch: insert a row, then update the 
same key, so that equality delete files are produced. The same failure also 
happens with `write.upsert.enabled=false` (changelog input with `-U`/`+U`).
   
   3. Start the converter:
   
      ```java
      TableMaintenance.forTable(env, tableLoader)
          .add(
              ConvertEqualityDeletes.builder()
                  .stagingBranch("staging")
                  .targetBranch("main")
                  .equalityFieldColumns(List.of("id", "d"))
                  .scheduleOnInterval(Duration.ofSeconds(60)))
          .append();
      ```
   
   ### Expected behavior
   
   Equality deletes on `staging` are resolved to deletion vectors and committed 
to `main`.
   
   ### Actual behavior
   
   Every cycle fails in `EqualityConvertReader`. The committer logs `Skipping 
commit ... a DV writer reported an error` and the `TaskResult` has 
`success=false`:
   
   ```
   java.lang.ClassCastException: class java.time.LocalDate cannot be cast to 
class java.lang.Integer (java.time.LocalDate and java.lang.Integer are in 
module java.base of loader 'bootstrap')
        at 
org.apache.iceberg.types.Conversions.toByteBuffer(Conversions.java:100)
        at 
org.apache.iceberg.types.Conversions.toByteBuffer(Conversions.java:87)
        at 
org.apache.iceberg.flink.maintenance.operator.StructLikeSerializer.writeField(StructLikeSerializer.java:135)
        at 
org.apache.iceberg.flink.maintenance.operator.StructLikeSerializer.serializeKey(StructLikeSerializer.java:63)
        at 
org.apache.iceberg.flink.maintenance.operator.EqualityConvertReader.processDataFile(EqualityConvertReader.java:168)
        at 
org.apache.iceberg.flink.maintenance.operator.EqualityConvertReader.processElement(EqualityConvertReader.java:115)
   ```
   
   As a control, the same pipeline works on an unpartitioned table whose only 
equality field is a `STRING` column. The converted `main` branch matched the 
source data exactly (row count and checksums).
   
   ### Analysis
   
   All references are to the `apache-iceberg-1.12.0` tag, `flink/v1.20`:
   
   - `EqualityConvertReader` reads data files 
([L157](https://github.com/apache/iceberg/blob/apache-iceberg-1.12.0/flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertReader.java#L157))
 and equality delete files 
([L201](https://github.com/apache/iceberg/blob/apache-iceberg-1.12.0/flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertReader.java#L201))
 with `FormatModelRegistry.readBuilder(file.format(), Record.class, input)`. 
This yields generic `Record`s, where a `DATE` value is a `java.time.LocalDate`.
   - The record is then passed directly to 
`StructLikeSerializer.serializeKey(record, keySchema.asStruct())` 
([L168](https://github.com/apache/iceberg/blob/apache-iceberg-1.12.0/flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertReader.java#L168),
 
[L204](https://github.com/apache/iceberg/blob/apache-iceberg-1.12.0/flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertReader.java#L204)).
   - `StructLikeSerializer.writeField` calls `struct.get(pos, Object.class)` 
and hands the value to `Conversions.toByteBuffer(fieldType, value)` 
([L128-L135](https://github.com/apache/iceberg/blob/apache-iceberg-1.12.0/flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/StructLikeSerializer.java#L128-L135)).
   - `Conversions.toByteBuffer` expects the internal representation: for `DATE` 
it casts `(int) value` (days since epoch), so a `LocalDate` fails.
   
   The code on `main` is the same, in all of `flink/v1.20`, `flink/v2.2` and 
`flink/v2.3`.
   
   The same mismatch between generic and internal representations exists for 
`TIME`, `TIMESTAMP` and `TIMESTAMPTZ` (`LocalTime` / `LocalDateTime` / 
`OffsetDateTime` versus `Long`). I only reproduced the `DATE` case, but tables 
partitioned by a transform on a timestamp column (for example `days(ts)`) are 
likely affected as well.
   
   ### Test coverage
   
   The existing reader and end-to-end tests use `SimpleDataUtil.SCHEMA` (`id 
INT`, `data STRING`). `TestStructLikeSerializer` covers `DATE` only for 
partition encoding, and it passes the internal value (an `int`). Key 
serialization is tested only for INT, STRING and DECIMAL, so this path was not 
exercised.
   
   ### Possible fix
   
   Before calling `serializeKey`, wrap the generic record with 
`org.apache.iceberg.data.InternalRecordWrapper`, built from the projected read 
schema. Do this in both the data file path (`readSchema`, which is `keySchema` 
plus `_pos`) and the equality delete file path (`keySchema`):
   
   ```java
   // processDataFile
   InternalRecordWrapper wrapper = new 
InternalRecordWrapper(readSchema.asStruct());
   ...
   SerializedEqualityValues key =
       fieldSerializer.serializeKey(wrapper.wrap(record), keySchema.asStruct());
   
   // processDeleteFile
   InternalRecordWrapper wrapper = new 
InternalRecordWrapper(keySchema.asStruct());
   ...
   SerializedEqualityValues key =
       fieldSerializer.serializeKey(wrapper.wrap(record), keySchema.asStruct());
   ```
   
   I have a fix with a regression test (a v3 table partitioned by a `DATE` 
column, with `DATE`, `TIME`, `TIMESTAMP` and `TIMESTAMPTZ` equality fields). 
The test fails with this `ClassCastException` on current `main` and passes with 
the fix. I will open a PR.
   
   ### Willingness to contribute
   
   - [x] I can contribute a fix for this bug independently
   - [ ] I would be willing to contribute a fix for this bug with guidance from 
the Iceberg community
   - [ ] I cannot contribute a fix for this bug at this time
   


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