Repository navigation
Conversation
`Record.__eq__` compared the underlying lists, so two separately decoded NaN values compared unequal while their `str`-based hashes matched. `DeleteFileIndex` keys partition-scoped position deletes by partition `Record`, so those deletes never applied to data files in a NaN identity partition, and scans returned deleted rows. Commit validation missed concurrently deleted files in NaN partitions for the same reason. Compare elementwise when the lists differ, counting NaN floats at the same position as equal. The hash is unchanged. Closes apache#4279
_build_partition_predicate emitted EqualTo for every non-null partition value. Expression literals reject NaN, so deleting, overwriting, or upserting a file in a NaN identity partition raised, as did a dynamic partition overwrite with NaN in the incoming data. Closes apache#4277
hcrosse
marked this pull request as ready for review
November 4, 2026 01:00
hcrosse
marked this pull request as draft
November 4, 2026 01:00
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The equality fast path repeats comparisons for unequal nested Records, causing exponential work with nesting depth instead of a single linear pass.
1 open issue
What changed in this PR
This PR fixes partition predicate construction for NaN identity partitions, allowing affected delete and replacement operations to proceed. It includes the dependency that makes NaN partition records compare equal for delete lookups and conflict validation.
Changes:
- Emit
IsNaNfor NaN partition values while preserving NULL and finite-value handling. - Treat corresponding NaN floats as equal in
Record. - Add regression coverage for file replacement, partition lookups, and concurrent-delete validation.
| File | Description |
|---|---|
pyiceberg/table/__init__.py |
Build NaN-aware partition predicates. |
pyiceberg/typedef.py |
Add NaN-aware record equality. |
tests/test_typedef.py |
Cover NaN record equality and set deduplication. |
tests/table/test_validate.py |
Check concurrent deletion conflicts in NaN partitions. |
tests/table/test_init.py |
Verify predicates for NaN, NULL, and finite values. |
tests/table/test_delete_file_index.py |
Verify delete lookup using NaN partition records. |
tests/table/test_commit_retry.py |
Reject replacements conflicting with concurrent position deletes. |
tests/catalog/test_catalog_behaviors.py |
Exercise deletes and overwrites in NaN identity partitions. |
Review effort: Balanced
Comment on lines
+207
to
+211
| if self._data == other._data: | ||
| return True | ||
| if len(self._data) != len(other._data): | ||
| return False | ||
| for a, b in zip(self._data, other._data, strict=True): |
Contributor
Author
There was a problem hiding this comment.
This comes from the commit this PR includes from #4280 and is fixed there. This PR picks up the fix when it merges main after that PR lands.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Closes #4277
Rationale for this change
delete(),overwrite(),upsert()anddynamic_partition_overwrite()raiseValueError: Cannot create expression literal from NaNwhen they touch a NaN identity partition._build_partition_predicatebuildsEqualTofor each non-NULL partition value, and expression literals reject NaN. I changed_build_partition_predicateto emitIsNaNfor NaN partition values next to the existingIsNullbranch.This PR depends on #4280. On main,
Recordcompares two NaN partition values as unequal, so commit validation misses a concurrent position delete in a NaN partition. Today the replacement raises before validation runs. With only this change, a stale file replacement would commit over that delete and bring the deleted row back. This PR includes both commits, and only the second one belongs to it.Dynamic overwrites with NaN keys used to raise. They can now reach two existing bugs, both of which need a file written under an older partition spec.
delete()filter already loses the row on main. The open Keep rows where a delete filter evaluates to null #4219 keeps the row for that filter. I did not test the dynamic-overwrite case against it.delete()drops NaN rows that do not match the filter #4130. Finite keys already reach it on main.upsert()with NaN in a join column still raises, becausecreate_match_filterbuilds literals from the join keys. That path is separate from this change.Are these changes tested?
Yes. I added three tests.
test_replace_files_in_nan_identity_partitioncovers whole-file deletion, partial rewriting, overwrite and dynamic overwrite in a NaN identity partition, on the memory and SQL catalogs.test_build_partition_predicate_with_nan_and_nullchecks the predicate for NaN, NULL and finite values.test_file_overwrite_validates_concurrent_position_delete_in_nan_partitionstages a file replacement, then commits a partition-scoped position delete from another table handle. It checks that the replacement fails validation and the deleted row stays gone, on the memory and SQL catalogs. PyIceberg cannot write delete files, so the helper commits the delete throughoverwrite()with_OverwriteFiles.new_manifest_writerpatched to write a delete manifest.All three fail on main with the NaN literal error and pass with both commits. Without the dependency, the concurrent-delete test fails because the stale replacement commits.
I also ran
upsert()on a row in the NaN partition anddelete(AlwaysTrue()). Both raise on main and succeed with the fix. These cases are not in the committed tests.The full unit suite with both commits gives 4482 passed, 4 failed and 5 skipped, versus 4472 passed, 4 failed and 5 skipped on main. Three of the ten new passes come from the dependency and seven from this PR. The same four Hive Kerberos tests fail on both because the
hive-kerberosextra cannot be installed in the test environment.prek run -apassed.Are there any user-facing changes?
Yes.
delete(),overwrite(),upsert()anddynamic_partition_overwrite()now work on NaN identity partitions, apart from the cases above. There is no API change. Please add thechangeloglabel.