Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix date marshal, unmarshall functions #374

Merged
merged 2 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading