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