Skip to content

Commit

Permalink
Merge pull request #374 from illia-li/il/fix/marshal/date
Browse files Browse the repository at this point in the history
Fix `date` marshal, unmarshall functions
  • Loading branch information
dkropachev authored Dec 13, 2024
2 parents 466d662 + 8a691c0 commit 7f20235
Show file tree
Hide file tree
Showing 8 changed files with 881 additions and 327 deletions.
84 changes: 13 additions & 71 deletions marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package gocql

import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
Expand All @@ -23,6 +22,7 @@ import (
"github.com/gocql/gocql/serialization/counter"
"github.com/gocql/gocql/serialization/cqlint"
"github.com/gocql/gocql/serialization/cqltime"
"github.com/gocql/gocql/serialization/date"
"github.com/gocql/gocql/serialization/decimal"
"github.com/gocql/gocql/serialization/double"
"github.com/gocql/gocql/serialization/float"
Expand Down Expand Up @@ -192,7 +192,7 @@ func Marshal(info TypeInfo, value interface{}) ([]byte, error) {
case TypeUDT:
return marshalUDT(info, value)
case TypeDate:
return marshalDate(info, value)
return marshalDate(value)
case TypeDuration:
return marshalDuration(info, value)
}
Expand Down Expand Up @@ -304,7 +304,7 @@ func Unmarshal(info TypeInfo, data []byte, value interface{}) error {
case TypeUDT:
return unmarshalUDT(info, data, value)
case TypeDate:
return unmarshalDate(info, data, value)
return unmarshalDate(data, value)
case TypeDuration:
return unmarshalDuration(info, data, value)
}
Expand Down Expand Up @@ -700,78 +700,20 @@ func unmarshalTimestamp(data []byte, value interface{}) error {
return nil
}

const millisecondsInADay int64 = 24 * 60 * 60 * 1000

func marshalDate(info TypeInfo, value interface{}) ([]byte, error) {
var timestamp int64
switch v := value.(type) {
case Marshaler:
return v.MarshalCQL(info)
case unsetColumn:
return nil, nil
case int64:
timestamp = v
x := timestamp/millisecondsInADay + int64(1<<31)
return encInt(int32(x)), nil
case time.Time:
if v.IsZero() {
return []byte{}, nil
}
timestamp = int64(v.UTC().Unix()*1e3) + int64(v.UTC().Nanosecond()/1e6)
x := timestamp/millisecondsInADay + int64(1<<31)
return encInt(int32(x)), nil
case *time.Time:
if v.IsZero() {
return []byte{}, nil
}
timestamp = int64(v.UTC().Unix()*1e3) + int64(v.UTC().Nanosecond()/1e6)
x := timestamp/millisecondsInADay + int64(1<<31)
return encInt(int32(x)), nil
case string:
if v == "" {
return []byte{}, nil
}
t, err := time.Parse("2006-01-02", v)
if err != nil {
return nil, marshalErrorf("can not marshal %T into %s, date layout must be '2006-01-02'", value, info)
}
timestamp = int64(t.UTC().Unix()*1e3) + int64(t.UTC().Nanosecond()/1e6)
x := timestamp/millisecondsInADay + int64(1<<31)
return encInt(int32(x)), nil
}

if value == nil {
return nil, nil
func marshalDate(value interface{}) ([]byte, error) {
data, err := date.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 unmarshalDate(info TypeInfo, data []byte, value interface{}) error {
switch v := value.(type) {
case Unmarshaler:
return v.UnmarshalCQL(info, data)
case *time.Time:
if len(data) == 0 {
*v = time.Time{}
return nil
}
var origin uint32 = 1 << 31
var current uint32 = binary.BigEndian.Uint32(data)
timestamp := (int64(current) - int64(origin)) * millisecondsInADay
*v = time.UnixMilli(timestamp).In(time.UTC)
return nil
case *string:
if len(data) == 0 {
*v = ""
return nil
}
var origin uint32 = 1 << 31
var current uint32 = binary.BigEndian.Uint32(data)
timestamp := (int64(current) - int64(origin)) * millisecondsInADay
*v = time.UnixMilli(timestamp).In(time.UTC).Format("2006-01-02")
return nil
func unmarshalDate(data []byte, value interface{}) error {
err := date.Unmarshal(data, value)
if err != nil {
return wrapUnmarshalError(err, "unmarshal error")
}
return unmarshalErrorf("can not unmarshal %s into %T", info, value)
return nil
}

func marshalDuration(info TypeInfo, value interface{}) ([]byte, error) {
Expand Down
120 changes: 0 additions & 120 deletions marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1221,126 +1221,6 @@ func TestUnmarshalInetCopyBytes(t *testing.T) {
}
}

func TestUnmarshalDate(t *testing.T) {
data := []uint8{0x80, 0x0, 0x43, 0x31}
var date time.Time
if err := unmarshalDate(NativeType{proto: 2, typ: TypeDate}, data, &date); err != nil {
t.Fatal(err)
}

expectedDate := "2017-02-04"
formattedDate := date.Format("2006-01-02")
if expectedDate != formattedDate {
t.Errorf("marshalTest: expected %v, got %v", expectedDate, formattedDate)
return
}
var stringDate string
if err2 := unmarshalDate(NativeType{proto: 2, typ: TypeDate}, data, &stringDate); err2 != nil {
t.Fatal(err2)
}
if expectedDate != stringDate {
t.Errorf("marshalTest: expected %v, got %v", expectedDate, formattedDate)
return
}
}

func TestMarshalDate(t *testing.T) {
now := time.Now().UTC()
timestamp := now.UnixNano() / int64(time.Millisecond)
expectedData := encInt(int32(timestamp/86400000 + int64(1<<31)))

var marshalDateTests = []struct {
Info TypeInfo
Data []byte
Value interface{}
}{
{
NativeType{proto: 4, typ: TypeDate},
expectedData,
timestamp,
},
{
NativeType{proto: 4, typ: TypeDate},
expectedData,
now,
},
{
NativeType{proto: 4, typ: TypeDate},
expectedData,
&now,
},
{
NativeType{proto: 4, typ: TypeDate},
expectedData,
now.Format("2006-01-02"),
},
}

for i, test := range marshalDateTests {
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, decInt(test.Data), data, decInt(data), test.Value)
}
}
}

func TestLargeDate(t *testing.T) {
farFuture := time.Date(999999, time.December, 31, 0, 0, 0, 0, time.UTC)
expectedFutureData := encInt(int32(farFuture.UnixMilli()/86400000 + int64(1<<31)))

farPast := time.Date(-999999, time.January, 1, 0, 0, 0, 0, time.UTC)
expectedPastData := encInt(int32(farPast.UnixMilli()/86400000 + int64(1<<31)))

var marshalDateTests = []struct {
Data []byte
Value interface{}
ExpectedDate string
}{
{
expectedFutureData,
farFuture,
"999999-12-31",
},
{
expectedPastData,
farPast,
"-999999-01-01",
},
}

nativeType := NativeType{proto: 4, typ: TypeDate}

for i, test := range marshalDateTests {
t.Log(i, test)

data, err := Marshal(nativeType, test.Value)
if err != nil {
t.Errorf("largeDateTest[%d]: %v", i, err)
continue
}
if !bytes.Equal(data, test.Data) {
t.Errorf("largeDateTest[%d]: expected %x (%v), got %x (%v) for time %s", i,
test.Data, decInt(test.Data), data, decInt(data), test.Value)
}

var date time.Time
if err := Unmarshal(nativeType, data, &date); err != nil {
t.Fatal(err)
}

formattedDate := date.Format("2006-01-02")
if test.ExpectedDate != formattedDate {
t.Fatalf("largeDateTest: expected %v, got %v", test.ExpectedDate, formattedDate)
}
}
}

func BenchmarkUnmarshalVarchar(b *testing.B) {
b.ReportAllocs()
src := make([]byte, 1024)
Expand Down
42 changes: 42 additions & 0 deletions serialization/date/marshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package date

import (
"reflect"
"time"
)

func Marshal(value interface{}) ([]byte, error) {
switch v := value.(type) {
case nil:
return nil, nil
case int32:
return EncInt32(v)
case int64:
return EncInt64(v)
case uint32:
return EncUint32(v)
case string:
return EncString(v)
case time.Time:
return EncTime(v)

case *int32:
return EncInt32R(v)
case *int64:
return EncInt64R(v)
case *uint32:
return EncUint32R(v)
case *string:
return EncStringR(v)
case *time.Time:
return EncTimeR(v)
default:
// Custom types (type MyDate uint32) 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))
}
}
Loading

0 comments on commit 7f20235

Please sign in to comment.