paulcaron16k opened a new issue, #3758:
URL: https://github.com/apache/iceberg-python/issues/3758

   ### Apache Iceberg version
   
   main (reproduced at `154288fb`; the affected files are byte-identical at 
`7d0f5031`, which is `version = "0.12.0"`)
   
   ### Please describe the bug 🐞
   
   On a **partitioned** table, `upsert` leaves the row it replaced *and* 
duplicates a row it never touched. No error is raised.
   
   `0.11.1` is correct. An unpartitioned table is correct on both, so the 
partition spec is required to reproduce.
   
   ## Reproduction
   
   ```python
   import tempfile, pathlib, datetime as dt
   import pyarrow as pa
   from pyiceberg.catalog.sql import SqlCatalog
   from pyiceberg.schema import Schema
   from pyiceberg.partitioning import PartitionField, PartitionSpec
   from pyiceberg.transforms import DayTransform
   from pyiceberg.types import NestedField, StringType, IntegerType, 
TimestampType
   
   wh = pathlib.Path(tempfile.mkdtemp())
   cat = SqlCatalog("c", uri=f"sqlite:///{wh}/c.db", warehouse=f"file://{wh}")
   cat.create_namespace("db")
   schema = Schema(
       NestedField(1, "k", StringType(), required=False),
       NestedField(2, "v", IntegerType(), required=False),
       NestedField(3, "ts", TimestampType(), required=False),
   )
   spec = PartitionSpec(
       PartitionField(source_id=3, field_id=1000, transform=DayTransform(), 
name="ts_day")
   )
   t = cat.create_table("db.t", schema=schema, partition_spec=spec,
                        properties={"format-version": "2"})
   
   arrow = pa.schema([pa.field("k", pa.string()), pa.field("v", pa.int32()),
                      pa.field("ts", pa.timestamp("us"))])
   base = dt.datetime(2026, 1, 6, 12)
   def rows(pairs):
       return pa.table({"k": [p[0] for p in pairs],
                        "v": pa.array([p[1] for p in pairs], type=pa.int32()),
                        "ts": [base] * len(pairs)}, schema=arrow)
   
   t.append(rows([("a", 1), ("b", 1)]))
   t.refresh()
   t.upsert(rows([("a", 2)]), join_cols=["k"])
   t.refresh()
   
   arr = t.scan().to_arrow()
   print(sorted(zip(arr["k"].to_pylist(), arr["v"].to_pylist())))
   print("data files:", len(t.inspect.files().to_pylist()))
   ```
   
   | version | output | data files |
   |---|---|---|
   | 0.11.1 | `[('a', 2), ('b', 1)]` — correct | 2 |
   | main | `[('a', 1), ('a', 2), ('b', 1), ('b', 1)]` | 3 |
   
   Two problems in one result: the superseded `('a', 1)` survives beside its 
replacement, and `('b', 1)` — untouched by the upsert — is duplicated.
   
   ## Suspected cause
   
   `_OverwriteFiles._existing_manifests` keeps a manifest its evaluator does 
not match, whole:
   
   ```python
   manifest_evaluators: dict[int, Callable[[ManifestFile], bool]] = 
KeyDefaultDict(self._build_manifest_evaluator)
   ...
   if not manifest_evaluators[manifest_file.partition_spec_id](manifest_file):
       existing_files.append(manifest_file)
       continue
   ```
   
   The predicate comes from `_build_delete_files_partition_predicate`, built 
from the deleted files' partition values. For this upsert the join column is 
`k`, which is not a partition field, so the resulting predicate cannot select 
the manifest holding the rows being replaced. That manifest is appended intact, 
while the rewritten file and the new row are added alongside it, so a reader 
sees both copies.
   
   I have not tried to determine whether the right fix is to widen the 
predicate, to fall back to a full scan when the predicate cannot be projected 
onto a spec, or to match deleted entries directly — happy to test a patch.
   
   ## Knock-on effect
   
   The symptom most users will hit first is the *next* upsert, which fails in 
`upsert_util.get_rows_to_update`:
   
   ```
   ValueError: Target table has duplicate rows, aborting upsert
   ```
   
   That guard is doing its job. It is detecting real duplicates — just ones the 
previous `upsert` created.
   
   ## Why this may be worth looking at before 0.12
   
   `version` on main is already `0.12.0`, and the failure is silent: a 
merge-style ingest into a partitioned table accumulates duplicate rows with no 
error until a later upsert happens to trip the duplicate check.
   
   ### Willingness to contribute
   
   Would be interested in contributing a fix to Apache Iceberg.
   


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