Repository: hamba/avro
Pull request #277 (2023-07-27 05:17:35)

Ability to serialize uint64 values as fixed-length entries:
This pull request adds the ability to fully express native, uint64 values as fixed-length records within Avro.

See https://stackoverflow.com/a/75242092/151741 for more details.

joliver: Tests have been added along with an example use case in the most recent commit message.

Patch:
diff --git a/README.md b/README.md
index 34ba5ec..740c970 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,7 @@ More examples in the [godoc](https://godoc.org/github.com/hamba/avro).
 | `double`                | `float64`                                              | `float64`                |
 | `long`                  | `int64`, `uint32`\*                                    | `int64`, `uint32`        |
 | `int`                   | `int`, `int32`, `int16`, `int8`, `uint8`\*, `uint16`\* | `int`, `uint8`, `uint16` |
+| `fixed`                 | `uint64`                                               | `uint64`                 |
 | `string`                | `string`                                               | `string`                 |
 | `array`                 | `[]T`                                                  | `[]any`          |
 | `enum`                  | `string`                                               | `string`                 |
diff --git a/codec_fixed.go b/codec_fixed.go
index 6af43eb..1467f57 100644
--- a/codec_fixed.go
+++ b/codec_fixed.go
@@ -20,6 +20,14 @@ func createDecoderOfFixed(schema Schema, typ reflect2.Type) ValDecoder {
 		}
 		return &fixedCodec{arrayType: typ.(*reflect2.UnsafeArrayType)}
 
+	case reflect.Uint64:
+		fixed := schema.(*FixedSchema)
+		if fixed.Size() != 8 {
+			break
+		}
+
+		return &fixedUint64Codec{}
+
 	case reflect.Struct:
 		ls := fixed.Logical()
 		if ls == nil {
@@ -51,6 +59,14 @@ func createEncoderOfFixed(schema Schema, typ reflect2.Type) ValEncoder {
 		}
 		return &fixedCodec{arrayType: typ.(*reflect2.UnsafeArrayType)}
 
+	case reflect.Uint64:
+		fixed := schema.(*FixedSchema)
+		if fixed.Size() != 8 {
+			break
+		}
+
+		return &fixedUint64Codec{}
+
 	case reflect.Ptr:
 		ptrType := typ.(*reflect2.UnsafePtrType)
 		elemType := ptrType.Elem()
@@ -80,6 +96,20 @@ func createEncoderOfFixed(schema Schema, typ reflect2.Type) ValEncoder {
 	}
 }
 
+type fixedUint64Codec [8]byte
+
+func (c *fixedUint64Codec) Decode(ptr unsafe.Pointer, r *Reader) {
+	buffer := c[:]
+	r.Read(buffer)
+	*(*uint64)(ptr) = binary.BigEndian.Uint64(buffer)
+}
+
+func (c *fixedUint64Codec) Encode(ptr unsafe.Pointer, w *Writer) {
+	buffer := c[:]
+	binary.BigEndian.PutUint64(buffer, *(*uint64)(ptr))
+	_, _ = w.Write(buffer)
+}
+
 type fixedCodec struct {
 	arrayType *reflect2.UnsafeArrayType
 }


Test patch:
diff --git a/decoder_fixed_test.go b/decoder_fixed_test.go
index 11c96df..45b6ad3 100644
--- a/decoder_fixed_test.go
+++ b/decoder_fixed_test.go
@@ -3,6 +3,7 @@ package avro_test
 import (
 	"bytes"
 	"fmt"
+	"math"
 	"math/big"
 	"testing"
 
@@ -128,3 +129,30 @@ func TestDecoder_FixedLogicalDurationSizeNot12(t *testing.T) {
 	assert.Error(t, err)
 	assert.Equal(t, fmt.Errorf("avro: avro.LogicalDuration is unsupported for Avro fixed, size=11"), err)
 }
+
+func TestDecoder_FixedUint64_Full(t *testing.T) {
+	defer ConfigTeardown()
+
+	data := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
+	schema := `{"type":"fixed", "name": "test", "size": 8}`
+	dec, err := avro.NewDecoder(schema, bytes.NewReader(data))
+	require.NoError(t, err)
+
+	var got uint64
+	err = dec.Decode(&got)
+	require.NoError(t, err)
+	assert.Equal(t, uint64(math.MaxUint64), got)
+}
+func TestDecoder_FixedUint64_Simple(t *testing.T) {
+	defer ConfigTeardown()
+
+	data := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}
+	schema := `{"type":"fixed", "name": "test", "size": 8}`
+	dec, err := avro.NewDecoder(schema, bytes.NewReader(data))
+	require.NoError(t, err)
+
+	var got uint64
+	err = dec.Decode(&got)
+	require.NoError(t, err)
+	assert.Equal(t, uint64(256), got)
+}
diff --git a/encoder_fixed_test.go b/encoder_fixed_test.go
index 28a5194..a405689 100644
--- a/encoder_fixed_test.go
+++ b/encoder_fixed_test.go
@@ -3,6 +3,7 @@ package avro_test
 import (
 	"bytes"
 	"fmt"
+	"math"
 	"math/big"
 	"testing"
 
@@ -121,3 +122,31 @@ func TestEncoder_FixedLogicalDurationSizeNot12(t *testing.T) {
 	assert.Error(t, err)
 	assert.Equal(t, fmt.Errorf("avro: avro.LogicalDuration is unsupported for Avro fixed, size=11"), err)
 }
+
+func TestEncoder_FixedUint64_Full(t *testing.T) {
+	defer ConfigTeardown()
+
+	schema := `{"type":"fixed", "name": "test", "size": 8}`
+	buf := bytes.NewBuffer([]byte{})
+	enc, err := avro.NewEncoder(schema, buf)
+	require.NoError(t, err)
+
+	err = enc.Encode(uint64(math.MaxUint64))
+
+	require.NoError(t, err)
+	assert.Equal(t, []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, buf.Bytes())
+}
+
+func TestEncoder_FixedUint64_Small(t *testing.T) {
+	defer ConfigTeardown()
+
+	schema := `{"type":"fixed", "name": "test", "size": 8}`
+	buf := bytes.NewBuffer([]byte{})
+	enc, err := avro.NewEncoder(schema, buf)
+	require.NoError(t, err)
+
+	err = enc.Encode(uint64(256))
+
+	require.NoError(t, err)
+	assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}, buf.Bytes())
+}