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",