Skip to content

Commit

Permalink
Merge pull request #334 from illia-li/il/fix/marshal/uuids
Browse files Browse the repository at this point in the history
Fix `uuid`, `timeuuid` marshal, unmarshall
  • Loading branch information
dkropachev authored Nov 21, 2024
2 parents 5e5808f + 3846c7d commit 77bae76
Show file tree
Hide file tree
Showing 12 changed files with 1,410 additions and 151 deletions.
135 changes: 55 additions & 80 deletions marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"gopkg.in/inf.v0"
"math"
"math/big"
"math/bits"
Expand All @@ -17,8 +18,7 @@ import (
"strconv"
"strings"
"time"

"gopkg.in/inf.v0"
"unsafe"

"github.com/gocql/gocql/serialization/bigint"
"github.com/gocql/gocql/serialization/blob"
Expand All @@ -28,7 +28,9 @@ import (
"github.com/gocql/gocql/serialization/float"
"github.com/gocql/gocql/serialization/smallint"
"github.com/gocql/gocql/serialization/text"
"github.com/gocql/gocql/serialization/timeuuid"
"github.com/gocql/gocql/serialization/tinyint"
"github.com/gocql/gocql/serialization/uuid"
"github.com/gocql/gocql/serialization/varchar"
)

Expand Down Expand Up @@ -174,8 +176,10 @@ func Marshal(info TypeInfo, value interface{}) ([]byte, error) {
return marshalList(info, value)
case TypeMap:
return marshalMap(info, value)
case TypeUUID, TypeTimeUUID:
return marshalUUID(info, value)
case TypeUUID:
return marshalUUID(value)
case TypeTimeUUID:
return marshalTimeUUID(value)
case TypeVarint:
return marshalVarint(info, value)
case TypeInet:
Expand Down Expand Up @@ -287,9 +291,9 @@ func Unmarshal(info TypeInfo, data []byte, value interface{}) error {
case TypeMap:
return unmarshalMap(info, data, value)
case TypeTimeUUID:
return unmarshalTimeUUID(info, data, value)
return unmarshalTimeUUID(data, value)
case TypeUUID:
return unmarshalUUID(info, data, value)
return unmarshalUUID(data, value)
case TypeInet:
return unmarshalInet(info, data, value)
case TypeTuple:
Expand Down Expand Up @@ -1679,97 +1683,68 @@ func unmarshalMap(info TypeInfo, data []byte, value interface{}) error {
return nil
}

func marshalUUID(info TypeInfo, value interface{}) ([]byte, error) {
switch val := value.(type) {
case unsetColumn:
return nil, nil
func marshalUUID(value interface{}) ([]byte, error) {
switch uv := value.(type) {
case UUID:
return val.Bytes(), nil
case [16]byte:
return val[:], nil
case []byte:
if len(val) != 16 {
return nil, marshalErrorf("can not marshal []byte %d bytes long into %s, must be exactly 16 bytes long", len(val), info)
}
return val, nil
case string:
b, err := ParseUUID(val)
if err != nil {
return nil, err
}
return b[:], nil
value = [16]byte(uv)
case *UUID:
value = (*[16]byte)(uv)
}

if value == nil {
return nil, nil
data, err := uuid.Marshal(value)
if err != nil {
return nil, wrapMarshalError(err, "marshal error")
}

return nil, marshalErrorf("can not marshal %T into %s", value, info)
return data, nil
}

func unmarshalUUID(info TypeInfo, data []byte, value interface{}) error {
if len(data) == 0 {
switch v := value.(type) {
case *string:
*v = ""
case *[]byte:
*v = nil
case *[16]byte:
*v = [16]byte{}
case *UUID:
*v = UUID{}
default:
return unmarshalErrorf("can not unmarshal X %s into %T", info, value)
func unmarshalUUID(data []byte, value interface{}) error {
switch uv := value.(type) {
case *UUID:
value = (*[16]byte)(uv)
case **UUID:
if uv == nil {
value = (**[16]byte)(nil)
} else {
value = (**[16]byte)(unsafe.Pointer(uv))
}

return nil
}

if len(data) != 16 {
return unmarshalErrorf("unable to parse UUID: UUIDs must be exactly 16 bytes long")
err := uuid.Unmarshal(data, value)
if err != nil {
return wrapUnmarshalError(err, "unmarshal error")
}
return nil
}

switch v := value.(type) {
case *[16]byte:
copy((*v)[:], data)
return nil
func marshalTimeUUID(value interface{}) ([]byte, error) {
switch uv := value.(type) {
case UUID:
value = [16]byte(uv)
case *UUID:
copy((*v)[:], data)
return nil
value = (*[16]byte)(uv)
}

u, err := UUIDFromBytes(data)
data, err := timeuuid.Marshal(value)
if err != nil {
return unmarshalErrorf("unable to parse UUID: %s", err)
}

switch v := value.(type) {
case *string:
*v = u.String()
return nil
case *[]byte:
*v = u[:]
return nil
return nil, wrapMarshalError(err, "marshal error")
}
return unmarshalErrorf("can not unmarshal X %s into %T", info, value)
return data, nil
}

func unmarshalTimeUUID(info TypeInfo, data []byte, value interface{}) error {
switch v := value.(type) {
case Unmarshaler:
return v.UnmarshalCQL(info, data)
case *time.Time:
id, err := UUIDFromBytes(data)
if err != nil {
return err
} else if id.Version() != 1 {
return unmarshalErrorf("invalid timeuuid")
func unmarshalTimeUUID(data []byte, value interface{}) error {
switch uv := value.(type) {
case *UUID:
value = (*[16]byte)(uv)
case **UUID:
if uv == nil {
value = (**[16]byte)(nil)
} else {
value = (**[16]byte)(unsafe.Pointer(uv))
}
*v = id.Time()
return nil
default:
return unmarshalUUID(info, data, value)
}
err := timeuuid.Unmarshal(data, value)
if err != nil {
return wrapUnmarshalError(err, "unmarshal error")
}
return nil
}

func marshalInet(info TypeInfo, value interface{}) ([]byte, error) {
Expand Down
41 changes: 1 addition & 40 deletions marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,6 @@ var marshalTests = []struct {
MarshalError error
UnmarshalError error
}{
{
NativeType{proto: 2, typ: TypeTimeUUID},
[]byte{0x3d, 0xcd, 0x98, 0x0, 0xf3, 0xd9, 0x11, 0xbf, 0x86, 0xd4, 0xb8, 0xe8, 0x56, 0x2c, 0xc, 0xd0},
func() UUID {
x, _ := UUIDFromBytes([]byte{0x3d, 0xcd, 0x98, 0x0, 0xf3, 0xd9, 0x11, 0xbf, 0x86, 0xd4, 0xb8, 0xe8, 0x56, 0x2c, 0xc, 0xd0})
return x
}(),
nil,
nil,
},
{
NativeType{proto: 2, typ: TypeTimeUUID},
[]byte{0xb8, 0xe8, 0x56, 0x2c, 0xc, 0xd0},
[]byte{0xb8, 0xe8, 0x56, 0x2c, 0xc, 0xd0},
marshalErrorf("can not marshal []byte 6 bytes long into timeuuid, must be exactly 16 bytes long"),
unmarshalErrorf("unable to parse UUID: UUIDs must be exactly 16 bytes long"),
},
{
NativeType{proto: 2, typ: TypeTimeUUID},
[]byte{0x3d, 0xcd, 0x98, 0x0, 0xf3, 0xd9, 0x11, 0xbf, 0x86, 0xd4, 0xb8, 0xe8, 0x56, 0x2c, 0xc, 0xd0},
[16]byte{0x3d, 0xcd, 0x98, 0x0, 0xf3, 0xd9, 0x11, 0xbf, 0x86, 0xd4, 0xb8, 0xe8, 0x56, 0x2c, 0xc, 0xd0},
nil,
nil,
},
{
NativeType{proto: 2, typ: TypeBoolean},
[]byte("\x00"),
Expand Down Expand Up @@ -404,20 +380,6 @@ var marshalTests = []struct {
nil,
nil,
},
{
NativeType{proto: 2, typ: TypeTimeUUID},
[]byte{0x3d, 0xcd, 0x98, 0x0, 0xf3, 0xd9, 0x11, 0xbf, 0x86, 0xd4, 0xb8, 0xe8, 0x56, 0x2c, 0xc, 0xd0},
&UUID{0x3d, 0xcd, 0x98, 0x0, 0xf3, 0xd9, 0x11, 0xbf, 0x86, 0xd4, 0xb8, 0xe8, 0x56, 0x2c, 0xc, 0xd0},
nil,
nil,
},
{
NativeType{proto: 2, typ: TypeTimeUUID},
[]byte(nil),
(*UUID)(nil),
nil,
nil,
},
{
NativeType{proto: 2, typ: TypeTimestamp},
[]byte("\x00\x00\x01\x40\x77\x16\xe1\xb8"),
Expand Down Expand Up @@ -1882,11 +1844,10 @@ func BenchmarkUnmarshalUUID(b *testing.B) {
b.ReportAllocs()
src := make([]byte, 16)
dst := UUID{}
var ti TypeInfo = NativeType{}

b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := unmarshalUUID(ti, src, &dst); err != nil {
if err := unmarshalUUID(src, &dst); err != nil {
b.Fatal(err)
}
}
Expand Down
32 changes: 32 additions & 0 deletions serialization/timeuuid/marshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package timeuuid

import (
"reflect"
)

func Marshal(value interface{}) ([]byte, error) {
switch v := value.(type) {
case nil:
return nil, nil
case [16]byte:
return EncArray(v)
case *[16]byte:
return EncArrayR(v)
case []byte:
return EncSlice(v)
case *[]byte:
return EncSliceR(v)
case string:
return EncString(v)
case *string:
return EncStringR(v)
default:
// Custom types (type MyUUID [16]byte) can be serialized only via `reflect` package.
// Later, when generic-based serialization is introduced we can do that via generics.
rv := reflect.ValueOf(value)
if rv.Kind() != reflect.Ptr {
return EncReflect(rv)
}
return EncReflectR(rv)
}
}
Loading

0 comments on commit 77bae76

Please sign in to comment.