diff --git a/README.md b/README.md index f25bc94..04bb0ee 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,9 @@ Some corner cases: // SET key value EX 10 NX set, err := rdb.SetNX(ctx, "key", "value", 10*time.Second).Result() +// SET key value keepttl NX +set, err := rdb.SetNX(ctx, "key", "value", redis.KeepTTL).Result() + // SORT list LIMIT 0 2 ASC vals, err := rdb.Sort(ctx, "list", &redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result() diff --git a/commands.go b/commands.go index 5ee2836..c843f51 100644 --- a/commands.go +++ b/commands.go @@ -9,6 +9,10 @@ import ( "github.com/go-redis/redis/v8/internal" ) +// KeepTTL used when set with keepttl option +// Example: Set(ctx, key, value, redis.KeepTTL). +const KeepTTL = -1 + func usePrecise(dur time.Duration) bool { return dur < time.Second || dur%time.Second != 0 } @@ -756,6 +760,7 @@ func (c cmdable) MSetNX(ctx context.Context, values ...interface{}) *BoolCmd { // // Use expiration for `SETEX`-like behavior. // Zero expiration means the key has no expiration time. +// KeepTTL(-1) expiration means command set adds keepttl option. func (c cmdable) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd { args := make([]interface{}, 3, 5) args[0] = "set" @@ -767,7 +772,10 @@ func (c cmdable) Set(ctx context.Context, key string, value interface{}, expirat } else { args = append(args, "ex", formatSec(ctx, expiration)) } + } else if expiration == KeepTTL { + args = append(args, "keepttl") } + cmd := NewStatusCmd(ctx, args...) _ = c(ctx, cmd) return cmd @@ -776,18 +784,23 @@ func (c cmdable) Set(ctx context.Context, key string, value interface{}, expirat // Redis `SET key value [expiration] NX` command. // // Zero expiration means the key has no expiration time. +// KeepTTL(-1) expiration means set command adds keepttl option. func (c cmdable) SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd { var cmd *BoolCmd - if expiration == 0 { + switch { + case expiration == 0: // Use old `SETNX` to support old Redis versions. cmd = NewBoolCmd(ctx, "setnx", key, value) - } else { + case expiration == KeepTTL: + cmd = NewBoolCmd(ctx, "set", key, value, "keepttl", "nx") + default: if usePrecise(expiration) { cmd = NewBoolCmd(ctx, "set", key, value, "px", formatMs(ctx, expiration), "nx") } else { cmd = NewBoolCmd(ctx, "set", key, value, "ex", formatSec(ctx, expiration), "nx") } } + _ = c(ctx, cmd) return cmd } @@ -795,17 +808,22 @@ func (c cmdable) SetNX(ctx context.Context, key string, value interface{}, expir // Redis `SET key value [expiration] XX` command. // // Zero expiration means the key has no expiration time. +// KeepTTL(-1) expiration means set command adds keepttl option. func (c cmdable) SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd { var cmd *BoolCmd - if expiration == 0 { + switch { + case expiration == 0: cmd = NewBoolCmd(ctx, "set", key, value, "xx") - } else { + case expiration == KeepTTL: + cmd = NewBoolCmd(ctx, "set", key, value, "keepttl", "xx") + default: if usePrecise(expiration) { cmd = NewBoolCmd(ctx, "set", key, value, "px", formatMs(ctx, expiration), "xx") } else { cmd = NewBoolCmd(ctx, "set", key, value, "ex", formatSec(ctx, expiration), "xx") } } + _ = c(ctx, cmd) return cmd } diff --git a/commands_test.go b/commands_test.go index ce26cfa..e30174c 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1145,6 +1145,23 @@ var _ = Describe("Commands", func() { }, "1s", "100ms").Should(Equal(redis.Nil)) }) + It("should Set with keepttl", func() { + // set with ttl + set := client.Set(ctx, "key", "hello", 5 * time.Second) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + // set with keepttl + set = client.Set(ctx, "key", "hello1", redis.KeepTTL) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + ttl := client.TTL(ctx, "key") + Expect(ttl.Err()).NotTo(HaveOccurred()) + // set keepttl will Retain the ttl associated with the key + Expect(ttl.Val().Nanoseconds()).NotTo(Equal(-1)) + }) + It("should SetGet", func() { set := client.Set(ctx, "key", "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) @@ -1183,6 +1200,16 @@ var _ = Describe("Commands", func() { Expect(val).To(Equal("hello")) }) + It("should SetNX with keepttl", func() { + isSet, err := client.SetNX(ctx, "key", "hello1", redis.KeepTTL).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(isSet).To(Equal(true)) + + ttl := client.TTL(ctx, "key") + Expect(ttl.Err()).NotTo(HaveOccurred()) + Expect(ttl.Val().Nanoseconds()).To(Equal(int64(-1))) + }) + It("should SetXX", func() { isSet, err := client.SetXX(ctx, "key", "hello2", 0).Result() Expect(err).NotTo(HaveOccurred()) @@ -1217,6 +1244,32 @@ var _ = Describe("Commands", func() { Expect(val).To(Equal("hello2")) }) + It("should SetXX with keepttl", func() { + isSet, err := client.SetXX(ctx, "key", "hello2", time.Second).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(isSet).To(Equal(false)) + + err = client.Set(ctx, "key", "hello", time.Second).Err() + Expect(err).NotTo(HaveOccurred()) + + isSet, err = client.SetXX(ctx, "key", "hello2", 5*time.Second).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(isSet).To(Equal(true)) + + isSet, err = client.SetXX(ctx, "key", "hello3", redis.KeepTTL).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(isSet).To(Equal(true)) + + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("hello3")) + + // set keepttl will Retain the ttl associated with the key + ttl, err := client.TTL(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(ttl).NotTo(Equal(-1)) + }) + It("should SetRange", func() { set := client.Set(ctx, "key", "Hello World", 0) Expect(set.Err()).NotTo(HaveOccurred())