diff --git a/commands.go b/commands.go index b0ed4552..a0e1edb0 100644 --- a/commands.go +++ b/commands.go @@ -117,6 +117,8 @@ type Cmdable interface { Get(ctx context.Context, key string) *StringCmd GetRange(ctx context.Context, key string, start, end int64) *StringCmd GetSet(ctx context.Context, key string, value interface{}) *StringCmd + GetEX(ctx context.Context, key string, ttl *SetTTL) *StringCmd + GetDel(ctx context.Context, key string) *StringCmd Incr(ctx context.Context, key string) *IntCmd IncrBy(ctx context.Context, key string, value int64) *IntCmd IncrByFloat(ctx context.Context, key string, value float64) *FloatCmd @@ -361,6 +363,58 @@ type statefulCmdable func(ctx context.Context, cmd Cmder) error //------------------------------------------------------------------------------ +type ttlAttr int + +const ( + TExpire ttlAttr = 1 << iota + TExpireAT + TKeepTTL + TPersist +) + +// TTL related parameters, not all commands support all ttl attributes. +// priority: Expire > ExpireAt > KeepTTL > Persist +type SetTTL struct { + // set the specified expire time. + // Expire > time.Second AND Expire % time.Second == 0: set key EX Expire/time.Second + // Expire < time.Second OR Expire % time.Second != 0: set key PX Expire/time.Millisecond + Expire time.Duration + + // set the specified Unix time at which the key will expire. + // Example: set key EXAT ExpireAt.Unix() + // Don't consider milliseconds for now(PXAT) + ExpireAt time.Time + + // Retain the time to live associated with the key. + KeepTTL bool + + // Remove the time to live associated with the key, Change to never expire + Persist bool +} + +func appendTTL(ctx context.Context, args []interface{}, t *SetTTL, attr ttlAttr) []interface{} { + if t == nil { + return args + } + + switch { + case attr&TExpire == 1 && t.Expire > 0: + if usePrecise(t.Expire) { + args = append(args, "px", formatMs(ctx, t.Expire)) + } else { + args = append(args, "ex", formatSec(ctx, t.Expire)) + } + case attr&TExpireAT == 1 && !t.ExpireAt.IsZero(): + args = append(args, "exat", t.ExpireAt.Unix()) + case attr&TKeepTTL == 1 && t.KeepTTL: + args = append(args, "keepttl") + case attr&TPersist == 1 && t.Persist: + args = append(args, "persist") + } + + return args +} + func (c statefulCmdable) Auth(ctx context.Context, password string) *StatusCmd { cmd := NewStatusCmd(ctx, "auth", password) _ = c(ctx, cmd) @@ -710,6 +764,23 @@ func (c cmdable) GetSet(ctx context.Context, key string, value interface{}) *Str return cmd } +// redis-server version >= 6.2.0 +func (c cmdable) GetEX(ctx context.Context, key string, ttl *SetTTL) *StringCmd { + args := make([]interface{}, 2, 4) + args = append(args, "getex", key) + args = appendTTL(ctx, args, ttl, TExpire|TExpireAT|TPersist) + cmd := NewStringCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// redis-server version >= 6.2.0 +func (c cmdable) GetDel(ctx context.Context, key string) *StringCmd { + cmd := NewStringCmd(ctx, "getdel", key) + _ = c(ctx, cmd) + return cmd +} + func (c cmdable) Incr(ctx context.Context, key string) *IntCmd { cmd := NewIntCmd(ctx, "incr", key) _ = c(ctx, cmd) diff --git a/commands_test.go b/commands_test.go index d7900fc6..14740127 100644 --- a/commands_test.go +++ b/commands_test.go @@ -477,7 +477,7 @@ var _ = Describe("Commands", func() { //if too much time (>1s) is used during command execution, it may also cause the test to fail. //so the ObjectIdleTime result should be <=now-start+1s //link: https://github.com/redis/redis/blob/5b48d900498c85bbf4772c1d466c214439888115/src/object.c#L1265-L1272 - Expect(idleTime.Val()).To(BeNumerically("<=", time.Now().Sub(start) + time.Second)) + Expect(idleTime.Val()).To(BeNumerically("<=", time.Now().Sub(start)+time.Second)) }) It("should Persist", func() { @@ -1083,6 +1083,37 @@ var _ = Describe("Commands", func() { Expect(get.Val()).To(Equal("0")) }) + It("should GetEX", func() { + set := client.Set(ctx, "key", "value", 100*time.Second) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + ttl := client.TTL(ctx, "key") + Expect(ttl.Err()).NotTo(HaveOccurred()) + Expect(ttl.Val()).To(BeNumerically("~", 100*time.Second, 3*time.Second)) + + getEX := client.GetEX(ctx, "key", &redis.SetTTL{Expire: 200 * time.Second}) + Expect(getEX.Err()).NotTo(HaveOccurred()) + Expect(getEX.Val()).To(Equal("value")) + + ttl = client.TTL(ctx, "key") + Expect(ttl.Err()).NotTo(HaveOccurred()) + Expect(ttl.Val()).To(BeNumerically("~", 200*time.Second, 3*time.Second)) + }) + + It("should GetDel", func() { + set := client.Set(ctx, "key", "value", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + getDel := client.GetDel(ctx, "key") + Expect(getDel.Err()).NotTo(HaveOccurred()) + Expect(getDel.Val()).To(Equal("value")) + + get := client.Get(ctx, "key") + Expect(get.Err()).To(Equal(redis.Nil)) + }) + It("should Incr", func() { set := client.Set(ctx, "key", "10", 0) Expect(set.Err()).NotTo(HaveOccurred())