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 c23b885d3 fix(table): reject duplicate snapshot IDs (#1592)
c23b885d3 is described below

commit c23b885d3cdb174868af47bfe11bc578bae65af4
Author: Minh Vu <[email protected]>
AuthorDate: Thu Jul 30 09:49:54 2026 +0200

    fix(table): reject duplicate snapshot IDs (#1592)
    
    ## What changed
    
    Validate snapshot ID uniqueness before constructing or checking snapshot
    references. Add coverage for identical duplicate snapshots and duplicate
    IDs carrying different manifest lists.
    
    ## Why
    
    Snapshot lookup returns the first matching entry. Accepting duplicate
    IDs made current-snapshot and reference resolution depend on JSON array
    order.
    
    ## Testing
    
    - `go test ./table -run
    'Test(RejectDuplicateSnapshotIDs|MetadataV[123]Parsing)$' -count=1`
    
    Signed-off-by: Minh Vu <[email protected]>
---
 table/metadata.go               | 16 ++++++++++++++++
 table/metadata_internal_test.go | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 54 insertions(+)

diff --git a/table/metadata.go b/table/metadata.go
index fd9e8896a..4aa87c94b 100644
--- a/table/metadata.go
+++ b/table/metadata.go
@@ -2264,6 +2264,10 @@ func (c *commonMetadata) validate() error {
                return err
        }
 
+       if err := c.checkSnapshots(); err != nil {
+               return err
+       }
+
        c.constructRefs()
 
        if err := c.checkMainRefMatchesCurrentSnapshot(); err != nil {
@@ -2277,6 +2281,18 @@ func (c *commonMetadata) validate() error {
        return nil
 }
 
+func (c *commonMetadata) checkSnapshots() error {
+       seen := make(map[int64]struct{}, len(c.SnapshotList))
+       for _, snapshot := range c.SnapshotList {
+               if _, ok := seen[snapshot.SnapshotID]; ok {
+                       return fmt.Errorf("%w: duplicate snapshot ID %d", 
ErrInvalidMetadata, snapshot.SnapshotID)
+               }
+               seen[snapshot.SnapshotID] = struct{}{}
+       }
+
+       return nil
+}
+
 func (c *commonMetadata) NameMapping() iceberg.NameMapping {
        if nameMappingJson, ok := c.Props[DefaultNameMappingKey]; ok {
                nm := iceberg.NameMapping{}
diff --git a/table/metadata_internal_test.go b/table/metadata_internal_test.go
index 7aaabe31f..bfe821968 100644
--- a/table/metadata_internal_test.go
+++ b/table/metadata_internal_test.go
@@ -564,6 +564,44 @@ func TestRejectInvalidSchemaEntries(t *testing.T) {
        })
 }
 
+func TestRejectDuplicateSnapshotIDs(t *testing.T) {
+       var metadata map[string]any
+       decoder := json.NewDecoder(strings.NewReader(ExampleTableMetadataV2))
+       decoder.UseNumber()
+       require.NoError(t, decoder.Decode(&metadata))
+       snapshots := metadata["snapshots"].([]any)
+       current := snapshots[len(snapshots)-1].(map[string]any)
+
+       tests := []struct {
+               name      string
+               duplicate map[string]any
+       }{
+               {name: "identical snapshot", duplicate: maps.Clone(current)},
+               {
+                       name: "different manifest list",
+                       duplicate: func() map[string]any {
+                               duplicate := maps.Clone(current)
+                               duplicate["manifest-list"] = 
"s3://bucket/metadata/different.avro"
+
+                               return duplicate
+                       }(),
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       invalid := maps.Clone(metadata)
+                       invalid["snapshots"] = append(slices.Clone(snapshots), 
tt.duplicate)
+                       data, err := json.Marshal(invalid)
+                       require.NoError(t, err)
+
+                       _, err = ParseMetadataBytes(data)
+                       require.ErrorIs(t, err, ErrInvalidMetadata)
+                       assert.ErrorContains(t, err, "duplicate snapshot ID 
3055729675574597004")
+               })
+       }
+}
+
 func TestSortOrderNotFound(t *testing.T) {
        metadataSortOrderNotFound := `{
         "format-version": 2,

Reply via email to