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 4ebe066d feat(metadata): remove schemas + validations & tests (#567)
4ebe066d is described below

commit 4ebe066d30e269e18b477d36947973104cc22999
Author: Tobias Pütz <[email protected]>
AuthorDate: Thu Sep 18 21:03:21 2025 +0200

    feat(metadata): remove schemas + validations & tests (#567)
    
    1. implement removeSchemas update
    2. Add validation of sortOrders against schema
---
 partitions.go                                      |  2 +-
 table/metadata.go                                  | 71 ++++++++++++++--
 table/metadata_builder_internal_test.go            | 94 +++++++++++++++++++++-
 table/metadata_internal_test.go                    | 46 ++++++++++-
 table/sorting.go                                   | 24 ++++++
 table/testdata/TableMetadataV1NoValidSchema.json   | 45 +++++++++++
 .../TableMetadataV1SchemasWithoutCurrentId.json    | 69 ++++++++++++++++
 .../TableMetadataV2CurrentSchemaNotFound.json      | 88 ++++++++++++++++++++
 table/testdata/TableMetadataV2MissingSchemas.json  | 71 ++++++++++++++++
 table/updates.go                                   |  8 +-
 table/updates_test.go                              | 13 ---
 11 files changed, 502 insertions(+), 29 deletions(-)

diff --git a/partitions.go b/partitions.go
index db66abd2..7bf5c0a2 100644
--- a/partitions.go
+++ b/partitions.go
@@ -96,7 +96,7 @@ type PartitionOption func(*PartitionSpec) error
 // Otherwise, the existing spec id will be used.
 // If a field in the spec is incompatible with the schema, an error will be
 // returned.
-func (p *PartitionSpec) BindToSchema(schema *Schema, lastPartitionID *int, 
newSpecID *int, isUnbound bool) (PartitionSpec, error) {
+func (p *PartitionSpec) BindToSchema(schema *Schema, lastPartitionID *int, 
newSpecID *int) (PartitionSpec, error) {
        opts := make([]PartitionOption, 0)
        if newSpecID != nil {
                opts = append(opts, WithSpecID(*newSpecID))
diff --git a/table/metadata.go b/table/metadata.go
index 568c9fd3..bae44694 100644
--- a/table/metadata.go
+++ b/table/metadata.go
@@ -286,8 +286,12 @@ func (b *MetadataBuilder) AddSchema(schema 
*iceberg.Schema) error {
 
 func (b *MetadataBuilder) AddPartitionSpec(spec *iceberg.PartitionSpec, 
initial bool) error {
        newSpecID := b.reuseOrCreateNewPartitionSpecID(*spec)
+       curSchema := b.CurrentSchema()
+       if curSchema == nil {
+               return errors.New("can't add sort order with no current schema")
+       }
 
-       freshSpec, err := spec.BindToSchema(b.CurrentSchema(), 
b.lastPartitionID, &newSpecID, false)
+       freshSpec, err := spec.BindToSchema(curSchema, b.lastPartitionID, 
&newSpecID)
        if err != nil {
                return err
        }
@@ -381,6 +385,15 @@ func (b *MetadataBuilder) RemoveSnapshots(snapshotIds 
[]int64) error {
 }
 
 func (b *MetadataBuilder) AddSortOrder(sortOrder *SortOrder, initial bool) 
error {
+       curSchema := b.CurrentSchema()
+       if curSchema == nil {
+               return errors.New("can't add sort order with no current schema")
+       }
+
+       if err := sortOrder.CheckCompatibility(curSchema); err != nil {
+               return fmt.Errorf("sort order %s is not compatible with current 
schema: %w", sortOrder, err)
+       }
+
        var sortOrders []SortOrder
        if !initial {
                sortOrders = append(sortOrders, b.sortOrderList...)
@@ -412,7 +425,8 @@ func (b *MetadataBuilder) RemoveProperties(keys []string) 
error {
 }
 
 func (b *MetadataBuilder) SetCurrentSchemaID(currentSchemaID int) error {
-       if currentSchemaID == -1 {
+       takeLast := currentSchemaID == -1
+       if takeLast {
                if b.lastAddedSchemaID == nil {
                        return errors.New("can't set current schema to last 
added schema, no schema has been added")
                }
@@ -428,7 +442,11 @@ func (b *MetadataBuilder) 
SetCurrentSchemaID(currentSchemaID int) error {
                return fmt.Errorf("can't set current schema to schema with id 
%d: %w", currentSchemaID, err)
        }
 
-       b.updates = append(b.updates, 
NewSetCurrentSchemaUpdate(currentSchemaID))
+       if takeLast {
+               b.updates = append(b.updates, NewSetCurrentSchemaUpdate(-1))
+       } else {
+               b.updates = append(b.updates, 
NewSetCurrentSchemaUpdate(currentSchemaID))
+       }
        b.currentSchemaID = currentSchemaID
 
        return nil
@@ -774,6 +792,7 @@ func (b *MetadataBuilder) Build() (Metadata, error) {
        if err != nil {
                return nil, err
        }
+
        if err := common.validate(); err != nil {
                return nil, err
        }
@@ -871,6 +890,33 @@ func (b *MetadataBuilder) RemovePartitionSpecs(ints []int) 
error {
        return nil
 }
 
+func (b *MetadataBuilder) RemoveSchemas(ints []int) error {
+       if len(ints) == 0 {
+               return nil
+       }
+
+       if slices.Contains(ints, b.currentSchemaID) {
+               return fmt.Errorf("can't remove current schema with id %d", 
b.currentSchemaID)
+       }
+
+       removed := make([]int, len(ints))
+       b.schemaList = slices.DeleteFunc(b.schemaList, func(s *iceberg.Schema) 
bool {
+               if slices.Contains(ints, s.ID) {
+                       removed = append(removed, s.ID)
+
+                       return true
+               }
+
+               return false
+       })
+
+       if len(removed) != 0 {
+               b.updates = append(b.updates, NewRemoveSchemasUpdate(ints))
+       }
+
+       return nil
+}
+
 // maxBy returns the maximum value of extract(e) for all e in elems.
 // If elems is empty, returns 0.
 func maxBy[S ~[]E, E any](elems S, extract func(e E) int) int {
@@ -1162,8 +1208,15 @@ func (c *commonMetadata) checkSortOrders() error {
 
        for _, o := range c.SortOrderList {
                if o.OrderID == c.DefaultSortOrderID {
+                       if err := o.CheckCompatibility(c.CurrentSchema()); err 
!= nil {
+                               return fmt.Errorf("default sort order %d is not 
compatible with current schema: %w", o.OrderID, err)
+                       }
+
                        return nil
                }
+               if o.OrderID == UnsortedSortOrderID && len(o.Fields) != 0 {
+                       return fmt.Errorf("sort order ID %d is reserved for 
unsorted order", UnsortedSortOrderID)
+               }
        }
 
        return fmt.Errorf("%w: default-sort-order-id %d can't be found in %+v",
@@ -1417,19 +1470,19 @@ func NewMetadataWithUUID(sc *iceberg.Schema, partitions 
*iceberg.PartitionSpec,
                return nil, err
        }
 
-       if err = builder.AddSortOrder(&reassignedIds.sortOrder, true); err != 
nil {
+       if err = builder.AddSchema(reassignedIds.schema); err != nil {
                return nil, err
        }
 
-       if err = builder.SetDefaultSortOrderID(-1); err != nil {
+       if err = builder.SetCurrentSchemaID(-1); err != nil {
                return nil, err
        }
 
-       if err = builder.AddSchema(reassignedIds.schema); err != nil {
+       if err = builder.AddSortOrder(&reassignedIds.sortOrder, true); err != 
nil {
                return nil, err
        }
 
-       if err = builder.SetCurrentSchemaID(-1); err != nil {
+       if err = builder.SetDefaultSortOrderID(-1); err != nil {
                return nil, err
        }
 
@@ -1437,6 +1490,10 @@ func NewMetadataWithUUID(sc *iceberg.Schema, partitions 
*iceberg.PartitionSpec,
                return nil, err
        }
 
+       if err = builder.SetDefaultSpecID(-1); err != nil {
+               return nil, err
+       }
+
        if err = builder.SetLoc(location); err != nil {
                return nil, err
        }
diff --git a/table/metadata_builder_internal_test.go 
b/table/metadata_builder_internal_test.go
index 0ff33869..0c733110 100644
--- a/table/metadata_builder_internal_test.go
+++ b/table/metadata_builder_internal_test.go
@@ -71,15 +71,18 @@ func builderWithoutChanges(formatVersion int) 
MetadataBuilder {
        if err = builder.SetFormatVersion(formatVersion); err != nil {
                panic(err)
        }
-       if err = builder.AddSortOrder(&sortOrder, true); err != nil {
-               panic(err)
-       }
        if err = builder.AddSchema(&tableSchema); err != nil {
                panic(err)
        }
        if err = builder.SetCurrentSchemaID(-1); err != nil {
                panic(err)
        }
+       if err = builder.AddSortOrder(&sortOrder, true); err != nil {
+               panic(err)
+       }
+       if err = builder.SetDefaultSortOrderID(-1); err != nil {
+               panic(err)
+       }
        if err = builder.AddPartitionSpec(&partitionSpec, true); err != nil {
                panic(err)
        }
@@ -331,6 +334,91 @@ func TestCannotAddDuplicateSnapshotID(t *testing.T) {
        require.ErrorContains(t, builder.AddSnapshot(&snapshot), "can't add 
snapshot with id 2, already exists")
 }
 
+func TestAddIncompatibleCurrentSchemaFails(t *testing.T) {
+       builder := builderWithoutChanges(2)
+       addedSchema := iceberg.NewSchema(1)
+       err := builder.AddSchema(addedSchema)
+       require.NoError(t, err)
+       err = builder.SetCurrentSchemaID(1)
+       require.NoError(t, err)
+       _, err = builder.Build()
+       require.ErrorContains(t, err, "with source id 3 not found in schema")
+}
+
+func TestActiveSchemaCannotBeRemoved(t *testing.T) {
+       builder := builderWithoutChanges(2)
+       // Try to remove the current schema
+       require.ErrorContains(t, builder.RemoveSchemas([]int{0}), "can't remove 
current schema with id 0")
+}
+
+func TestRemoveSchemas(t *testing.T) {
+       meta, err := getTestTableMetadata("TableMetadataV2Valid.json")
+       require.NoError(t, err)
+       require.Len(t, meta.Schemas(), 2, "expected 2 schemas in the metadata")
+       builder, err := MetadataBuilderFromBase(meta)
+       require.NoError(t, err)
+       err = builder.RemoveSchemas([]int{0})
+       require.NoError(t, err, "expected to remove schema with ID 1")
+       newMeta, err := builder.Build()
+       require.NoError(t, err)
+       require.Len(t, newMeta.Schemas(), 1, "expected 1 schema in the metadata 
after removal")
+       require.Equal(t, 1, newMeta.CurrentSchema().ID, "expected current 
schema to be 1")
+       require.Equal(t, 1, newMeta.(*metadataV2).CurrentSchemaID)
+       require.Len(t, builder.updates, 1, "expected one update for schema 
removal")
+       require.Equal(t, builder.updates[0].Action(), UpdateRemoveSchemas)
+       require.Equal(t, builder.updates[0].(*removeSchemasUpdate).SchemaIDs, 
[]int{0}, "expected schema ID 0 to be removed")
+}
+
+// Java: TestTableMetadata.testUpdateSchema
+func TestUpdateSchema(t *testing.T) {
+       // Test schema updates and evolution
+       schema1 := iceberg.NewSchema(
+               0,
+               iceberg.NestedField{ID: 1, Name: "y", Type: 
iceberg.PrimitiveTypes.Int64, Required: true, Doc: "comment"},
+       )
+
+       meta, err := NewMetadata(
+               schema1,
+               iceberg.UnpartitionedSpec,
+               UnsortedSortOrder,
+               "s3://bucket/test/location",
+               map[string]string{},
+       )
+       require.NoError(t, err)
+
+       require.Equal(t, 0, meta.CurrentSchema().ID)
+       require.Len(t, meta.Schemas(), 1)
+       require.Equal(t, 1, meta.LastColumnID())
+
+       // Update schema by adding a field
+       schema2 := iceberg.NewSchema(
+               1,
+               iceberg.NestedField{ID: 1, Name: "y", Type: 
iceberg.PrimitiveTypes.Int64, Required: true, Doc: "comment"},
+               iceberg.NestedField{ID: 2, Name: "x", Type: 
iceberg.PrimitiveTypes.String, Required: true},
+       )
+
+       builder, err := MetadataBuilderFromBase(meta)
+       require.NoError(t, err)
+
+       err = builder.AddSchema(schema2)
+       require.NoError(t, err)
+
+       err = builder.SetCurrentSchemaID(-1) // Use last added
+       require.NoError(t, err)
+
+       updatedMeta, err := builder.Build()
+       require.NoError(t, err)
+
+       require.Equal(t, 1, updatedMeta.CurrentSchema().ID)
+       require.Len(t, updatedMeta.Schemas(), 2)
+       require.Equal(t, 2, updatedMeta.LastColumnID())
+
+       // Verify both schemas are preserved
+       schemas := updatedMeta.Schemas()
+       require.True(t, schemas[0].Equals(schema1))
+       require.True(t, schemas[1].Equals(schema2))
+}
+
 func TestRemoveMainSnapshotRef(t *testing.T) {
        meta, err := getTestTableMetadata("TableMetadataV2Valid.json")
        require.NoError(t, err)
diff --git a/table/metadata_internal_test.go b/table/metadata_internal_test.go
index 87c3fd55..78bc5fe0 100644
--- a/table/metadata_internal_test.go
+++ b/table/metadata_internal_test.go
@@ -755,7 +755,9 @@ func TestMetadataV2Serialize(t *testing.T) {
 func TestMetadataBuilderSetDefaultSpecIDLastPartition(t *testing.T) {
        builder, err := NewMetadataBuilder()
        assert.NoError(t, err)
-
+       schema := schema()
+       assert.NoError(t, builder.AddSchema(&schema))
+       assert.NoError(t, builder.SetCurrentSchemaID(-1))
        partitionSpec := iceberg.NewPartitionSpecID(0)
        assert.NoError(t, builder.AddPartitionSpec(&partitionSpec, false))
 
@@ -986,6 +988,48 @@ func TestDefaultPartitionSpec(t *testing.T) {
        require.Equal(t, partitionSpec.ID(), defaultSpecID)
 }
 
+func TestTableMetadataV1SchemasWithoutCurrentId(t *testing.T) {
+       meta, err := 
getTestTableMetadata("TableMetadataV1SchemasWithoutCurrentId.json")
+       require.NoError(t, err)
+       require.Equal(t, meta.(*metadataV1).Version(), 1)
+       require.Equal(t, meta.TableUUID(), 
uuid.MustParse("d20125c8-7284-442c-9aea-15fee620737c"))
+       schema := meta.CurrentSchema()
+       require.Equal(t, len(schema.Fields()), 3)
+       require.Equal(t, schema.Fields()[0].Name, "x")
+       require.Equal(t, schema.Fields()[1].Name, "y")
+       require.Equal(t, schema.Fields()[2].Name, "z")
+}
+
+func TestTableMetadataV1NoValidSchema(t *testing.T) {
+       meta, err := getTestTableMetadata("TableMetadataV1NoValidSchema.json")
+       require.ErrorContains(t, err, "invalid metadata: current-schema-id -1 
can't be found in any schema")
+       require.Nil(t, meta)
+}
+
+func TestTableMetadataV2SchemaNotFound(t *testing.T) {
+       meta, err := 
getTestTableMetadata("TableMetadataV2CurrentSchemaNotFound.json")
+       require.ErrorContains(t, err, "invalid metadata: current-schema-id 2 
can't be found in any schema")
+       require.Nil(t, meta)
+}
+
+func TestTableMetadataV2MissingSchemas(t *testing.T) {
+       meta, err := getTestTableMetadata("TableMetadataV2MissingSchemas.json")
+       require.ErrorContains(t, err, "invalid metadata: current-schema-id -1 
can't be found in any schema")
+       require.Nil(t, meta)
+}
+
+// Java: TestTableMetadata.testParseSchemaIdentifierFields
+func TestParseSchemaIdentifierFields(t *testing.T) {
+       meta, err := getTestTableMetadata("TableMetadataV2Valid.json")
+       require.NoError(t, err)
+       // Verify identifier fields
+       schemas := meta.Schemas()
+       require.Len(t, schemas, 2)
+
+       require.Empty(t, schemas[0].IdentifierFieldIDs)
+       require.Equal(t, []int{1, 2}, schemas[1].IdentifierFieldIDs)
+}
+
 func getTestTableMetadata(fileName string) (Metadata, error) {
        fCont, err := os.ReadFile(path.Join("testdata", fileName))
        if err != nil {
diff --git a/table/sorting.go b/table/sorting.go
index 1ae9f053..d72ceb78 100644
--- a/table/sorting.go
+++ b/table/sorting.go
@@ -137,6 +137,30 @@ type SortOrder struct {
        Fields  []SortField `json:"fields"`
 }
 
+func (s *SortOrder) CheckCompatibility(schema *iceberg.Schema) error {
+       if s == nil {
+               return nil
+       }
+
+       for _, field := range s.Fields {
+               f, ok := schema.FindFieldByID(field.SourceID)
+               if !ok {
+                       return fmt.Errorf("sort field with source id %d not 
found in schema", field.SourceID)
+               }
+
+               if _, ok := f.Type.(iceberg.PrimitiveType); !ok {
+                       return fmt.Errorf("cannot sort by non-primitive source 
field: %s", f.Type.Type())
+               }
+
+               // FIXME: field.Transform should be made required
+               if field.Transform != nil && 
!field.Transform.CanTransform(f.Type) {
+                       return fmt.Errorf("invalid source type %s for transform 
%s", f.Type.Type(), field.Transform)
+               }
+       }
+
+       return nil
+}
+
 func (s SortOrder) Equals(rhs SortOrder) bool {
        return s.OrderID == rhs.OrderID &&
                slices.Equal(s.Fields, rhs.Fields)
diff --git a/table/testdata/TableMetadataV1NoValidSchema.json 
b/table/testdata/TableMetadataV1NoValidSchema.json
new file mode 100644
index 00000000..b29c2fa3
--- /dev/null
+++ b/table/testdata/TableMetadataV1NoValidSchema.json
@@ -0,0 +1,45 @@
+{
+  "format-version": 1,
+  "table-uuid": "d20125c8-7284-442c-9aea-15fee620737c",
+  "location": "s3://bucket/test/location",
+  "last-updated-ms": 1602638573874,
+  "last-column-id": 3,
+  "schemas": [
+    {
+      "fields": [
+        {
+          "id": 1,
+          "name": "x",
+          "required": true,
+          "type": "long"
+        },
+        {
+          "id": 2,
+          "name": "y",
+          "required": true,
+          "type": "long",
+          "doc": "comment"
+        },
+        {
+          "id": 3,
+          "name": "z",
+          "required": true,
+          "type": "long"
+        }
+      ],
+      "schema-id": 0,
+      "type": "struct"
+    }
+  ],
+  "partition-spec": [
+    {
+      "name": "x",
+      "transform": "identity",
+      "source-id": 1,
+      "field-id": 1000
+    }
+  ],
+  "properties": {},
+  "current-snapshot-id": -1,
+  "snapshots": []
+}
\ No newline at end of file
diff --git a/table/testdata/TableMetadataV1SchemasWithoutCurrentId.json 
b/table/testdata/TableMetadataV1SchemasWithoutCurrentId.json
new file mode 100644
index 00000000..7276c2a8
--- /dev/null
+++ b/table/testdata/TableMetadataV1SchemasWithoutCurrentId.json
@@ -0,0 +1,69 @@
+{
+  "format-version": 1,
+  "table-uuid": "d20125c8-7284-442c-9aea-15fee620737c",
+  "location": "s3://bucket/test/location",
+  "last-updated-ms": 1602638573874,
+  "last-column-id": 3,
+  "schemas": [
+    {
+      "fields": [
+        {
+          "id": 1,
+          "name": "x",
+          "required": true,
+          "type": "long"
+        },
+        {
+          "id": 2,
+          "name": "y",
+          "required": true,
+          "type": "long",
+          "doc": "comment"
+        },
+        {
+          "id": 3,
+          "name": "z",
+          "required": true,
+          "type": "long"
+        }
+      ],
+      "schema-id": 0,
+      "type": "struct"
+    }
+  ],
+  "schema": {
+    "type": "struct",
+    "fields": [
+      {
+        "id": 1,
+        "name": "x",
+        "required": true,
+        "type": "long"
+      },
+      {
+        "id": 2,
+        "name": "y",
+        "required": true,
+        "type": "long",
+        "doc": "comment"
+      },
+      {
+        "id": 3,
+        "name": "z",
+        "required": true,
+        "type": "long"
+      }
+    ]
+  },
+  "partition-spec": [
+    {
+      "name": "x",
+      "transform": "identity",
+      "source-id": 1,
+      "field-id": 1000
+    }
+  ],
+  "properties": {},
+  "current-snapshot-id": -1,
+  "snapshots": []
+}
\ No newline at end of file
diff --git a/table/testdata/TableMetadataV2CurrentSchemaNotFound.json 
b/table/testdata/TableMetadataV2CurrentSchemaNotFound.json
new file mode 100644
index 00000000..d010785b
--- /dev/null
+++ b/table/testdata/TableMetadataV2CurrentSchemaNotFound.json
@@ -0,0 +1,88 @@
+{
+  "format-version": 2,
+  "table-uuid": "9c12d441-03fe-4693-9a96-a0705ddf69c1",
+  "location": "s3://bucket/test/location",
+  "last-sequence-number": 34,
+  "last-updated-ms": 1602638573590,
+  "last-column-id": 3,
+  "current-schema-id": 2,
+  "schemas": [
+    {
+      "type": "struct",
+      "schema-id": 0,
+      "fields": [
+        {
+          "id": 1,
+          "name": "x",
+          "required": true,
+          "type": "long"
+        }
+      ]
+    },
+    {
+      "type": "struct",
+      "schema-id": 1,
+      "fields": [
+        {
+          "id": 1,
+          "name": "x",
+          "required": true,
+          "type": "long"
+        },
+        {
+          "id": 2,
+          "name": "y",
+          "required": true,
+          "type": "long",
+          "doc": "comment"
+        },
+        {
+          "id": 3,
+          "name": "z",
+          "required": true,
+          "type": "long"
+        }
+      ]
+    }
+  ],
+  "default-spec-id": 0,
+  "partition-specs": [
+    {
+      "spec-id": 0,
+      "fields": [
+        {
+          "name": "x",
+          "transform": "identity",
+          "source-id": 1,
+          "field-id": 1000
+        }
+      ]
+    }
+  ],
+  "last-partition-id": 1000,
+  "default-sort-order-id": 3,
+  "sort-orders": [
+    {
+      "order-id": 3,
+      "fields": [
+        {
+          "transform": "identity",
+          "source-id": 2,
+          "direction": "asc",
+          "null-order": "nulls-first"
+        },
+        {
+          "transform": "bucket[4]",
+          "source-id": 3,
+          "direction": "desc",
+          "null-order": "nulls-last"
+        }
+      ]
+    }
+  ],
+  "properties": {},
+  "current-snapshot-id": -1,
+  "snapshots": [],
+  "snapshot-log": [],
+  "metadata-log": []
+}
\ No newline at end of file
diff --git a/table/testdata/TableMetadataV2MissingSchemas.json 
b/table/testdata/TableMetadataV2MissingSchemas.json
new file mode 100644
index 00000000..3754354e
--- /dev/null
+++ b/table/testdata/TableMetadataV2MissingSchemas.json
@@ -0,0 +1,71 @@
+{
+  "format-version": 2,
+  "table-uuid": "9c12d441-03fe-4693-9a96-a0705ddf69c1",
+  "location": "s3://bucket/test/location",
+  "last-sequence-number": 34,
+  "last-updated-ms": 1602638573590,
+  "last-column-id": 3,
+  "schema": {
+    "type": "struct",
+    "fields": [
+      {
+        "id": 1,
+        "name": "x",
+        "required": true,
+        "type": "long"
+      },
+      {
+        "id": 2,
+        "name": "y",
+        "required": true,
+        "type": "long",
+        "doc": "comment"
+      },
+      {
+        "id": 3,
+        "name": "z",
+        "required": true,
+        "type": "long"
+      }
+    ]
+  },
+  "default-spec-id": 0,
+  "partition-specs": [
+    {
+      "spec-id": 0,
+      "fields": [
+        {
+          "name": "x",
+          "transform": "identity",
+          "source-id": 1,
+          "field-id": 1000
+        }
+      ]
+    }
+  ],
+  "default-sort-order-id": 3,
+  "sort-orders": [
+    {
+      "order-id": 3,
+      "fields": [
+        {
+          "transform": "identity",
+          "source-id": 2,
+          "direction": "asc",
+          "null-order": "nulls-first"
+        },
+        {
+          "transform": "bucket[4]",
+          "source-id": 3,
+          "direction": "desc",
+          "null-order": "nulls-last"
+        }
+      ]
+    }
+  ],
+  "properties": {},
+  "current-snapshot-id": -1,
+  "snapshots": [],
+  "snapshot-log": [],
+  "metadata-log": []
+}
\ No newline at end of file
diff --git a/table/updates.go b/table/updates.go
index dd867aab..996e1db4 100644
--- a/table/updates.go
+++ b/table/updates.go
@@ -541,18 +541,18 @@ func (u *removeSpecUpdate) Apply(builder 
*MetadataBuilder) error {
 
 type removeSchemasUpdate struct {
        baseUpdate
-       SchemaIds []int64 `json:"schema-ids"`
+       SchemaIDs []int `json:"schema-i-ds"`
 }
 
 // NewRemoveSchemasUpdate creates a new Update that removes a list of schemas 
from
 // the table metadata.
-func NewRemoveSchemasUpdate(schemaIds []int64) *removeSchemasUpdate {
+func NewRemoveSchemasUpdate(schemaIds []int) *removeSchemasUpdate {
        return &removeSchemasUpdate{
                baseUpdate: baseUpdate{ActionName: UpdateRemoveSchemas},
-               SchemaIds:  schemaIds,
+               SchemaIDs:  schemaIds,
        }
 }
 
 func (u *removeSchemasUpdate) Apply(builder *MetadataBuilder) error {
-       return fmt.Errorf("%w: %s", iceberg.ErrNotImplemented, 
UpdateRemoveSchemas)
+       return builder.RemoveSchemas(u.SchemaIDs)
 }
diff --git a/table/updates_test.go b/table/updates_test.go
index c96a97c4..ade8a4da 100644
--- a/table/updates_test.go
+++ b/table/updates_test.go
@@ -19,7 +19,6 @@ package table
 
 import (
        "encoding/json"
-       "errors"
        "testing"
 
        "github.com/apache/iceberg-go"
@@ -218,15 +217,3 @@ func TestUnmarshalUpdates(t *testing.T) {
                })
        }
 }
-
-func TestRemoveSchemas(t *testing.T) {
-       var builder *MetadataBuilder
-       removeSchemas := removeSchemasUpdate{
-               SchemaIds: []int64{},
-       }
-       t.Run("remove schemas should fail", func(t *testing.T) {
-               if err := removeSchemas.Apply(builder); !errors.Is(err, 
iceberg.ErrNotImplemented) {
-                       t.Fatalf("Expected unimplemented error, got %v", err)
-               }
-       })
-}

Reply via email to