feat(iceberg): add upsert support to write_iceberg#7255
Open
DrishyaShah wants to merge 2 commits into
Open
Conversation
Adds mode=upsert to DataFrame.write_iceberg and IcebergTable.upsert(), delegating to PyIceberg Table.upsert() (requires pyiceberg>=0.9.0). Matched rows are updated, unmatched rows are inserted. Returns a summary DataFrame with rows_updated and rows_inserted counts. Closes Eventual-Inc#3844 Co-authored-by: Claude Code <noreply@anthropic.com>
Contributor
Greptile SummaryThis PR adds Iceberg upsert support to the write APIs. The main changes are:
Confidence Score: 4/5The upsert path needs a fix for partial update inputs before merging.
daft/dataframe/dataframe.py Important Files Changed
Reviews (1): Last reviewed commit: "feat(iceberg): add upsert support to wri..." | Re-trigger Greptile |
mode="upsert" previously null-padded any table column missing from the input DataFrame before calling PyIceberg's upsert(), which replaces matched rows in full -- so omitted columns were silently overwritten with NULL on update. PyIceberg's upsert() has no partial-column update path (it errors on schema mismatch internally), so require the full column set upfront with a clear ValueError instead of padding. Also validates join_cols reference real DataFrame columns, replacing a raw KeyError from inside PyIceberg with an actionable error. Addresses Greptile review feedback on the upsert PR.
srilman
requested changes
Jul 21, 2026
srilman
left a comment
Contributor
There was a problem hiding this comment.
@DrishyaShah I would prefer not adding this to Daft itself because unlike the other modes in write_iceberg which works incrementally, upsert seems to materializes all outputs upfront (via the self.to_arrow()), which will likely cause an OOM. Rather than using the PyIceberg upsert API, it would be cool if we had some more native support instead. Ideally it:
- Does the merging incrementally rather than materializing all at once
- Works in both native and distributed
What do you think? Would love to see an issue is this is interesting to you
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.
Changes Made
Adds
mode="upsert"toDataFrame.write_iceberg, delegating to PyIceberg'sTable.upsert()(requires pyiceberg>=0.9.0). A newjoin_colsparameterspecifies the column(s) used to match existing rows: matched rows are
updated, unmatched rows are inserted. Also adds a corresponding
IcebergTable.upsert(df, join_cols)convenience method on the catalog API,mirroring the existing
append/overwritemethods.Validation added:
Table.upsertwas introduced upstream)join_colsto be non-emptycheckpoint=...combined withmode="upsert"(not supported)Return value differs from
append/overwrite: instead of file-levelADD/DELETE operations,
upsertreturns a 1-row summary DataFrame withrows_updatedandrows_inserted, taken from PyIceberg'sUpsertResult.Note: unlike
append/overwrite, the upsert path materializes the fullDataFrame into a single local PyArrow table via
to_arrow()before handingit to PyIceberg. This isn't avoidable in this PR — PyIceberg's own
upsert()API only accepts one in-memorypa.Table, so there's nodistributed write path available for this mode yet.
Docs updated in
docs/connectors/iceberg.md(new usage example, and theFAQ entry that previously said upserts weren't supported).
AI Usage
Implementation was written with Claude Code assistance and manually
verified by the author. Additionally verified in review: full test suite
run (94 passed, 1 skipped, including 3 new upsert tests), PyIceberg's
Table.upsert()signature andUpsertResultfields cross-checked againstan installed pyiceberg 0.11, lint clean on changed lines, and the branch
rebased onto current upstream
mainwith no residual conflicts.Related Issues
Closes #3844