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 d6d540d4 fix(schema): replace state during JSON decoding (#1628)
d6d540d4 is described below

commit d6d540d460ee8eb7cad03b1f1523b32b04723317
Author: Minh Vu <[email protected]>
AuthorDate: Wed Aug 5 18:58:25 2026 +0200

    fix(schema): replace state during JSON decoding (#1628)
    
    * fix(schema): replace state during JSON decoding
    
    * fix(schema): reset cached indexes without copying locks
    
    * test(schema): exercise cached lookup replacement
---
 schema.go      | 21 +++++++++++-----
 schema_test.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 94 insertions(+), 6 deletions(-)

diff --git a/schema.go b/schema.go
index 9f51474e..1a917fb8 100644
--- a/schema.go
+++ b/schema.go
@@ -277,10 +277,11 @@ func (s *Schema) FieldIDs() []int {
 
 func (s *Schema) UnmarshalJSON(b []byte) error {
        type Alias Schema
+       var decoded Schema
        aux := struct {
                Fields []NestedField `json:"fields"`
                *Alias
-       }{Alias: (*Alias)(s)}
+       }{Alias: (*Alias)(&decoded)}
 
        if err := json.Unmarshal(b, &aux); err != nil {
                return err
@@ -290,13 +291,21 @@ func (s *Schema) UnmarshalJSON(b []byte) error {
                return err
        }
 
-       s.init()
-
-       s.fields = aux.Fields
-       if s.IdentifierFieldIDs == nil {
-               s.IdentifierFieldIDs = []int{}
+       decoded.fields = aux.Fields
+       if decoded.IdentifierFieldIDs == nil {
+               decoded.IdentifierFieldIDs = []int{}
        }
 
+       s.ID = decoded.ID
+       s.IdentifierFieldIDs = decoded.IdentifierFieldIDs
+       s.fields = decoded.fields
+       s.idToName.Store(nil)
+       s.idToField.Store(nil)
+       s.nameToID.Store(nil)
+       s.nameToIDLower.Store(nil)
+       s.idToAccessor.Store(nil)
+       s.init()
+
        return nil
 }
 
diff --git a/schema_test.go b/schema_test.go
index 34dbe741..1aaedea9 100644
--- a/schema_test.go
+++ b/schema_test.go
@@ -570,6 +570,85 @@ func TestUnmarshalSchema(t *testing.T) {
        assert.True(t, tableSchemaSimple.Equals(&schema))
 }
 
+func TestUnmarshalSchemaReplacesExistingState(t *testing.T) {
+       schema := iceberg.NewSchemaWithIdentifiers(7, []int{1},
+               iceberg.NestedField{ID: 1, Name: "old", Type: 
iceberg.PrimitiveTypes.String},
+       )
+       _, ok := schema.FindFieldByID(1)
+       require.True(t, ok)
+       _, ok = schema.FindColumnName(1)
+       require.True(t, ok)
+       _, ok = schema.FindFieldByName("old")
+       require.True(t, ok)
+       _, ok = schema.FindFieldByNameCaseInsensitive("OLD")
+       require.True(t, ok)
+       assert.Contains(t, schema.NameMapping().String(), "old")
+
+       require.NoError(t, json.Unmarshal([]byte(`{
+               "type": "struct",
+               "fields": [{"id": 2, "name": "new", "type": "long", "required": 
true}]
+       }`), schema))
+
+       assert.Zero(t, schema.ID)
+       assert.Empty(t, schema.IdentifierFieldIDs)
+       assert.Equal(t, 1, schema.NumFields())
+       _, ok = schema.FindFieldByID(1)
+       assert.False(t, ok)
+       _, ok = schema.FindColumnName(1)
+       assert.False(t, ok)
+       _, ok = schema.FindFieldByName("old")
+       assert.False(t, ok)
+       _, ok = schema.FindFieldByNameCaseInsensitive("OLD")
+       assert.False(t, ok)
+       field, ok := schema.FindFieldByID(2)
+       require.True(t, ok)
+       assert.Equal(t, "new", field.Name)
+       assert.Contains(t, schema.NameMapping().String(), "new")
+       assert.NotContains(t, schema.NameMapping().String(), "old")
+}
+
+func TestUnmarshalSchemaPreservesExistingStateOnError(t *testing.T) {
+       schema := iceberg.NewSchemaWithIdentifiers(7, []int{1},
+               iceberg.NestedField{ID: 1, Name: "old", Type: 
iceberg.PrimitiveTypes.String},
+       )
+       _, ok := schema.FindFieldByID(1)
+       require.True(t, ok)
+       _, ok = schema.FindColumnName(1)
+       require.True(t, ok)
+       _, ok = schema.FindFieldByName("old")
+       require.True(t, ok)
+       _, ok = schema.FindFieldByNameCaseInsensitive("OLD")
+       require.True(t, ok)
+       assert.Contains(t, schema.NameMapping().String(), "old")
+       assert.False(t, schema.FieldHasOptionalParent(1))
+
+       err := json.Unmarshal([]byte(`{
+               "type": "struct",
+               "fields": [
+                       {"id": 2, "name": "first", "type": "long", "required": 
true},
+                       {"id": 2, "name": "duplicate", "type": "string", 
"required": false}
+               ]
+       }`), schema)
+       require.ErrorIs(t, err, iceberg.ErrInvalidSchema)
+
+       assert.Equal(t, 7, schema.ID)
+       assert.Equal(t, []int{1}, schema.IdentifierFieldIDs)
+       assert.Equal(t, 1, schema.NumFields())
+       field, ok := schema.FindFieldByID(1)
+       require.True(t, ok)
+       assert.Equal(t, "old", field.Name)
+       _, ok = schema.FindColumnName(1)
+       require.True(t, ok)
+       field, ok = schema.FindFieldByName("old")
+       require.True(t, ok)
+       assert.Equal(t, "old", field.Name)
+       field, ok = schema.FindFieldByNameCaseInsensitive("OLD")
+       require.True(t, ok)
+       assert.Equal(t, "old", field.Name)
+       assert.Contains(t, schema.NameMapping().String(), "old")
+       assert.False(t, schema.FieldHasOptionalParent(1))
+}
+
 func TestUnmarshalSchemaRejectsDuplicateFieldIDs(t *testing.T) {
        tests := []struct {
                name     string

Reply via email to