This is an automated email from the ASF dual-hosted git repository.

zeroshade 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 887694fbf fix(table): keep main as a branch (#2472)
887694fbf is described below

commit 887694fbf014564040c8607cf635af89d76badda
Author: Minh Vu <[email protected]>
AuthorDate: Mon Nov 9 20:47:18 2026 +0100

    fix(table): keep main as a branch (#2472)
    
    fix(table): require main snapshot ref to be a branch
---
 table/metadata.go                       |  7 +++++++
 table/metadata_builder_internal_test.go | 15 +++++++++++----
 table/metadata_internal_test.go         | 21 +++++++++++++++++++++
 table/transaction_internal_test.go      |  9 +++++++--
 4 files changed, 46 insertions(+), 6 deletions(-)

diff --git a/table/metadata.go b/table/metadata.go
index d7ae9dd80..703b18f07 100644
--- a/table/metadata.go
+++ b/table/metadata.go
@@ -1424,6 +1424,9 @@ func (b *MetadataBuilder) SetSnapshotRef(
        if err := ref.validate(); err != nil {
                return fmt.Errorf("%w: invalid snapshot ref: %w", 
iceberg.ErrInvalidArgument, err)
        }
+       if name == MainBranch && ref.Type != BranchRef {
+               return fmt.Errorf("%w: main snapshot ref must be a branch", 
iceberg.ErrInvalidArgument)
+       }
 
        var maxRefAgeMs, maxSnapshotAgeMs int64
        var minSnapshotsToKeep int
@@ -3149,6 +3152,10 @@ func (c *commonMetadata) NameMapping() 
iceberg.NameMapping {
 }
 
 func (c *commonMetadata) checkMainRefMatchesCurrentSnapshot() error {
+       if ref, ok := c.SnapshotRefs[MainBranch]; ok && ref.Type != BranchRef {
+               return fmt.Errorf("%w: main snapshot ref must be a branch", 
ErrInvalidMetadata)
+       }
+
        if c.CurrentSnapshotID != nil {
                if ref, ok := c.SnapshotRefs[MainBranch]; ok {
                        if ref.SnapshotID != *c.CurrentSnapshotID {
diff --git a/table/metadata_builder_internal_test.go 
b/table/metadata_builder_internal_test.go
index e8dbfa308..ee41bf991 100644
--- a/table/metadata_builder_internal_test.go
+++ b/table/metadata_builder_internal_test.go
@@ -906,25 +906,32 @@ func TestSetSnapshotRefBranchToTagDropsAllRetention(t 
*testing.T) {
                SchemaID:         &schemaID,
        }
        require.NoError(t, builder.AddSnapshot(&snapshot))
+       require.NoError(t, builder.SetSnapshotRef(MainBranch, 1, BranchRef))
        require.NoError(t, builder.SetSnapshotRef(
-               MainBranch, 1, BranchRef,
+               "release", 1, BranchRef,
                WithMinSnapshotsToKeep(5),
                WithMaxSnapshotAgeMs(int64(172800000)),
                WithMaxRefAgeMs(int64(604800000)),
        ))
 
        // The retaining helper must not carry retention across a type change.
-       upd := builder.NewRetainingSnapshotRefUpdate(MainBranch, 1, TagRef)
+       upd := builder.NewRetainingSnapshotRefUpdate("release", 1, TagRef)
        require.Zero(t, upd.MinSnapshotsToKeep)
        require.Zero(t, upd.MaxSnapshotAgeMs)
        require.Zero(t, upd.MaxRefAgeMs)
 
-       require.NoError(t, builder.SetSnapshotRef(MainBranch, 1, TagRef))
-       ref := builder.refs[MainBranch]
+       require.NoError(t, builder.SetSnapshotRef("release", 1, TagRef))
+       ref := builder.refs["release"]
        require.Equal(t, TagRef, ref.Type)
        require.Nil(t, ref.MinSnapshotsToKeep)
        require.Nil(t, ref.MaxSnapshotAgeMs)
        require.Nil(t, ref.MaxRefAgeMs)
+
+       err := builder.SetSnapshotRef(MainBranch, 1, TagRef)
+       require.ErrorIs(t, err, iceberg.ErrInvalidArgument)
+       require.Equal(t, BranchRef, builder.refs[MainBranch].Type)
+       _, err = builder.Build()
+       require.NoError(t, err)
 }
 
 func TestNewRetainingSnapshotRefUpdateTagPreservesMaxRefAge(t *testing.T) {
diff --git a/table/metadata_internal_test.go b/table/metadata_internal_test.go
index ae6206eba..37b36aa89 100644
--- a/table/metadata_internal_test.go
+++ b/table/metadata_internal_test.go
@@ -1487,6 +1487,27 @@ func TestV2RefCreation(t *testing.T) {
        }, meta.SnapshotRefs)
 }
 
+func TestParseMetadataRejectsMainTag(t *testing.T) {
+       var fields map[string]json.RawMessage
+       require.NoError(t, json.Unmarshal([]byte(ExampleTableMetadataV2), 
&fields))
+
+       var refs map[string]SnapshotRef
+       require.NoError(t, json.Unmarshal(fields["refs"], &refs))
+       main := refs[MainBranch]
+       main.Type = TagRef
+       refs[MainBranch] = main
+
+       refsJSON, err := json.Marshal(refs)
+       require.NoError(t, err)
+       fields["refs"] = refsJSON
+       metadataJSON, err := json.Marshal(fields)
+       require.NoError(t, err)
+
+       _, err = ParseMetadataBytes(metadataJSON)
+       require.ErrorIs(t, err, ErrInvalidMetadata)
+       require.ErrorContains(t, err, "main snapshot ref must be a branch")
+}
+
 func TestV1WriteMetadataToV2(t *testing.T) {
        // https://iceberg.apache.org/spec/#version-2
        //
diff --git a/table/transaction_internal_test.go 
b/table/transaction_internal_test.go
index 03a3ac8ec..3e5e8d908 100644
--- a/table/transaction_internal_test.go
+++ b/table/transaction_internal_test.go
@@ -1221,9 +1221,14 @@ func TestTransactionCommitRejectsSameIDTagRace(t 
*testing.T) {
        require.NotNil(t, head)
        tagBuilder, err := MetadataBuilderFromBase(baseMeta, "")
        require.NoError(t, err)
-       require.NoError(t, tagBuilder.SetSnapshotRef(MainBranch, 
head.SnapshotID, TagRef))
-       tagMeta, err := tagBuilder.Build()
+       tagCommon, err := tagBuilder.buildCommonMetadata()
        require.NoError(t, err)
+       tagCommon.SnapshotRefs = cloneSnapshotRefs(tagCommon.SnapshotRefs)
+       tagCommon.SnapshotRefs[MainBranch] = SnapshotRef{SnapshotID: 
head.SnapshotID, Type: TagRef}
+       tagMeta := &metadataV2{
+               LastSeqNum:     baseMeta.LastSequenceNumber(),
+               commonMetadata: *tagCommon,
+       }
 
        cat := &headTrackingCatalog{metadata: tagMeta}
        tbl := New(Identifier{"db", "tag-race"}, baseMeta, "metadata.json",

Reply via email to