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 87c9223c0 perf(table): skip metadata clone for duplicate requirements 
(#2105)
87c9223c0 is described below

commit 87c9223c07068fff4b336e4631b70d6bc77b753e
Author: Minh Vu <[email protected]>
AuthorDate: Tue Oct 6 10:22:04 2026 +0200

    perf(table): skip metadata clone for duplicate requirements (#2105)
    
    * perf(table): skip metadata clone for duplicate requirements
    
    * test(table): assert duplicate apply keeps metadata builder
    
    * test(table): cover duplicate requirements with updates
---
 table/transaction.go               | 20 ++++++++++++++++----
 table/transaction_internal_test.go | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 52 insertions(+), 4 deletions(-)

diff --git a/table/transaction.go b/table/transaction.go
index 18d9d5d89..dae5e3ebf 100644
--- a/table/transaction.go
+++ b/table/transaction.go
@@ -165,10 +165,7 @@ func (t *Transaction) apply(updates []Update, reqs 
[]Requirement) error {
                return errors.New("transaction has already been committed")
        }
 
-       stagedMeta := meta.clone()
-       if stagedMeta == nil {
-               return errors.New("cannot apply updates to nil metadata")
-       }
+       var stagedMeta *MetadataBuilder
 
        // Only new requirement validation needs the immutable metadata view.
        // Updates can be applied directly to the staged builder, and duplicate
@@ -207,6 +204,12 @@ func (t *Transaction) apply(updates []Update, reqs 
[]Requirement) error {
 
                                continue
                        }
+                       if stagedMeta == nil {
+                               stagedMeta = meta.clone()
+                               if stagedMeta == nil {
+                                       return errors.New("cannot apply updates 
to nil metadata")
+                               }
+                       }
                        if current == nil {
                                built, err := stagedMeta.Build()
                                if err != nil {
@@ -226,6 +229,15 @@ func (t *Transaction) apply(updates []Update, reqs 
[]Requirement) error {
                        stagedReqs = append(stagedReqs, r)
                }
        }
+       if stagedMeta == nil {
+               if len(updates) == 0 {
+                       return nil
+               }
+               stagedMeta = meta.clone()
+               if stagedMeta == nil {
+                       return errors.New("cannot apply updates to nil 
metadata")
+               }
+       }
 
        prevUpdates, prevLastUpdated := len(stagedMeta.updates), 
stagedMeta.lastUpdatedMS
        for _, u := range updates {
diff --git a/table/transaction_internal_test.go 
b/table/transaction_internal_test.go
index 306916335..4049d7b59 100644
--- a/table/transaction_internal_test.go
+++ b/table/transaction_internal_test.go
@@ -929,8 +929,10 @@ func 
TestTransactionApplyDedupesEquivalentRequirementsWithinAndAcrossCalls(t *te
        require.Len(t, txn.reqs, 1)
        requireContainsRefSnapshotRequirement(t, txn.reqs, MainBranch, 
&mainSnapshotID)
 
+       meta := txn.meta
        err = txn.apply(nil, []Requirement{AssertRefSnapshotID(MainBranch, 
&mainSnapshotID)})
        require.NoError(t, err)
+       require.Same(t, meta, txn.meta)
        require.Len(t, txn.reqs, 1)
        requireContainsRefSnapshotRequirement(t, txn.reqs, MainBranch, 
&mainSnapshotID)
 }
@@ -1297,7 +1299,9 @@ func 
TestTransactionApplyDefersMetadataBuildForNoopRequirements(t *testing.T) {
                // A duplicate requirement only needs its conflict check. Make 
Build fail
                // after the first apply so this path cannot accidentally 
rebuild metadata.
                txn.meta.defaultSpecID = 999
+               meta := txn.meta
                require.NoError(t, txn.apply(nil, []Requirement{requirement}))
+               require.Same(t, meta, txn.meta)
                _, err := txn.meta.Build()
                require.ErrorIs(t, err, ErrInvalidMetadata)
        })
@@ -2171,3 +2175,35 @@ func TestTableCommitWithoutUpdatesStaysEmpty(t 
*testing.T) {
        assert.Equal(t, []Requirement{}, tc.Requirements, "an empty payload 
must serialize as [], not null")
        assert.Equal(t, []Update{}, tc.Updates, "an empty payload must 
serialize as [], not null")
 }
+
+func TestTransactionApplyDuplicateRequirementWithUpdates(t *testing.T) {
+       for _, fail := range []bool{false, true} {
+               t.Run(fmt.Sprintf("failure=%t", fail), func(t *testing.T) {
+                       txn, _ := createTestTransactionWithMemIO(t, 
*iceberg.UnpartitionedSpec)
+                       requirement := AssertCurrentSchemaID(0)
+                       require.NoError(t, txn.apply(nil, 
[]Requirement{requirement}))
+                       before, err := txn.meta.Build()
+                       require.NoError(t, err)
+                       original := txn.meta
+                       updates := 
[]Update{NewSetPropertiesUpdate(iceberg.Properties{"test-key": "test-value"})}
+                       if fail {
+                               updates = append(updates, 
NewSetCurrentSchemaUpdate(9999))
+                       }
+                       err = txn.apply(updates, []Requirement{requirement})
+                       require.Equal(t, []Requirement{requirement}, txn.reqs)
+                       after, buildErr := txn.meta.Build()
+                       require.NoError(t, buildErr)
+                       if fail {
+                               require.Error(t, err)
+                               require.Same(t, original, txn.meta)
+                               require.True(t, before.Equals(after))
+
+                               return
+                       }
+                       require.NoError(t, err)
+                       require.NotSame(t, original, txn.meta)
+                       require.Equal(t, "test-value", 
after.Properties()["test-key"])
+                       require.Empty(t, before.Properties()["test-key"])
+               })
+       }
+}

Reply via email to