hzmsh opened a new pull request, #4115:
URL: https://github.com/apache/iceberg-python/pull/4115

   # Rationale for this change
   
   If you stop partitioning by a column and then drop that column, the next 
`delete()` breaks the table. The commit succeeds, but after that neither 
PyIceberg nor Spark can read the table.
   
   ```python
   import pyarrow as pa
   
   from pyiceberg.catalog.memory import InMemoryCatalog
   
   catalog = InMemoryCatalog("default")
   catalog.create_namespace("default")
   data = pa.Table.from_pydict({"id": [1, 2], "category": ["a", "b"]})
   tbl = catalog.create_table("default.t", schema=data.schema)
   with tbl.update_spec() as update:
       update.add_identity("category")
   tbl.append(data)
   
   with tbl.update_spec() as update:
       update.remove_field("category")   # 1. stop partitioning by category
   with tbl.update_schema() as update:
       update.delete_column("category")  # 2. drop the column
   
   tbl.delete("id == 1")                 # 3. commits fine
   tbl.scan().to_arrow()                 # main: IndexError, expected: id = [2]
   ```
   
   **What goes wrong**
   
   - The old partition spec still has a `category` field, but the column is 
gone, so its type becomes `unknown`.
   - `unknown` is stored in Avro as `"null"`.
   - When the manifest is rewritten, PyIceberg makes the field optional by 
wrapping it as `["null", <type>]`, which gives `["null", "null"]`.
   - Avro doesn't allow the same type twice in a union. PyIceberg fails with 
`IndexError`, Spark with `Duplicate in union:null`.
   
   **When it started**
   
   - Before #3899 (including the 0.12.0 release): step 3 raises an error. The 
delete fails, but the table is untouched and still readable.
   - After #3899 (merged to `main`, not released yet): step 3 succeeds but 
writes a broken manifest, so the whole table becomes unreadable.
   - So today this only affects people running `main`. Once 0.13.0 is released, 
it would affect everyone who upgrades.
   
   **Fix**
   
   - If a field's type is `unknown`, write it as plain `"null"` with no union. 
It's changed in the 3 places that build the Avro schema and the writers.
   - This is what Java does: unknown maps to `NULL` 
([TypeToSchema.java](https://github.com/apache/iceberg/blob/9120cfa789d4ea29acd22c95973774f6a813a212/core/src/main/java/org/apache/iceberg/avro/TypeToSchema.java#L216-L217)),
 and `NULL` is never wrapped in a union 
([AvroSchemaUtil.java](https://github.com/apache/iceberg/blob/9120cfa789d4ea29acd22c95973774f6a813a212/core/src/main/java/org/apache/iceberg/avro/AvroSchemaUtil.java#L181-L192)).
   - Java has written unknown this way since apache/iceberg#12455. #3899 ported 
a different part of Java's fix for this bug (apache/iceberg#14356), but not 
this one.
   
   **Reports from users**
   
   - apache/iceberg#14343: same steps in Java (remove partition field, drop the 
column, then delete). Fixed in apache/iceberg#14356.
   - apache/iceberg#10234: Spark users also had their tables break after 
dropping an old partition column (an older, different error).
   
   ## Are these changes tested?
   
   Yes.
   
   - New tests:
     - `tests/avro/test_file.py::test_write_optional_unknown_type`: write an 
optional unknown field, read it back.
     - 
`tests/catalog/test_catalog_behaviors.py::test_delete_after_dropping_old_partition_source_column`:
 the example above, v1 and v2, every catalog fixture.
     - All 7 fail on `main` and pass with this change (pure Python and Cython 
Avro decoders).
   - Unit tests on Linux: 4134 passed vs 4127 on `main`; the difference is the 
7 new tests. 9 other failures are the same on both (optional packages I don't 
have).
   - Integration tests (Docker): 1348 passed. The other 10 need `ray` or 
`daft`, which I don't have installed, and fail the same way on `main`.
   - Spark check: ran the example on the REST catalog and read the table with 
Spark. `main`: `Duplicate in union:null`. This PR: `[2]` for v1 and v2.
   - `ruff check` and `ruff format --check` pass.
   
   ## Are there any user-facing changes?
   
   Yes, a bug fix.
   
   - `delete()` and `overwrite()` after dropping an old partition column now 
write readable manifests.
   - Not covered: on v1 tables, `overwrite()` in this case still fails earlier 
with `Could not find field with id: 2`, with or without this change. That's a 
separate issue.
   - Files already written as `["null", "null"]` by an unreleased `main` build 
are not repaired.
   
   Since #3899 isn't released yet, could this go into the 0.13.0 milestone?
   
   ---
   
   Written with AI assistance (Claude Code, Claude Opus 5.5). The Java 
references were read from `apache/iceberg` `main`, and the tests above were run 
on this branch.
   
   <!-- In the case of user-facing changes, please add the changelog label. -->
   


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