Skip to content

Commit

Permalink
allow null types to have properties
Browse files Browse the repository at this point in the history
  • Loading branch information
jhump committed Oct 11, 2024
1 parent 57091b0 commit cc31452
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 8 deletions.
24 changes: 23 additions & 1 deletion schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -1429,9 +1429,22 @@ func (s *FixedSchema) CacheFingerprint() [32]byte {

// NullSchema is an Avro null type schema.
type NullSchema struct {
properties
fingerprinter
}

// NewNullSchema creates a new NullSchema.
func NewNullSchema(opts ...SchemaOption) *NullSchema {
var cfg schemaConfig
for _, opt := range opts {
opt(&cfg)
}

return &NullSchema{
properties: newProperties(cfg.props, primitiveReserved),
}
}

// Type returns the type of the schema.
func (s *NullSchema) Type() Type {
return Null
Expand All @@ -1444,7 +1457,16 @@ func (s *NullSchema) String() string {

// MarshalJSON marshals the schema to json.
func (s *NullSchema) MarshalJSON() ([]byte, error) {
return []byte(`"null"`), nil
if len(s.props) == 0 {
return []byte(`"null"`), nil
}
buf := new(bytes.Buffer)
buf.WriteString(`{"type":"null"`)
if err := s.marshalPropertiesToJSON(buf); err != nil {
return nil, err
}
buf.WriteString("}")
return buf.Bytes(), nil
}

// Fingerprint returns the SHA256 fingerprint of the schema.
Expand Down
4 changes: 4 additions & 0 deletions schema_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ func TestSchema_JSON(t *testing.T) {
input: `{"type":"null"}`,
json: `"null"`,
},
{
input: `{"type":"null","other":"foo"}`,
json: `{"type":"null","other":"foo"}`,
},
{
input: `"boolean"`,
json: `"boolean"`,
Expand Down
14 changes: 7 additions & 7 deletions schema_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,7 @@ func parseComplexType(namespace string, m map[string]any, seen seenCache, cache
typ := Type(str)

switch typ {
case Null:
// TODO: Per the spec, "null" is a primitive type so should be permitted to
// have other properties/metadata.
// https://avro.apache.org/docs/1.12.0/specification/#primitive-types
return &NullSchema{}, nil

case String, Bytes, Int, Long, Float, Double, Boolean:
case String, Bytes, Int, Long, Float, Double, Boolean, Null:
return parsePrimitive(typ, m)

case Record, Error:
Expand Down Expand Up @@ -172,6 +166,9 @@ type primitiveSchema struct {

func parsePrimitive(typ Type, m map[string]any) (Schema, error) {
if len(m) == 0 {
if typ == Null {
return &NullSchema{}, nil
}
return NewPrimitiveSchema(typ, nil), nil
}

Expand All @@ -191,6 +188,9 @@ func parsePrimitive(typ Type, m map[string]any) (Schema, error) {
}
}

if typ == Null {
return NewNullSchema(WithProps(p.Props)), nil
}
return NewPrimitiveSchema(typ, logical, WithProps(p.Props)), nil
}

Expand Down
48 changes: 48 additions & 0 deletions schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func TestNullSchema(t *testing.T) {
schemas := []string{
`null`,
`{"type":"null"}`,
`{"type":"null", "other-property": 123, "another-property": ["a","b","c"]}`,
}

for _, schm := range schemas {
Expand Down Expand Up @@ -1892,6 +1893,27 @@ func TestParse_PreservesAllProperties(t *testing.T) {
}, rec.Props())
},
},
{
name: "null",
schema: `{
"type": "null",
"name": "SomeMap",
"logicalType": "weights",
"precision": "abc",
"scale": "def",
"other": [1,2,3]
}`,
check: func(t *testing.T, schema avro.Schema) {
rec := schema.(*avro.NullSchema)
assert.Equal(t, map[string]any{
"name": "SomeMap",
"logicalType": "weights",
"precision": "abc",
"scale": "def",
"other": []any{1.0, 2.0, 3.0},
}, rec.Props())
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
Expand Down Expand Up @@ -2141,4 +2163,30 @@ func TestNewSchema_IgnoresInvalidProperties(t *testing.T) {
"other": true,
}, rec.Props())
})
t.Run("null", func(t *testing.T) {
rec := avro.NewNullSchema(
avro.WithProps(map[string]any{
// invalid (conflict with other type properties)
"type": false,
// valid
"name": 123,
"namespace": "abc",
"doc": "blah",
"aliases": "foo",
"other": true,
"logicalType": "baz",
"precision": "abc",
"scale": "def",
}))
assert.Equal(t, map[string]any{
"name": 123,
"namespace": "abc",
"doc": "blah",
"aliases": "foo",
"other": true,
"logicalType": "baz",
"precision": "abc",
"scale": "def",
}, rec.Props())
})
}

0 comments on commit cc31452

Please sign in to comment.