Skip to content

Commit

Permalink
Merge pull request #375 from illia-li/il/fix/marshal/timestamp_zero_n…
Browse files Browse the repository at this point in the history
…il_time

Fix `timestamp` serialization into `time.Time`,  makes `zero data` <-> `zeroTimestamp`
  • Loading branch information
dkropachev authored Dec 13, 2024
2 parents 7f20235 + 5f6e337 commit 0ca791b
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 22 deletions.
6 changes: 4 additions & 2 deletions cassandra_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2075,7 +2075,9 @@ func TestNilInQuery(t *testing.T) {

// Don't initialize time.Time bind variable if cassandra timestamp column is empty
func TestEmptyTimestamp(t *testing.T) {
zeroTimestamp := time.UnixMilli(0).UTC()
session := createSession(t)

defer session.Close()

if err := createTable(session, "CREATE TABLE gocql_test.test_empty_timestamp (id int, time timestamp, num int, PRIMARY KEY (id))"); err != nil {
Expand All @@ -2092,8 +2094,8 @@ func TestEmptyTimestamp(t *testing.T) {
t.Fatalf("failed to select with err: %v", err)
}

if !timeVal.IsZero() {
t.Errorf("time.Time bind variable should still be empty (was %s)", timeVal)
if !timeVal.Equal(zeroTimestamp) {
t.Errorf("time.Time bind variable should be zero (was %s)", timeVal)
}
}

Expand Down
13 changes: 6 additions & 7 deletions serialization/timestamp/marshal_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import (
"time"
)

const (
maxValInt64 int64 = 86399999999999
minValInt64 int64 = 0
maxValDur time.Duration = 86399999999999
minValDur time.Duration = 0
var (
maxTimestamp = time.Date(292278994, 8, 17, 7, 12, 55, 807*1000000, time.UTC)
zeroTimestamp = time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
minTimestamp = time.Date(-292275055, 5, 16, 16, 47, 4, 192*1000000, time.UTC)
)

func EncInt64(v int64) ([]byte, error) {
Expand All @@ -25,8 +24,8 @@ func EncInt64R(v *int64) ([]byte, error) {
}

func EncTime(v time.Time) ([]byte, error) {
if v.IsZero() {
return make([]byte, 0), nil
if v.After(maxTimestamp) || v.Before(minTimestamp) {
return nil, fmt.Errorf("failed to marshal timestamp: the (%T)(%s) value should be in the range from -292275055-05-16T16:47:04.192Z to 292278994-08-17T07:12:55.807", v, v.Format(time.RFC3339Nano))
}
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
Expand Down
5 changes: 3 additions & 2 deletions serialization/timestamp/unmarshal_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func DecTime(p []byte, v *time.Time) error {
}
switch len(p) {
case 0:
*v = time.Time{}
*v = zeroTimestamp
case 8:
*v = decTime(p)
default:
Expand All @@ -73,7 +73,8 @@ func DecTimeR(p []byte, v **time.Time) error {
if p == nil {
*v = nil
} else {
*v = new(time.Time)
val := zeroTimestamp
*v = &val
}
case 8:
val := decTime(p)
Expand Down
24 changes: 24 additions & 0 deletions tests/serialization/marshal_16_timestamp_corrupt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,33 @@ func TestMarshalTimestampCorrupt(t *testing.T) {
}

for _, tSuite := range testSuites {
marshal := tSuite.marshal
unmarshal := tSuite.unmarshal

t.Run(tSuite.name, func(t *testing.T) {
serialization.NegativeMarshalSet{
Values: mod.Values{
time.Date(292278994, 8, 17, 7, 12, 55, 808*1000000, time.UTC),
time.Date(292278994, 8, 17, 7, 12, 56, 807*1000000, time.UTC),
time.Date(292278994, 8, 17, 7, 13, 55, 807*1000000, time.UTC),
time.Date(292278994, 8, 17, 8, 12, 55, 807*1000000, time.UTC),
time.Date(292278994, 8, 18, 7, 12, 55, 807*1000000, time.UTC),
time.Date(292278994, 9, 17, 7, 12, 55, 807*1000000, time.UTC),
time.Date(292278995, 8, 17, 7, 12, 55, 807*1000000, time.UTC),
}.AddVariants(mod.All...),
}.Run("big_vals", t, marshal)

serialization.NegativeMarshalSet{
Values: mod.Values{
time.Date(-292275055, 5, 16, 16, 47, 4, 191*1000000, time.UTC),
time.Date(-292275055, 5, 16, 16, 47, 3, 192*1000000, time.UTC),
time.Date(-292275055, 5, 16, 16, 46, 4, 192*1000000, time.UTC),
time.Date(-292275055, 5, 16, 15, 47, 4, 192*1000000, time.UTC),
time.Date(-292275055, 5, 15, 16, 47, 4, 192*1000000, time.UTC),
time.Date(-292275055, 4, 16, 16, 47, 4, 192*1000000, time.UTC),
time.Date(-292275056, 5, 16, 16, 47, 4, 192*1000000, time.UTC),
}.AddVariants(mod.All...),
}.Run("small_vals", t, marshal)

serialization.NegativeUnmarshalSet{
Data: []byte("\x7f\xff\xff\xff\xff\xff\xff\xff\xff"),
Expand Down
15 changes: 4 additions & 11 deletions tests/serialization/marshal_16_timestamp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,7 @@ func TestMarshalsTimestamp(t *testing.T) {
},
}

zeroTime := time.Unix(0, 0).UTC()

// The `time` package have a speciality - values `time.Time{}` and `time.Unix(0,0).UTC()` are different
// The old unmarshal function unmarshalls `nil` and `zero` data into `time.Time{}`, but data with zeros into `time.Unix(0,0).UTC()`
brokenTime := serialization.GetTypes(time.Time{}, &time.Time{})
_ = brokenTime
zeroTimestamp := time.Unix(0, 0).UTC()

for _, tSuite := range testSuites {
marshal := tSuite.marshal
Expand All @@ -62,23 +57,21 @@ func TestMarshalsTimestamp(t *testing.T) {
serialization.PositiveSet{
Data: nil,
Values: mod.Values{
int64(0), zeroTime,
int64(0), zeroTimestamp,
}.AddVariants(mod.CustomType),
BrokenUnmarshalTypes: brokenTime,
}.Run("[nil]unmarshal", t, nil, unmarshal)

serialization.PositiveSet{
Data: make([]byte, 0),
Values: mod.Values{
int64(0), zeroTime,
int64(0), zeroTimestamp,
}.AddVariants(mod.All...),
BrokenUnmarshalTypes: brokenTime,
}.Run("[]unmarshal", t, nil, unmarshal)

serialization.PositiveSet{
Data: []byte("\x00\x00\x00\x00\x00\x00\x00\x00"),
Values: mod.Values{
int64(0), zeroTime,
int64(0), zeroTimestamp,
}.AddVariants(mod.All...),
}.Run("zeros", t, marshal, unmarshal)

Expand Down

0 comments on commit 0ca791b

Please sign in to comment.