Skip to content

Commit

Permalink
Merge pull request #371 from illia-li/il/fix/marshal/timestamp
Browse files Browse the repository at this point in the history
Fix `timestamp` marshal, unmarshall
  • Loading branch information
dkropachev authored Dec 9, 2024
2 parents 0b40e7a + 37c7e99 commit b578328
Show file tree
Hide file tree
Showing 8 changed files with 414 additions and 224 deletions.
69 changes: 13 additions & 56 deletions marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/gocql/gocql/serialization/inet"
"github.com/gocql/gocql/serialization/smallint"
"github.com/gocql/gocql/serialization/text"
"github.com/gocql/gocql/serialization/timestamp"
"github.com/gocql/gocql/serialization/timeuuid"
"github.com/gocql/gocql/serialization/tinyint"
"github.com/gocql/gocql/serialization/uuid"
Expand Down Expand Up @@ -173,7 +174,7 @@ func Marshal(info TypeInfo, value interface{}) ([]byte, error) {
case TypeTime:
return marshalTime(value)
case TypeTimestamp:
return marshalTimestamp(info, value)
return marshalTimestamp(value)
case TypeList, TypeSet:
return marshalList(info, value)
case TypeMap:
Expand Down Expand Up @@ -287,7 +288,7 @@ func Unmarshal(info TypeInfo, data []byte, value interface{}) error {
case TypeTime:
return unmarshalTime(data, value)
case TypeTimestamp:
return unmarshalTimestamp(info, data, value)
return unmarshalTimestamp(data, value)
case TypeList, TypeSet:
return unmarshalList(info, data, value)
case TypeMap:
Expand Down Expand Up @@ -683,64 +684,20 @@ func unmarshalTime(data []byte, value interface{}) error {
return nil
}

func marshalTimestamp(info TypeInfo, value interface{}) ([]byte, error) {
switch v := value.(type) {
case Marshaler:
return v.MarshalCQL(info)
case unsetColumn:
return nil, nil
case int64:
return encBigInt(v), nil
case time.Time:
if v.IsZero() {
return []byte{}, nil
}
x := int64(v.UTC().Unix()*1e3) + int64(v.UTC().Nanosecond()/1e6)
return encBigInt(x), nil
}

if value == nil {
return nil, nil
}

rv := reflect.ValueOf(value)
switch rv.Type().Kind() {
case reflect.Int64:
return encBigInt(rv.Int()), nil
func marshalTimestamp(value interface{}) ([]byte, error) {
data, err := timestamp.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 unmarshalTimestamp(info TypeInfo, data []byte, value interface{}) error {
switch v := value.(type) {
case Unmarshaler:
return v.UnmarshalCQL(info, data)
case *int64:
*v = decBigInt(data)
return nil
case *time.Time:
if len(data) == 0 {
*v = time.Time{}
return nil
}
x := decBigInt(data)
sec := x / 1000
nsec := (x - sec*1000) * 1000000
*v = time.Unix(sec, nsec).In(time.UTC)
return nil
}

rv := reflect.ValueOf(value)
if rv.Kind() != reflect.Ptr {
return unmarshalErrorf("can not unmarshal into non-pointer %T", value)
}
rv = rv.Elem()
switch rv.Type().Kind() {
case reflect.Int64:
rv.SetInt(decBigInt(data))
return nil
func unmarshalTimestamp(data []byte, value interface{}) error {
err := timestamp.Unmarshal(data, value)
if err != nil {
return wrapUnmarshalError(err, "unmarshal error")
}
return unmarshalErrorf("can not unmarshal %s into %T", info, value)
return nil
}

const millisecondsInADay int64 = 24 * 60 * 60 * 1000
Expand Down
97 changes: 0 additions & 97 deletions marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,20 +122,6 @@ var marshalTests = []struct {
nil,
nil,
},
{
NativeType{proto: 2, typ: TypeTimestamp},
[]byte("\x00\x00\x01\x40\x77\x16\xe1\xb8"),
time.Date(2013, time.August, 13, 9, 52, 3, 0, time.UTC),
nil,
nil,
},
{
NativeType{proto: 2, typ: TypeTimestamp},
[]byte("\x00\x00\x01\x40\x77\x16\xe1\xb8"),
int64(1376387523000),
nil,
nil,
},
{
NativeType{proto: 5, typ: TypeDuration},
[]byte("\x89\xa2\xc3\xc2\x9a\xe0F\x91\x06"),
Expand Down Expand Up @@ -317,23 +303,6 @@ var marshalTests = []struct {
nil,
nil,
},
{
NativeType{proto: 2, typ: TypeTimestamp},
[]byte("\x00\x00\x01\x40\x77\x16\xe1\xb8"),
func() *time.Time {
t := time.Date(2013, time.August, 13, 9, 52, 3, 0, time.UTC)
return &t
}(),
nil,
nil,
},
{
NativeType{proto: 2, typ: TypeTimestamp},
[]byte(nil),
(*time.Time)(nil),
nil,
nil,
},
{
NativeType{proto: 2, typ: TypeBoolean},
[]byte("\x00"),
Expand Down Expand Up @@ -863,72 +832,6 @@ func TestMarshalPointer(t *testing.T) {
}
}

func TestMarshalTimestamp(t *testing.T) {
var marshalTimestampTests = []struct {
Info TypeInfo
Data []byte
Value interface{}
}{
{
NativeType{proto: 2, typ: TypeTimestamp},
[]byte("\x00\x00\x01\x40\x77\x16\xe1\xb8"),
time.Date(2013, time.August, 13, 9, 52, 3, 0, time.UTC),
},
{
NativeType{proto: 2, typ: TypeTimestamp},
[]byte("\x00\x00\x01\x40\x77\x16\xe1\xb8"),
int64(1376387523000),
},
{
// 9223372036854 is the maximum time representable in ms since the epoch
// with int64 if using UnixNano to convert
NativeType{proto: 2, typ: TypeTimestamp},
[]byte("\x00\x00\x08\x63\x7b\xd0\x5a\xf6"),
time.Date(2262, time.April, 11, 23, 47, 16, 854775807, time.UTC),
},
{
// One nanosecond after causes overflow when using UnixNano
// Instead it should resolve to the same time in ms
NativeType{proto: 2, typ: TypeTimestamp},
[]byte("\x00\x00\x08\x63\x7b\xd0\x5a\xf6"),
time.Date(2262, time.April, 11, 23, 47, 16, 854775808, time.UTC),
},
{
// -9223372036855 is the minimum time representable in ms since the epoch
// with int64 if using UnixNano to convert
NativeType{proto: 2, typ: TypeTimestamp},
[]byte("\xff\xff\xf7\x9c\x84\x2f\xa5\x09"),
time.Date(1677, time.September, 21, 00, 12, 43, 145224192, time.UTC),
},
{
// One nanosecond earlier causes overflow when using UnixNano
// it should resolve to the same time in ms
NativeType{proto: 2, typ: TypeTimestamp},
[]byte("\xff\xff\xf7\x9c\x84\x2f\xa5\x09"),
time.Date(1677, time.September, 21, 00, 12, 43, 145224191, time.UTC),
},
{
// Store the zero time as a blank slice
NativeType{proto: 2, typ: TypeTimestamp},
[]byte{},
time.Time{},
},
}

for i, test := range marshalTimestampTests {
t.Log(i, test)
data, err := Marshal(test.Info, test.Value)
if err != nil {
t.Errorf("marshalTest[%d]: %v", i, err)
continue
}
if !bytes.Equal(data, test.Data) {
t.Errorf("marshalTest[%d]: expected %x (%v), got %x (%v) for time %s", i,
test.Data, decBigInt(test.Data), data, decBigInt(data), test.Value)
}
}
}

func TestMarshalTuple(t *testing.T) {
info := TupleTypeInfo{
NativeType: NativeType{proto: 3, typ: TypeTuple},
Expand Down
30 changes: 30 additions & 0 deletions serialization/timestamp/marshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package timestamp

import (
"reflect"
"time"
)

func Marshal(value interface{}) ([]byte, error) {
switch v := value.(type) {
case nil:
return nil, nil
case int64:
return EncInt64(v)
case *int64:
return EncInt64R(v)
case time.Time:
return EncTime(v)
case *time.Time:
return EncTimeR(v)

default:
// Custom types (type MyTime int64) can be serialized only via `reflect` package.
// Later, when generic-based serialization is introduced we can do that via generics.
rv := reflect.TypeOf(value)
if rv.Kind() != reflect.Ptr {
return EncReflect(reflect.ValueOf(v))
}
return EncReflectR(reflect.ValueOf(v))
}
}
65 changes: 65 additions & 0 deletions serialization/timestamp/marshal_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package timestamp

import (
"fmt"
"reflect"
"time"
)

const (
maxValInt64 int64 = 86399999999999
minValInt64 int64 = 0
maxValDur time.Duration = 86399999999999
minValDur time.Duration = 0
)

func EncInt64(v int64) ([]byte, error) {
return encInt64(v), nil
}

func EncInt64R(v *int64) ([]byte, error) {
if v == nil {
return nil, nil
}
return EncInt64(*v)
}

func EncTime(v time.Time) ([]byte, error) {
if v.IsZero() {
return make([]byte, 0), nil
}
ms := v.Unix()*1e3 + int64(v.Nanosecond())/1e6
return []byte{byte(ms >> 56), byte(ms >> 48), byte(ms >> 40), byte(ms >> 32), byte(ms >> 24), byte(ms >> 16), byte(ms >> 8), byte(ms)}, nil
}

func EncTimeR(v *time.Time) ([]byte, error) {
if v == nil {
return nil, nil
}
return EncTime(*v)
}

func EncReflect(v reflect.Value) ([]byte, error) {
switch v.Kind() {
case reflect.Int64:
return encInt64(v.Int()), nil
case reflect.Struct:
if v.Type().String() == "gocql.unsetColumn" {
return nil, nil
}
return nil, fmt.Errorf("failed to marshal timestamp: unsupported value type (%T)(%[1]v)", v.Interface())
default:
return nil, fmt.Errorf("failed to marshal timestamp: unsupported value type (%T)(%[1]v)", v.Interface())
}
}

func EncReflectR(v reflect.Value) ([]byte, error) {
if v.IsNil() {
return nil, nil
}
return EncReflect(v.Elem())
}

func encInt64(v int64) []byte {
return []byte{byte(v >> 56), byte(v >> 48), byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
36 changes: 36 additions & 0 deletions serialization/timestamp/unmarshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package timestamp

import (
"fmt"
"reflect"
"time"
)

func Unmarshal(data []byte, value interface{}) error {
switch v := value.(type) {
case nil:
return nil

case *int64:
return DecInt64(data, v)
case **int64:
return DecInt64R(data, v)
case *time.Time:
return DecTime(data, v)
case **time.Time:
return DecTimeR(data, v)
default:

// Custom types (type MyTime int64) can be deserialized only via `reflect` package.
// Later, when generic-based serialization is introduced we can do that via generics.
rv := reflect.ValueOf(value)
rt := rv.Type()
if rt.Kind() != reflect.Ptr {
return fmt.Errorf("failed to unmarshal timestamp: unsupported value type (%T)(%[1]v)", value)
}
if rt.Elem().Kind() != reflect.Ptr {
return DecReflect(data, rv)
}
return DecReflectR(data, rv)
}
}
Loading

0 comments on commit b578328

Please sign in to comment.