This is an automated email from the ASF dual-hosted git repository.
laskoviymishka pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git
The following commit(s) were added to refs/heads/main by this push:
new 2b67a12a8 refactor(table)!: consolidate ReplaceFiles APIs (#2309)
2b67a12a8 is described below
commit 2b67a12a8437aa3ef3df630029cdeadc30494a89
Author: Minh Vu <[email protected]>
AuthorDate: Thu Oct 29 17:12:07 2026 +0100
refactor(table)!: consolidate ReplaceFiles APIs (#2309)
refactor(table)!: remove ReplaceFilesWithDeleteFiles in favor of NewRewrite
Keep the five-argument ReplaceFiles signature unchanged. Route delete-file
replacements through NewRewrite and AddDeleteFileWithDataSequenceNumber,
which records a replace snapshot. Update regression tests and document the
migration in the release notes.
Signed-off-by: Minh Vu <[email protected]>
Co-authored-by: Andrei Tserakhau <[email protected]>
---
table/replace_files_test.go | 142 ++++++++++++++++++++++++++------------------
table/transaction.go | 30 ----------
website/src/releases.md | 1 +
3 files changed, 84 insertions(+), 89 deletions(-)
diff --git a/table/replace_files_test.go b/table/replace_files_test.go
index 065aed728..7a0810aa9 100644
--- a/table/replace_files_test.go
+++ b/table/replace_files_test.go
@@ -108,6 +108,25 @@ func newRewriteDeletionVector(t *testing.T, path, ref
string, offset, length *in
return builder.Build()
}
+type rewriteDeleteAddition struct {
+ File iceberg.DataFile
+ DataSequenceNumber int64
+}
+
+// stageDeleteFileRewrite stages a row-preserving delete-file rewrite via the
+// public builder, preserving the source delete files' data sequence numbers.
+func stageDeleteFileRewrite(ctx context.Context, tx *table.Transaction,
toRemove []iceberg.DataFile, toAdd []rewriteDeleteAddition) error {
+ rewrite := tx.NewRewrite(nil)
+ for _, file := range toRemove {
+ rewrite.DeleteFile(file)
+ }
+ for _, addition := range toAdd {
+ rewrite.AddDeleteFileWithDataSequenceNumber(addition.File,
addition.DataSequenceNumber)
+ }
+
+ return rewrite.Commit(ctx)
+}
+
func TestReplaceFiles_DataAndDeleteFiles(t *testing.T) {
tbl := newReplaceFilesTestTable(t)
@@ -192,7 +211,7 @@ func TestReplaceFiles_DataAndDeleteFiles(t *testing.T) {
assert.Equal(t, table.OpOverwrite, snap.Summary.Operation)
}
-func TestReplaceFilesWithDeleteFilesPreservesDataSequenceNumber(t *testing.T) {
+func TestRewriteDeleteFilesPreservesDataSequenceNumber(t *testing.T) {
tbl := newReplaceFilesTestTable(t)
arrowSc, err := table.SchemaToArrowSchema(tbl.Schema(), nil, false,
false)
require.NoError(t, err)
@@ -227,15 +246,20 @@ func
TestReplaceFilesWithDeleteFilesPreservesDataSequenceNumber(t *testing.T) {
require.NoError(t, err)
tx = tbl.NewTransaction()
- require.NoError(t, tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ require.NoError(t, stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{oldDelete},
- []table.DeleteFileAddition{{
+ []rewriteDeleteAddition{{
File: newDeleteBuilder.Build(),
DataSequenceNumber: oldSequence,
- }}, nil))
+ }}))
tbl, err = tx.Commit(t.Context())
require.NoError(t, err)
+ snapshot := tbl.CurrentSnapshot()
+ require.NotNil(t, snapshot)
+ assert.Equal(t, table.OpReplace, snapshot.Summary.Operation,
+ "row-preserving delete-file rewrites must record a replace
snapshot")
+
assert.Equal(t, oldSequence, currentManifestEntry(t, tbl,
newDeletePath).DataSequenceNumber(),
"rewritten delete files must retain the replaced data sequence
number")
assert.NotEqual(t, tbl.CurrentSnapshot().SequenceNumber, oldSequence,
@@ -244,7 +268,7 @@ func
TestReplaceFilesWithDeleteFilesPreservesDataSequenceNumber(t *testing.T) {
"the replacement must preserve delete applicability")
}
-func TestReplaceFilesWithDeleteFilesRejectsDVOnV2(t *testing.T) {
+func TestRewriteDeleteFilesRejectsDVOnV2(t *testing.T) {
tbl := newReplaceFilesTestTable(t)
ref := tbl.Location() + "/data/data.parquet"
dvBuilder, err := iceberg.NewDataFileBuilder(
@@ -256,27 +280,27 @@ func TestReplaceFilesWithDeleteFilesRejectsDVOnV2(t
*testing.T) {
oldDelete := newPosDeleteFile(t,
tbl.Location()+"/data/old-pos-delete.parquet")
tx := tbl.NewTransaction()
- err = tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ err = stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{oldDelete},
- []table.DeleteFileAddition{{File: dv, DataSequenceNumber: 0}},
nil)
+ []rewriteDeleteAddition{{File: dv, DataSequenceNumber: 0}})
require.Error(t, err)
assert.Contains(t, err.Error(), "requires table format version >= 3")
}
-func TestReplaceFilesWithDeleteFilesRejectsPositionDeleteOnV3(t *testing.T) {
+func TestRewriteDeleteFilesRejectsPositionDeleteOnV3(t *testing.T) {
tbl := newReplaceFilesTestTableVersion(t, 3)
tx := tbl.NewTransaction()
- err := tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ err := stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{newPosDeleteFile(t,
"old-pos-delete.parquet")},
- []table.DeleteFileAddition{{
+ []rewriteDeleteAddition{{
File: newPosDeleteFile(t,
"new-pos-delete.parquet"),
DataSequenceNumber: 0,
- }}, nil)
+ }})
require.Error(t, err)
assert.Contains(t, err.Error(), "must be a deletion vector for v3
table")
}
-func TestReplaceFilesWithDeleteFilesValidatesDeletionVectorMetadata(t
*testing.T) {
+func TestRewriteDeleteFilesValidatesDeletionVectorMetadata(t *testing.T) {
tbl := newReplaceFilesTestTableVersion(t, 3)
oldDelete := newPosDeleteFile(t, "old-pos-delete.parquet")
ref := "data.parquet"
@@ -320,16 +344,16 @@ func
TestReplaceFilesWithDeleteFilesValidatesDeletionVectorMetadata(t *testing.T
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tx := tbl.NewTransaction()
- err := tx.ReplaceFilesWithDeleteFiles(t.Context(), nil,
nil,
+ err := stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{oldDelete},
- []table.DeleteFileAddition{{File: tt.file,
DataSequenceNumber: 0}}, nil)
+ []rewriteDeleteAddition{{File: tt.file,
DataSequenceNumber: 0}})
require.Error(t, err)
assert.Contains(t, err.Error(), tt.errSubstr)
})
}
}
-func TestReplaceFilesWithDeleteFilesValidatesDeletionVectorIdentity(t
*testing.T) {
+func TestRewriteDeleteFilesValidatesDeletionVectorIdentity(t *testing.T) {
tbl := newReplaceFilesTestTableVersion(t, 3)
oldDelete := newPosDeleteFile(t, "old-pos-delete.parquet")
offsetA, offsetB := int64(8), int64(24)
@@ -337,24 +361,24 @@ func
TestReplaceFilesWithDeleteFilesValidatesDeletionVectorIdentity(t *testing.T
t.Run("duplicate referenced data file", func(t *testing.T) {
tx := tbl.NewTransaction()
- err := tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ err := stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{oldDelete},
- []table.DeleteFileAddition{
+ []rewriteDeleteAddition{
{File: newRewriteDeletionVector(t, "a.puffin",
"data.parquet", &offsetA, &length), DataSequenceNumber: 0},
{File: newRewriteDeletionVector(t, "b.puffin",
"data.parquet", &offsetB, &length), DataSequenceNumber: 0},
- }, nil)
+ })
require.Error(t, err)
assert.Contains(t, err.Error(), "must reference distinct data
files")
})
t.Run("duplicate blob identity", func(t *testing.T) {
tx := tbl.NewTransaction()
- err := tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ err := stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{oldDelete},
- []table.DeleteFileAddition{
+ []rewriteDeleteAddition{
{File: newRewriteDeletionVector(t,
"shared.puffin", "data-a.parquet", &offsetA, &length), DataSequenceNumber: 0},
{File: newRewriteDeletionVector(t,
"shared.puffin", "data-b.parquet", &offsetA, &length), DataSequenceNumber: 0},
- }, nil)
+ })
require.Error(t, err)
assert.Contains(t, err.Error(), "blob identity must be unique")
})
@@ -380,29 +404,29 @@ func
TestReplaceFilesWithDeleteFilesValidatesDeletionVectorIdentity(t *testing.T
vectorA := newRewriteDeletionVector(t, sharedPath,
tasks[0].File.FilePath(), &offsetA, &length)
vectorB := newRewriteDeletionVector(t, sharedPath,
tasks[1].File.FilePath(), &offsetB, &length)
tx = sharedTbl.NewTransaction()
- err = tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ err = stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{sourceDelete},
- []table.DeleteFileAddition{
+ []rewriteDeleteAddition{
{File: vectorA, DataSequenceNumber: sequence},
{File: vectorB, DataSequenceNumber: sequence},
- }, nil)
+ })
require.NoError(t, err, "distinct DV blobs in one Puffin
container must be accepted")
sharedTbl, err = tx.Commit(t.Context())
require.NoError(t, err)
offsetC := offsetB + length
tx = sharedTbl.NewTransaction()
- err = tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ err = stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{vectorA},
- []table.DeleteFileAddition{{
+ []rewriteDeleteAddition{{
File: newRewriteDeletionVector(t,
sharedPath, tasks[0].File.FilePath(), &offsetC, &length),
DataSequenceNumber: sequence,
- }}, nil)
+ }})
require.NoError(t, err, "a new DV blob may reuse an existing
container path while a sibling survives")
})
}
-func TestReplaceFilesWithDeleteFilesRejectsDuplicatePaths(t *testing.T) {
+func TestRewriteDeleteFilesRejectsDuplicatePaths(t *testing.T) {
const path = "shared-delete-file"
offset, length := int64(8), int64(16)
pos := newPosDeleteFile(t, path)
@@ -427,19 +451,19 @@ func
TestReplaceFilesWithDeleteFilesRejectsDuplicatePaths(t *testing.T) {
} {
t.Run(tt.name, func(t *testing.T) {
tbl := newReplaceFilesTestTableVersion(t, tt.version)
- additions := make([]table.DeleteFileAddition,
len(tt.files))
+ additions := make([]rewriteDeleteAddition,
len(tt.files))
for i, file := range tt.files {
- additions[i] = table.DeleteFileAddition{File:
file, DataSequenceNumber: 0}
+ additions[i] = rewriteDeleteAddition{File:
file, DataSequenceNumber: 0}
}
- err :=
tbl.NewTransaction().ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
- []iceberg.DataFile{newEqDeleteFile(t,
"old-equality-delete.parquet")}, additions, nil)
+ err := stageDeleteFileRewrite(t.Context(),
tbl.NewTransaction(),
+ []iceberg.DataFile{newEqDeleteFile(t,
"old-equality-delete.parquet")}, additions)
require.EqualError(t, err, tt.wantError)
})
}
}
-func TestReplaceFilesWithDeleteFilesValidatesExistingPaths(t *testing.T) {
+func TestRewriteDeleteFilesValidatesExistingPaths(t *testing.T) {
for _, version := range []int{2, 3} {
t.Run(fmt.Sprintf("v%d", version), func(t *testing.T) {
tbl := newReplaceFilesTestTable(t)
@@ -471,12 +495,12 @@ func
TestReplaceFilesWithDeleteFilesValidatesExistingPaths(t *testing.T) {
} {
t.Run(tt.name, func(t *testing.T) {
tx := tbl.NewTransaction()
- err :=
tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ err :=
stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{source},
- []table.DeleteFileAddition{{
+ []rewriteDeleteAddition{{
File:
newEqDeleteFile(t, tt.path),
DataSequenceNumber:
sequence,
- }}, nil)
+ }})
if tt.name == "new delete file" {
require.NoError(t, err)
@@ -489,7 +513,7 @@ func
TestReplaceFilesWithDeleteFilesValidatesExistingPaths(t *testing.T) {
}
}
-func TestReplaceFilesWithDeleteFilesRejectsPartialPositionDeleteToDVRewrite(t
*testing.T) {
+func TestRewriteDeleteFilesRejectsPartialPositionDeleteToDVRewrite(t
*testing.T) {
tbl := newReplaceFilesTestTable(t)
arrowSc, err := table.SchemaToArrowSchema(tbl.Schema(), nil, false,
false)
require.NoError(t, err)
@@ -534,22 +558,22 @@ func
TestReplaceFilesWithDeleteFilesRejectsPartialPositionDeleteToDVRewrite(t *t
replacement := newRewriteDeletionVector(t,
tbl.Location()+"/data/replacement.puffin", dataPath, &offset, &length)
tx = tbl.NewTransaction()
- err = tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ err = stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{oldDeletes[0]},
- []table.DeleteFileAddition{{File: replacement,
DataSequenceNumber: sequence}}, nil)
+ []rewriteDeleteAddition{{File: replacement, DataSequenceNumber:
sequence}})
require.Error(t, err)
assert.Contains(t, err.Error(), "requires replacing all applicable
position-delete files")
assert.Contains(t, err.Error(), oldDeletes[1].FilePath())
assert.Contains(t, err.Error(), "partition-scoped")
tx = tbl.NewTransaction()
- require.NoError(t, tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ require.NoError(t, stageDeleteFileRewrite(t.Context(), tx,
oldDeletes,
- []table.DeleteFileAddition{{File: replacement,
DataSequenceNumber: sequence}}, nil),
+ []rewriteDeleteAddition{{File: replacement, DataSequenceNumber:
sequence}}),
"the DV rewrite should be accepted once every applicable
position delete is replaced")
}
-func TestReplaceFilesWithDeleteFilesRejectsDeletionVectorPartitionMismatch(t
*testing.T) {
+func TestRewriteDeleteFilesRejectsDeletionVectorPartitionMismatch(t
*testing.T) {
tbl, spec := newPartitionedReplaceFilesTestTable(t)
dataPath := tbl.Location() + "/data/data.parquet"
dataPartition := map[int]any{1000: int64(10)}
@@ -592,15 +616,15 @@ func
TestReplaceFilesWithDeleteFilesRejectsDeletionVectorPartitionMismatch(t *te
Build()
tx = tbl.NewTransaction()
- err = tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ err = stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{oldDelete},
- []table.DeleteFileAddition{{File: replacement,
DataSequenceNumber: deleteSequence}}, nil)
+ []rewriteDeleteAddition{{File: replacement, DataSequenceNumber:
deleteSequence}})
require.Error(t, err)
assert.Contains(t, err.Error(), "partition spec or values")
assert.Contains(t, err.Error(), dataPath)
}
-func TestReplaceFilesWithDeleteFilesIgnoresOlderSurvivingPositionDelete(t
*testing.T) {
+func TestRewriteDeleteFilesIgnoresOlderSurvivingPositionDelete(t *testing.T) {
tbl, spec := newPartitionedReplaceFilesTestTable(t)
partition := map[int]any{1000: int64(10)}
@@ -675,16 +699,16 @@ func
TestReplaceFilesWithDeleteFilesIgnoresOlderSurvivingPositionDelete(t *testi
Build()
tx = tbl.NewTransaction()
- require.NoError(t, tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ require.NoError(t, stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{newPositionDelete},
- []table.DeleteFileAddition{{File: replacement,
DataSequenceNumber: newPositionDeleteSequence}}, nil),
+ []rewriteDeleteAddition{{File: replacement, DataSequenceNumber:
newPositionDeleteSequence}}),
"an older position delete cannot apply to the newer target data
file")
tbl, err = tx.Commit(t.Context())
require.NoError(t, err)
assert.Equal(t, newPositionDeleteSequence, currentManifestEntry(t, tbl,
replacement.FilePath()).DataSequenceNumber())
}
-func TestReplaceFilesWithDeleteFilesRejectsSurvivingDeletionVector(t
*testing.T) {
+func TestRewriteDeleteFilesRejectsSurvivingDeletionVector(t *testing.T) {
tbl, target := seedV3TableWithDV(t)
tasks, err := tbl.Scan().PlanFiles(t.Context())
require.NoError(t, err)
@@ -713,14 +737,14 @@ func
TestReplaceFilesWithDeleteFilesRejectsSurvivingDeletionVector(t *testing.T)
replacement := newRewriteDeletionVector(t,
tbl.Location()+"/data/replacement-dv.puffin", target, &offset, &length)
sequence := currentManifestEntry(t, tbl,
siblingDVs[0].FilePath()).DataSequenceNumber()
tx = tbl.NewTransaction()
- err = tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ err = stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{siblingDVs[0]},
- []table.DeleteFileAddition{{File: replacement,
DataSequenceNumber: sequence}}, nil)
+ []rewriteDeleteAddition{{File: replacement, DataSequenceNumber:
sequence}})
require.Error(t, err)
assert.Contains(t, err.Error(), "already exists and must be replaced")
}
-func TestReplaceFilesWithDeleteFilesAllowsDroppedEqualityField(t *testing.T) {
+func TestRewriteDeleteFilesAllowsDroppedEqualityField(t *testing.T) {
tbl := newReplaceFilesTestTable(t)
oldDeleteBuilder, err := iceberg.NewDataFileBuilder(
*iceberg.UnpartitionedSpec, iceberg.EntryContentEqDeletes,
@@ -749,9 +773,9 @@ func
TestReplaceFilesWithDeleteFilesAllowsDroppedEqualityField(t *testing.T) {
require.NoError(t, err)
newDelete := newDeleteBuilder.EqualityFieldIDs([]int{2}).Build()
tx = tbl.NewTransaction()
- require.NoError(t, tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ require.NoError(t, stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{oldDelete},
- []table.DeleteFileAddition{{File: newDelete,
DataSequenceNumber: sequence}}, nil))
+ []rewriteDeleteAddition{{File: newDelete, DataSequenceNumber:
sequence}}))
}
// currentManifestEntry returns the manifest entry for path in the current
@@ -1265,12 +1289,12 @@ func
TestReplaceFilesRejectsDeletionVectorSupersededByCurrentSnapshot(t *testing
require.NoError(t, err)
require.Len(t, rewritten, 1)
- return
tx.ReplaceFilesWithDeleteFiles(t.Context(), nil, nil,
+ return stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{superseded},
- []table.DeleteFileAddition{{
+ []rewriteDeleteAddition{{
File:
rewritten[0],
DataSequenceNumber:
currentManifestEntry(t, tbl, superseded.FilePath()).DataSequenceNumber(),
- }}, nil)
+ }})
},
},
} {
@@ -1320,12 +1344,12 @@ func
TestReplaceFilesRemovesLiveDeletionVectorNotSupersededEntry(t *testing.T) {
require.Len(t, rewritten, 1)
tx := tbl.NewTransaction()
- require.NoError(t, tx.ReplaceFilesWithDeleteFiles(t.Context(),
nil, nil,
+ require.NoError(t, stageDeleteFileRewrite(t.Context(), tx,
[]iceberg.DataFile{liveDV},
- []table.DeleteFileAddition{{
+ []rewriteDeleteAddition{{
File: rewritten[0],
DataSequenceNumber: currentManifestEntry(t,
tbl, liveDV.FilePath()).DataSequenceNumber(),
- }}, nil))
+ }}))
tbl, err = tx.Commit(t.Context())
require.NoError(t, err)
diff --git a/table/transaction.go b/table/transaction.go
index 2de3114b3..54321735a 100644
--- a/table/transaction.go
+++ b/table/transaction.go
@@ -1830,18 +1830,6 @@ func (t *Transaction) ReplaceDataFiles(ctx
context.Context, filesToDelete, files
return t.apply(updates, reqs)
}
-// DeleteFileAddition is a delete file added by a rewrite. The data sequence
-// number must be copied from the delete files being replaced so the new file
-// has the same applicability boundary as the old files. When one rewrite
-// contains multiple replacement groups, callers are responsible for assigning
-// each output the maximum sequence number of the exact source files it
replaces;
-// the transaction can validate membership in the overall source sequence set,
-// but the grouping is not represented by this API.
-type DeleteFileAddition struct {
- File iceberg.DataFile
- DataSequenceNumber int64
-}
-
// ReplaceFiles atomically replaces data files and removes associated delete
files
// in a single snapshot. This is the commit primitive for compaction: old data
files
// are replaced with new (compacted) data files, and delete files that are
fully
@@ -1851,24 +1839,6 @@ func (t *Transaction) ReplaceFiles(ctx context.Context,
dataFilesToDelete, dataF
return t.replaceFiles(ctx, dataFilesToDelete, dataFilesToAdd,
deleteFilesToRemove, nil, nil, snapshotProps, opts...)
}
-// ReplaceFilesWithDeleteFiles atomically replaces data files, adds new delete
-// files, and removes superseded delete files in one snapshot. Every added
delete
-// file must carry the data sequence number of the delete files it replaces.
-// This is intended for rewrites that change delete-file representation, such
as
-// rewriting position deletes into deletion vectors.
-func (t *Transaction) ReplaceFilesWithDeleteFiles(ctx context.Context,
dataFilesToDelete, dataFilesToAdd, deleteFilesToRemove []iceberg.DataFile,
deleteFilesToAdd []DeleteFileAddition, snapshotProps iceberg.Properties, opts
...WriteOption) error {
- additions := make([]rewriteDeleteFileAddition, len(deleteFilesToAdd))
- for i, addition := range deleteFilesToAdd {
- seq := addition.DataSequenceNumber
- additions[i] = rewriteDeleteFileAddition{
- file: addition.File,
- dataSequenceNumber: &seq,
- }
- }
-
- return t.replaceFiles(ctx, dataFilesToDelete, dataFilesToAdd,
deleteFilesToRemove, nil, additions, snapshotProps, opts...)
-}
-
func (t *Transaction) replaceFiles(ctx context.Context, dataFilesToDelete,
dataFilesToAdd, deleteFilesToRemove, autoDeleteFilesToRemove
[]iceberg.DataFile, deleteFilesToAdd []rewriteDeleteFileAddition, snapshotProps
iceberg.Properties, opts ...WriteOption) error {
meta, err := t.txnMeta()
if err != nil {
diff --git a/website/src/releases.md b/website/src/releases.md
index b9c043a0b..5041e88e4 100644
--- a/website/src/releases.md
+++ b/website/src/releases.md
@@ -42,6 +42,7 @@ Per-release notes (highlights, breaking changes,
contributors) are published on
### Unreleased compatibility notes
+- **`ReplaceFilesWithDeleteFiles` removed.** For delete-file replacements, use
`tx.NewRewrite(snapshotProps)`, call `DeleteFile` for each source file and
`AddDeleteFileWithDataSequenceNumber` for each replacement, then commit the
builder. Rewrites record a `replace` snapshot instead of `overwrite`. The
five-argument `ReplaceFiles` API is unchanged and remains available for
compaction and delete-file removals.
- **Path-based data-file addition renamed.** Replace `Transaction.AddFiles`
with `AddDataFilesByPath`, `AddFilesOption` with `AddDataFilesByPathOption`,
and `WithAddFilesConcurrency` with `WithAddDataFilesByPathConcurrency`.
Behavior is unchanged.
- **Manifest builder constructor renamed.** Replace
`iceberg.NewManifestFile(...)` with `iceberg.NewManifestBuilder(...)`. It still
returns a `ManifestBuilder`.
- **Manifest sequence APIs use explicit names.** `ManifestBuilder.SequenceNum`
and `ManifestFile.SequenceNum` become `SequenceNumber`, and
`ManifestFile.MinSequenceNum` becomes `MinSequenceNumber`.
`ManifestEntryBuilder.SequenceNum` and `ManifestEntry.SequenceNum` become
`DataSequenceNumber`. `FileSequenceNum` becomes `FileSequenceNumber` for
entries and their builder. Avro and JSON field names are unchanged.