diff --git a/go/iceberg/go.mod b/go/iceberg/go.mod index 66f79bc2fae5..fdfaa21d7861 100644 --- a/go/iceberg/go.mod +++ b/go/iceberg/go.mod @@ -26,6 +26,8 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go/iceberg/go.sum b/go/iceberg/go.sum index 79690f1eae5f..b7c96854d8f4 100644 --- a/go/iceberg/go.sum +++ b/go/iceberg/go.sum @@ -1,12 +1,24 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go/iceberg/schema_test.go b/go/iceberg/schema_test.go index d22d0b7e8a45..8d73eb543d6f 100644 --- a/go/iceberg/schema_test.go +++ b/go/iceberg/schema_test.go @@ -106,8 +106,8 @@ func TestNestedFieldToString(t *testing.T) { {2, "3: baz: optional boolean"}, {3, "4: qux: required list"}, {4, "6: quux: required map>"}, - {5, "11: location: required list>"}, - {6, "15: person: optional struct"}, + {5, "11: location: required list>"}, + {6, "15: person: optional struct<16: name: string, 17: age: required int>"}, } for _, tt := range tests { diff --git a/go/iceberg/types.go b/go/iceberg/types.go index 2a9ae126b9d5..8ca0ad7534fb 100644 --- a/go/iceberg/types.go +++ b/go/iceberg/types.go @@ -233,9 +233,17 @@ func (s *StructType) String() string { if i != 0 { b.WriteString(", ") } - b.WriteString(f.Name) - b.WriteString(": ") + fmt.Fprintf(&b, "%d: %s: ", + f.ID, f.Name) + if f.Required { + b.WriteString("required ") + } b.WriteString(f.Type.String()) + if f.Doc != "" { + b.WriteString(" (") + b.WriteString(f.Doc) + b.WriteByte(')') + } } b.WriteString(">") diff --git a/go/iceberg/types_test.go b/go/iceberg/types_test.go index 2d1af1acf179..f39f62207e13 100644 --- a/go/iceberg/types_test.go +++ b/go/iceberg/types_test.go @@ -26,12 +26,15 @@ import ( "github.com/stretchr/testify/require" ) -func TestRemainingTypes(t *testing.T) { +func TestTypesBasic(t *testing.T) { tests := []struct { expected string typ iceberg.Type }{ + {"boolean", iceberg.PrimitiveTypes.Bool}, + {"int", iceberg.PrimitiveTypes.Int32}, {"long", iceberg.PrimitiveTypes.Int64}, + {"float", iceberg.PrimitiveTypes.Float32}, {"double", iceberg.PrimitiveTypes.Float64}, {"date", iceberg.PrimitiveTypes.Date}, {"time", iceberg.PrimitiveTypes.Time}, @@ -62,3 +65,172 @@ func TestRemainingTypes(t *testing.T) { }) } } + +func TestFixedType(t *testing.T) { + typ := iceberg.FixedTypeOf(5) + assert.Equal(t, 5, typ.Len()) + assert.Equal(t, "fixed[5]", typ.String()) + assert.True(t, typ.Equals(iceberg.FixedTypeOf(5))) + assert.False(t, typ.Equals(iceberg.FixedTypeOf(6))) +} + +func TestDecimalType(t *testing.T) { + typ := iceberg.DecimalTypeOf(9, 2) + assert.Equal(t, 9, typ.Precision()) + assert.Equal(t, 2, typ.Scale()) + assert.Equal(t, "decimal(9, 2)", typ.String()) + assert.True(t, typ.Equals(iceberg.DecimalTypeOf(9, 2))) + assert.False(t, typ.Equals(iceberg.DecimalTypeOf(9, 3))) +} + +func TestStructType(t *testing.T) { + typ := &iceberg.StructType{ + FieldList: []iceberg.NestedField{ + {ID: 1, Name: "required_field", Type: iceberg.PrimitiveTypes.Int32, Required: true}, + {ID: 2, Name: "optional_field", Type: iceberg.FixedTypeOf(5), Required: false}, + {ID: 3, Name: "required_field", Type: &iceberg.StructType{ + FieldList: []iceberg.NestedField{ + {ID: 4, Name: "optional_field", Type: iceberg.DecimalTypeOf(8, 2), Required: false}, + {ID: 5, Name: "required_field", Type: iceberg.PrimitiveTypes.Int64, Required: false}, + }, + }, Required: false}, + }, + } + + assert.Len(t, typ.FieldList, 3) + assert.False(t, typ.Equals(&iceberg.StructType{FieldList: []iceberg.NestedField{{ID: 1, Name: "optional_field", Type: iceberg.PrimitiveTypes.Int32, Required: true}}})) + out, err := json.Marshal(typ) + require.NoError(t, err) + + var actual iceberg.StructType + require.NoError(t, json.Unmarshal(out, &actual)) + assert.True(t, typ.Equals(&actual)) +} + +func TestListType(t *testing.T) { + typ := &iceberg.ListType{ + ElementID: 1, + ElementRequired: false, + Element: &iceberg.StructType{ + FieldList: []iceberg.NestedField{ + {ID: 2, Name: "required_field", Type: iceberg.DecimalTypeOf(8, 2), Required: true}, + {ID: 3, Name: "optional_field", Type: iceberg.PrimitiveTypes.Int64, Required: false}, + }, + }, + } + + assert.IsType(t, (*iceberg.StructType)(nil), typ.ElementField().Type) + assert.Len(t, typ.ElementField().Type.(iceberg.NestedType).Fields(), 2) + assert.Equal(t, 1, typ.ElementField().ID) + assert.False(t, typ.Equals(&iceberg.ListType{ + ElementID: 1, + ElementRequired: true, + Element: &iceberg.StructType{ + FieldList: []iceberg.NestedField{ + {ID: 2, Name: "required_field", Type: iceberg.DecimalTypeOf(8, 2), Required: true}, + }, + }, + })) + + out, err := json.Marshal(typ) + require.NoError(t, err) + + var actual iceberg.ListType + require.NoError(t, json.Unmarshal(out, &actual)) + assert.True(t, typ.Equals(&actual)) +} + +func TestMapType(t *testing.T) { + typ := &iceberg.MapType{ + KeyID: 1, + KeyType: iceberg.PrimitiveTypes.Float64, + ValueID: 2, + ValueType: iceberg.PrimitiveTypes.UUID, + ValueRequired: false, + } + + assert.IsType(t, iceberg.PrimitiveTypes.Float64, typ.KeyField().Type) + assert.Equal(t, 1, typ.KeyField().ID) + assert.IsType(t, iceberg.PrimitiveTypes.UUID, typ.ValueField().Type) + assert.Equal(t, 2, typ.ValueField().ID) + assert.False(t, typ.Equals(&iceberg.MapType{ + KeyID: 1, KeyType: iceberg.PrimitiveTypes.Int64, + ValueID: 2, ValueType: iceberg.PrimitiveTypes.UUID, ValueRequired: false, + })) + assert.False(t, typ.Equals(&iceberg.MapType{ + KeyID: 1, KeyType: iceberg.PrimitiveTypes.Float64, + ValueID: 2, ValueType: iceberg.PrimitiveTypes.String, ValueRequired: true, + })) + + out, err := json.Marshal(typ) + require.NoError(t, err) + + var actual iceberg.MapType + require.NoError(t, json.Unmarshal(out, &actual)) + assert.True(t, typ.Equals(&actual)) +} + +var ( + NonParameterizedTypes = []iceberg.Type{ + iceberg.PrimitiveTypes.Bool, + iceberg.PrimitiveTypes.Int32, + iceberg.PrimitiveTypes.Int64, + iceberg.PrimitiveTypes.Float32, + iceberg.PrimitiveTypes.Float64, + iceberg.PrimitiveTypes.Date, + iceberg.PrimitiveTypes.Time, + iceberg.PrimitiveTypes.Timestamp, + iceberg.PrimitiveTypes.TimestampTz, + iceberg.PrimitiveTypes.String, + iceberg.PrimitiveTypes.Binary, + iceberg.PrimitiveTypes.UUID, + } +) + +func TestNonParameterizedTypeEquality(t *testing.T) { + for i, in := range NonParameterizedTypes { + for j, check := range NonParameterizedTypes { + if i == j { + assert.Truef(t, in.Equals(check), "expected %s == %s", in, check) + } else { + assert.Falsef(t, in.Equals(check), "expected %s != %s", in, check) + } + } + } +} + +func TestTypeStrings(t *testing.T) { + tests := []struct { + typ iceberg.Type + str string + }{ + {iceberg.PrimitiveTypes.Bool, "boolean"}, + {iceberg.PrimitiveTypes.Int32, "int"}, + {iceberg.PrimitiveTypes.Int64, "long"}, + {iceberg.PrimitiveTypes.Float32, "float"}, + {iceberg.PrimitiveTypes.Float64, "double"}, + {iceberg.PrimitiveTypes.Date, "date"}, + {iceberg.PrimitiveTypes.Time, "time"}, + {iceberg.PrimitiveTypes.Timestamp, "timestamp"}, + {iceberg.PrimitiveTypes.TimestampTz, "timestamptz"}, + {iceberg.PrimitiveTypes.String, "string"}, + {iceberg.PrimitiveTypes.UUID, "uuid"}, + {iceberg.PrimitiveTypes.Binary, "binary"}, + {iceberg.FixedTypeOf(22), "fixed[22]"}, + {iceberg.DecimalTypeOf(19, 25), "decimal(19, 25)"}, + {&iceberg.StructType{ + FieldList: []iceberg.NestedField{ + {ID: 1, Name: "required_field", Type: iceberg.PrimitiveTypes.String, Required: true, Doc: "this is a doc"}, + {ID: 2, Name: "optional_field", Type: iceberg.PrimitiveTypes.Int32, Required: true}, + }, + }, "struct<1: required_field: required string (this is a doc), 2: optional_field: required int>"}, + {&iceberg.ListType{ + ElementID: 22, Element: iceberg.PrimitiveTypes.String}, "list"}, + {&iceberg.MapType{KeyID: 19, KeyType: iceberg.PrimitiveTypes.String, ValueID: 25, ValueType: iceberg.PrimitiveTypes.Float64}, + "map"}, + } + + for _, tt := range tests { + assert.Equal(t, tt.str, tt.typ.String()) + } +}