Skip to content

Commit

Permalink
logicalTypes: fix alternative encoding for decimal types
Browse files Browse the repository at this point in the history
The alternative is an explicit codec decimal type, additionally, this
only worked for a small subset of values that were both valid native
and valid binary encoding. Since we want to stay in the native value
space, we just need to convert from native primative to native logical
type. In order to do this we just do an encode/decode step for decimal
logical types.
  • Loading branch information
rockwotj committed Jan 20, 2025
1 parent e10381a commit e00ecff
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 12 deletions.
27 changes: 15 additions & 12 deletions record.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,20 +117,23 @@ func makeRecordCodec(st map[string]*Codec, enclosingNamespace string, schemaMap
// TODO: change to schemaCanonical below
defaultValue = Union(fieldCodec.schemaOriginal, defaultValue)
default:
debug("fieldName: %q; type: %q; defaultValue: %T(%#v)\n", fieldName, c.typeName, defaultValue, defaultValue)

// Support defaults for logical types
if logicalType, ok := fieldSchemaMap["logicalType"]; ok {
if logicalType == "decimal" {
v, ok := defaultValue.(string)
if !ok {
return nil, fmt.Errorf("Record %q field %q: default value ought to have a string type got: %T", c.typeName, fieldName, defaultValue)
}
defaultValue, _, err = fieldCodec.nativeFromBinary([]byte(v))
if err != nil {
return nil, fmt.Errorf("Record %q field %q: default value ought to decode from textual: %w", c.typeName, fieldName, err)
}
if fieldSchemaMap["logicalType"] == "decimal" || typeNameShort == "decimal" {
v, ok := defaultValue.(string)
if !ok {
return nil, fmt.Errorf("Record %q field %q: default value ought to have a string type got: %T", c.typeName, fieldName, defaultValue)
}
// the default is a native byte array, we need to encode it first, then we can decode it into a *big.Rat
encoded, err := bytesBinaryFromNative(nil, v)
if err != nil {
return nil, fmt.Errorf("Record %q field %q: default value ought to be encodable from native binary: %w", c.typeName, fieldName, err)
}
defaultValue, _, err = fieldCodec.nativeFromBinary(encoded)
if err != nil {
return nil, fmt.Errorf("Record %q field %q: default value ought to decode from textual: %w", c.typeName, fieldName, err)
}
} else {
debug("fieldName: %q; type: %q; defaultValue: %T(%#v)\n", fieldName, c.typeName, defaultValue, defaultValue)
}
}

Expand Down
4 changes: 4 additions & 0 deletions record_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,10 @@ func TestRecordFieldFixedDefaultValue(t *testing.T) {
testSchemaValid(t, `{"type": "record", "name": "r1", "fields":[{"name": "f1", "type": {"type": "fixed", "name": "someFixed", "size": 1}, "default": "\u0000"}]}`)
}

func TestRecordFieldDecimalDefaultValue(t *testing.T) {
testSchemaValid(t, `{"type": "record", "name": "r1", "fields":[{"name": "f1", "type": {"type": "bytes", "scale": 2, "precision":10, "logicalType":"deicmal"}, "default": "d"}]}`)
}

func TestRecordFieldDefaultValueTypes(t *testing.T) {
t.Run("success", func(t *testing.T) {
codec, err := NewCodec(`{"type": "record", "name": "r1", "fields":[{"name": "someBoolean", "type": "boolean", "default": true},{"name": "someBytes", "type": "bytes", "default": "0"},{"name": "someDouble", "type": "double", "default": 0},{"name": "someFloat", "type": "float", "default": 0},{"name": "someInt", "type": "int", "default": 0},{"name": "someLong", "type": "long", "default": 0},{"name": "someString", "type": "string", "default": "0"}, {"name":"someTimestamp", "type":"long", "logicalType":"timestamp-millis","default":0}, {"name": "someDecimal", "type": "bytes", "logicalType": "decimal", "precision": 4, "scale": 2, "default":"\u0000"}]}`)
Expand Down

0 comments on commit e00ecff

Please sign in to comment.