mirror of
https://github.com/redis/go-redis.git
synced 2026-10-22 19:49:00 +03:00
fix(cmd): keep SetArgs ExpireAt milliseconds (#4053)
SetArgs always sent ExpireAt as EXAT with Unix seconds, so any millisecond part was silently dropped. Send PXAT with Unix milliseconds when ExpireAt has millisecond precision, and keep EXAT otherwise.
This commit is contained in:
@@ -516,7 +516,8 @@ type SetArgs struct {
|
||||
MatchDigest uint64
|
||||
|
||||
// Zero `TTL` or `Expiration` means that the key has no expiration time.
|
||||
TTL time.Duration
|
||||
TTL time.Duration
|
||||
// ExpireAt is sent as EXAT, or as PXAT when it has millisecond precision.
|
||||
ExpireAt time.Time
|
||||
|
||||
// When Get is true, the command returns the old value stored at key, or nil when key did not exist.
|
||||
@@ -538,7 +539,11 @@ func (c cmdable) SetArgs(ctx context.Context, key string, value interface{}, a S
|
||||
}
|
||||
|
||||
if !a.ExpireAt.IsZero() {
|
||||
args = append(args, "exat", a.ExpireAt.Unix())
|
||||
if a.ExpireAt.Nanosecond() >= int(time.Millisecond) {
|
||||
args = append(args, "pxat", a.ExpireAt.UnixMilli())
|
||||
} else {
|
||||
args = append(args, "exat", a.ExpireAt.Unix())
|
||||
}
|
||||
}
|
||||
if a.TTL > 0 {
|
||||
if usePrecise(a.TTL) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// captureCmdable returns a cmdable that records the Cmder passed to it without
|
||||
@@ -176,3 +177,43 @@ func TestIncrEXFloat_Args(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetArgs_ExpireAt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expireAt time.Time
|
||||
want []interface{}
|
||||
}{
|
||||
{
|
||||
name: "whole_seconds",
|
||||
expireAt: time.Unix(1700000000, 0),
|
||||
want: []interface{}{"set", "key", "value", "exat", int64(1700000000)},
|
||||
},
|
||||
{
|
||||
name: "sub_millisecond_is_truncated",
|
||||
expireAt: time.Unix(1700000000, 500_000),
|
||||
want: []interface{}{"set", "key", "value", "exat", int64(1700000000)},
|
||||
},
|
||||
{
|
||||
name: "milliseconds",
|
||||
expireAt: time.UnixMilli(1700000000123),
|
||||
want: []interface{}{"set", "key", "value", "pxat", int64(1700000000123)},
|
||||
},
|
||||
{
|
||||
name: "beyond_nanosecond_range",
|
||||
expireAt: time.Date(2270, time.January, 2, 3, 4, 5, 123000000, time.UTC),
|
||||
want: []interface{}{"set", "key", "value", "pxat", int64(9467204645123)},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var cmd Cmder
|
||||
c := captureCmdable(&cmd)
|
||||
c.SetArgs(context.Background(), "key", "value", SetArgs{ExpireAt: tt.expireAt})
|
||||
if got := cmd.Args(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("args = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user