diff --git a/commands_test.go b/commands_test.go index 80acf09d1..56b83ebaa 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1796,6 +1796,200 @@ var _ = Describe("Commands", func() { Expect(get.Err()).To(Equal(redis.Nil)) }) + It("should DelExArgs when value matches", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "lock", "token-123", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Delete only if value matches + deleted := client.DelExArgs(ctx, "lock", redis.DelExArgs{ + Mode: "IFEQ", + MatchValue: "token-123", + }) + Expect(deleted.Err()).NotTo(HaveOccurred()) + Expect(deleted.Val()).To(Equal(int64(1))) + + // Verify key was deleted + get := client.Get(ctx, "lock") + Expect(get.Err()).To(Equal(redis.Nil)) + }) + + It("should DelExArgs fail when value does not match", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "lock", "token-123", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Try to delete with wrong value + deleted := client.DelExArgs(ctx, "lock", redis.DelExArgs{ + Mode: "IFEQ", + MatchValue: "wrong-token", + }) + Expect(deleted.Err()).NotTo(HaveOccurred()) + Expect(deleted.Val()).To(Equal(int64(0))) + + // Verify key was NOT deleted + val, err := client.Get(ctx, "lock").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("token-123")) + }) + + It("should DelExArgs on non-existent key", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Try to delete non-existent key + deleted := client.DelExArgs(ctx, "nonexistent", redis.DelExArgs{ + Mode: "IFEQ", + MatchValue: "any-value", + }) + Expect(deleted.Err()).NotTo(HaveOccurred()) + Expect(deleted.Val()).To(Equal(int64(0))) + }) + + It("should DelExArgs with IFEQ", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "temp-key", "temp-value", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Delete with IFEQ + args := redis.DelExArgs{ + Mode: "IFEQ", + MatchValue: "temp-value", + } + deleted := client.DelExArgs(ctx, "temp-key", args) + Expect(deleted.Err()).NotTo(HaveOccurred()) + Expect(deleted.Val()).To(Equal(int64(1))) + + // Verify key was deleted + get := client.Get(ctx, "temp-key") + Expect(get.Err()).To(Equal(redis.Nil)) + }) + + It("should DelExArgs with IFNE", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "temporary", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Delete only if value is NOT "permanent" + args := redis.DelExArgs{ + Mode: "IFNE", + MatchValue: "permanent", + } + deleted := client.DelExArgs(ctx, "key", args) + Expect(deleted.Err()).NotTo(HaveOccurred()) + Expect(deleted.Val()).To(Equal(int64(1))) + + // Verify key was deleted + get := client.Get(ctx, "key") + Expect(get.Err()).To(Equal(redis.Nil)) + }) + + It("should DelExArgs with IFNE fail when value matches", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "permanent", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Try to delete but value matches (should fail) + args := redis.DelExArgs{ + Mode: "IFNE", + MatchValue: "permanent", + } + deleted := client.DelExArgs(ctx, "key", args) + Expect(deleted.Err()).NotTo(HaveOccurred()) + Expect(deleted.Val()).To(Equal(int64(0))) + + // Verify key was NOT deleted + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("permanent")) + }) + + It("should Digest", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set a value + err := client.Set(ctx, "my-key", "my-value", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Get digest + digest := client.Digest(ctx, "my-key") + Expect(digest.Err()).NotTo(HaveOccurred()) + Expect(digest.Val()).NotTo(BeEmpty()) + + // Digest should be consistent + digest2 := client.Digest(ctx, "my-key") + Expect(digest2.Err()).NotTo(HaveOccurred()) + Expect(digest2.Val()).To(Equal(digest.Val())) + }) + + It("should Digest on non-existent key", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Get digest of non-existent key + digest := client.Digest(ctx, "nonexistent") + Expect(digest.Err()).To(Equal(redis.Nil)) + }) + + It("should use Digest with SetArgs IFDEQ", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "value1", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Get digest + digest := client.Digest(ctx, "key") + Expect(digest.Err()).NotTo(HaveOccurred()) + + // Update using digest + args := redis.SetArgs{ + Mode: "IFDEQ", + MatchDigest: digest.Val(), + } + result := client.SetArgs(ctx, "key", "value2", args) + Expect(result.Err()).NotTo(HaveOccurred()) + Expect(result.Val()).To(Equal("OK")) + + // Verify value was updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("value2")) + }) + + It("should use Digest with DelExArgs IFDEQ", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "value", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Get digest + digest := client.Digest(ctx, "key") + Expect(digest.Err()).NotTo(HaveOccurred()) + + // Delete using digest + args := redis.DelExArgs{ + Mode: "IFDEQ", + MatchDigest: digest.Val(), + } + deleted := client.DelExArgs(ctx, "key", args) + Expect(deleted.Err()).NotTo(HaveOccurred()) + Expect(deleted.Val()).To(Equal(int64(1))) + + // Verify key was deleted + 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()) @@ -2474,6 +2668,320 @@ var _ = Describe("Commands", func() { Expect(ttl).NotTo(Equal(-1)) }) + It("should SetIFEQ when value matches", func() { + if RedisVersion < 8.4 { + Skip("CAS/CAD commands require Redis >= 8.4") + } + + // Set initial value + err := client.Set(ctx, "key", "old-value", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Update only if current value is "old-value" + result := client.SetIFEQ(ctx, "key", "new-value", "old-value", 0) + Expect(result.Err()).NotTo(HaveOccurred()) + Expect(result.Val()).To(Equal("OK")) + + // Verify value was updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("new-value")) + }) + + It("should SetIFEQ fail when value does not match", func() { + if RedisVersion < 8.4 { + Skip("CAS/CAD commands require Redis >= 8.4") + } + + // Set initial value + err := client.Set(ctx, "key", "current-value", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Try to update with wrong match value + result := client.SetIFEQ(ctx, "key", "new-value", "wrong-value", 0) + Expect(result.Err()).To(Equal(redis.Nil)) + + // Verify value was NOT updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("current-value")) + }) + + It("should SetIFEQ with expiration", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "token-123", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Update with expiration + result := client.SetIFEQ(ctx, "key", "token-456", "token-123", 500*time.Millisecond) + Expect(result.Err()).NotTo(HaveOccurred()) + Expect(result.Val()).To(Equal("OK")) + + // Verify value was updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("token-456")) + + // Wait for expiration + Eventually(func() error { + return client.Get(ctx, "key").Err() + }, "1s", "100ms").Should(Equal(redis.Nil)) + }) + + It("should SetIFNE when value does not match", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "pending", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Update only if current value is NOT "completed" + result := client.SetIFNE(ctx, "key", "processing", "completed", 0) + Expect(result.Err()).NotTo(HaveOccurred()) + Expect(result.Val()).To(Equal("OK")) + + // Verify value was updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("processing")) + }) + + It("should SetIFNE fail when value matches", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "completed", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Try to update but value matches (should fail) + result := client.SetIFNE(ctx, "key", "processing", "completed", 0) + Expect(result.Err()).To(Equal(redis.Nil)) + + // Verify value was NOT updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("completed")) + }) + + It("should SetArgs with IFEQ", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "counter", "100", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Update with IFEQ + args := redis.SetArgs{ + Mode: "IFEQ", + MatchValue: "100", + TTL: 1 * time.Hour, + } + result := client.SetArgs(ctx, "counter", "200", args) + Expect(result.Err()).NotTo(HaveOccurred()) + Expect(result.Val()).To(Equal("OK")) + + // Verify value was updated + val, err := client.Get(ctx, "counter").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("200")) + }) + + It("should SetArgs with IFEQ and GET", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "old", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Update with IFEQ and GET old value + args := redis.SetArgs{ + Mode: "IFEQ", + MatchValue: "old", + Get: true, + } + result := client.SetArgs(ctx, "key", "new", args) + Expect(result.Err()).NotTo(HaveOccurred()) + Expect(result.Val()).To(Equal("old")) + + // Verify value was updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("new")) + }) + + It("should SetArgs with IFNE", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "status", "pending", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Update with IFNE + args := redis.SetArgs{ + Mode: "IFNE", + MatchValue: "completed", + TTL: 30 * time.Minute, + } + result := client.SetArgs(ctx, "status", "processing", args) + Expect(result.Err()).NotTo(HaveOccurred()) + Expect(result.Val()).To(Equal("OK")) + + // Verify value was updated + val, err := client.Get(ctx, "status").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("processing")) + }) + + It("should SetIFEQGet return previous value", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "old-value", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Update and get previous value + result := client.SetIFEQGet(ctx, "key", "new-value", "old-value", 0) + Expect(result.Err()).NotTo(HaveOccurred()) + Expect(result.Val()).To(Equal("old-value")) + + // Verify value was updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("new-value")) + }) + + It("should SetIFNEGet return previous value", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "pending", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Update and get previous value + result := client.SetIFNEGet(ctx, "key", "processing", "completed", 0) + Expect(result.Err()).NotTo(HaveOccurred()) + Expect(result.Val()).To(Equal("pending")) + + // Verify value was updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("processing")) + }) + + It("should SetIFDEQ when digest matches", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "value1", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Get digest + digest := client.Digest(ctx, "key") + Expect(digest.Err()).NotTo(HaveOccurred()) + + // Update using digest + result := client.SetIFDEQ(ctx, "key", "value2", digest.Val(), 0) + Expect(result.Err()).NotTo(HaveOccurred()) + Expect(result.Val()).To(Equal("OK")) + + // Verify value was updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("value2")) + }) + + It("should SetIFDEQ fail when digest does not match", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "value1", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Get digest of a different value to use as wrong digest + err = client.Set(ctx, "temp-key", "different-value", 0).Err() + Expect(err).NotTo(HaveOccurred()) + wrongDigest := client.Digest(ctx, "temp-key") + Expect(wrongDigest.Err()).NotTo(HaveOccurred()) + + // Try to update with wrong digest + result := client.SetIFDEQ(ctx, "key", "value2", wrongDigest.Val(), 0) + Expect(result.Err()).To(Equal(redis.Nil)) + + // Verify value was NOT updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("value1")) + }) + + It("should SetIFDEQGet return previous value", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "value1", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Get digest + digest := client.Digest(ctx, "key") + Expect(digest.Err()).NotTo(HaveOccurred()) + + // Update using digest and get previous value + result := client.SetIFDEQGet(ctx, "key", "value2", digest.Val(), 0) + Expect(result.Err()).NotTo(HaveOccurred()) + Expect(result.Val()).To(Equal("value1")) + + // Verify value was updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("value2")) + }) + + It("should SetIFDNE when digest does not match", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "value1", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Get digest of a different value + err = client.Set(ctx, "temp-key", "different-value", 0).Err() + Expect(err).NotTo(HaveOccurred()) + differentDigest := client.Digest(ctx, "temp-key") + Expect(differentDigest.Err()).NotTo(HaveOccurred()) + + // Update with different digest (should succeed because digest doesn't match) + result := client.SetIFDNE(ctx, "key", "value2", differentDigest.Val(), 0) + Expect(result.Err()).NotTo(HaveOccurred()) + Expect(result.Val()).To(Equal("OK")) + + // Verify value was updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("value2")) + }) + + It("should SetIFDNE fail when digest matches", func() { + SkipBeforeRedisVersion(8.4, "CAS/CAD commands require Redis >= 8.4") + + // Set initial value + err := client.Set(ctx, "key", "value1", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Get digest + digest := client.Digest(ctx, "key") + Expect(digest.Err()).NotTo(HaveOccurred()) + + // Try to update but digest matches (should fail) + result := client.SetIFDNE(ctx, "key", "value2", digest.Val(), 0) + Expect(result.Err()).To(Equal(redis.Nil)) + + // Verify value was NOT updated + val, err := client.Get(ctx, "key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("value1")) + }) + It("should SetRange", func() { set := client.Set(ctx, "key", "Hello World", 0) Expect(set.Err()).NotTo(HaveOccurred()) diff --git a/string_commands.go b/string_commands.go index cc49800d5..3a62e6275 100644 --- a/string_commands.go +++ b/string_commands.go @@ -9,6 +9,8 @@ type StringCmdable interface { Append(ctx context.Context, key, value string) *IntCmd Decr(ctx context.Context, key string) *IntCmd DecrBy(ctx context.Context, key string, decrement int64) *IntCmd + DelExArgs(ctx context.Context, key string, a DelExArgs) *IntCmd + Digest(ctx context.Context, key string) *StringCmd 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 @@ -25,6 +27,14 @@ type StringCmdable interface { Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd SetArgs(ctx context.Context, key string, value interface{}, a SetArgs) *StatusCmd SetEx(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd + SetIFEQ(ctx context.Context, key string, value interface{}, matchValue interface{}, expiration time.Duration) *StatusCmd + SetIFEQGet(ctx context.Context, key string, value interface{}, matchValue interface{}, expiration time.Duration) *StringCmd + SetIFNE(ctx context.Context, key string, value interface{}, matchValue interface{}, expiration time.Duration) *StatusCmd + SetIFNEGet(ctx context.Context, key string, value interface{}, matchValue interface{}, expiration time.Duration) *StringCmd + SetIFDEQ(ctx context.Context, key string, value interface{}, matchDigest string, expiration time.Duration) *StatusCmd + SetIFDEQGet(ctx context.Context, key string, value interface{}, matchDigest string, expiration time.Duration) *StringCmd + SetIFDNE(ctx context.Context, key string, value interface{}, matchDigest string, expiration time.Duration) *StatusCmd + SetIFDNEGet(ctx context.Context, key string, value interface{}, matchDigest string, expiration time.Duration) *StringCmd SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd SetRange(ctx context.Context, key string, offset int64, value string) *IntCmd @@ -49,6 +59,58 @@ func (c cmdable) DecrBy(ctx context.Context, key string, decrement int64) *IntCm return cmd } +// DelExArgs provides arguments for the DelExArgs function. +type DelExArgs struct { + // Mode can be `IFEQ`, `IFNE`, `IFDEQ`, or `IFDNE`. + Mode string + + // MatchValue is used with IFEQ/IFNE modes for compare-and-delete operations. + // - IFEQ: only delete if current value equals MatchValue + // - IFNE: only delete if current value does not equal MatchValue + MatchValue interface{} + + // MatchDigest is used with IFDEQ/IFDNE modes for digest-based compare-and-delete. + // - IFDEQ: only delete if current value's digest equals MatchDigest + // - IFDNE: only delete if current value's digest does not equal MatchDigest + MatchDigest string +} + +// DelExArgs Redis `DELEX key [IFEQ|IFNE|IFDEQ|IFDNE] match-value` command. +// Compare-and-delete with flexible conditions. +// +// Returns the number of keys that were removed (0 or 1). +func (c cmdable) DelExArgs(ctx context.Context, key string, a DelExArgs) *IntCmd { + args := []interface{}{"delex", key} + + if a.Mode != "" { + args = append(args, a.Mode) + + // Add match value/digest based on mode + switch a.Mode { + case "ifeq", "IFEQ", "ifne", "IFNE": + if a.MatchValue != nil { + args = append(args, a.MatchValue) + } + case "ifdeq", "IFDEQ", "ifdne", "IFDNE": + if a.MatchDigest != "" { + args = append(args, a.MatchDigest) + } + } + } + + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// Digest Redis `DIGEST key` command. +// Returns the hex digest of the specified key's value. +func (c cmdable) Digest(ctx context.Context, key string) *StringCmd { + cmd := NewStringCmd(ctx, "digest", key) + _ = c(ctx, cmd) + return cmd +} + // Get Redis `GET key` command. It returns redis.Nil error when key does not exist. func (c cmdable) Get(ctx context.Context, key string) *StringCmd { cmd := NewStringCmd(ctx, "get", key) @@ -258,9 +320,19 @@ func (c cmdable) Set(ctx context.Context, key string, value interface{}, expirat // SetArgs provides arguments for the SetArgs function. type SetArgs struct { - // Mode can be `NX` or `XX` or empty. + // Mode can be `NX`, `XX`, `IFEQ`, `IFNE`, `IFDEQ`, `IFDNE` or empty. Mode string + // MatchValue is used with IFEQ/IFNE modes for compare-and-set operations. + // - IFEQ: only set if current value equals MatchValue + // - IFNE: only set if current value does not equal MatchValue + MatchValue interface{} + + // MatchDigest is used with IFDEQ/IFDNE modes for digest-based compare-and-set. + // - IFDEQ: only set if current value's digest equals MatchDigest + // - IFDNE: only set if current value's digest does not equal MatchDigest + MatchDigest string + // Zero `TTL` or `Expiration` means that the key has no expiration time. TTL time.Duration ExpireAt time.Time @@ -296,6 +368,18 @@ func (c cmdable) SetArgs(ctx context.Context, key string, value interface{}, a S if a.Mode != "" { args = append(args, a.Mode) + + // Add match value/digest for CAS modes + switch a.Mode { + case "ifeq", "IFEQ", "ifne", "IFNE": + if a.MatchValue != nil { + args = append(args, a.MatchValue) + } + case "ifdeq", "IFDEQ", "ifdne", "IFDNE": + if a.MatchDigest != "" { + args = append(args, a.MatchDigest) + } + } } if a.Get { @@ -363,6 +447,218 @@ func (c cmdable) SetXX(ctx context.Context, key string, value interface{}, expir return cmd } +// SetIFEQ Redis `SET key value [expiration] IFEQ match-value` command. +// Compare-and-set: only sets the value if the current value equals matchValue. +// +// Returns "OK" on success. +// Returns nil if the operation was aborted due to condition not matching. +// Zero expiration means the key has no expiration time. +func (c cmdable) SetIFEQ(ctx context.Context, key string, value interface{}, matchValue interface{}, expiration time.Duration) *StatusCmd { + args := []interface{}{"set", key, value} + + if expiration > 0 { + if usePrecise(expiration) { + args = append(args, "px", formatMs(ctx, expiration)) + } else { + args = append(args, "ex", formatSec(ctx, expiration)) + } + } else if expiration == KeepTTL { + args = append(args, "keepttl") + } + + args = append(args, "ifeq", matchValue) + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// SetIFEQGet Redis `SET key value [expiration] IFEQ match-value GET` command. +// Compare-and-set with GET: only sets the value if the current value equals matchValue, +// and returns the previous value. +// +// Returns the previous value on success. +// Returns nil if the operation was aborted due to condition not matching. +// Zero expiration means the key has no expiration time. +func (c cmdable) SetIFEQGet(ctx context.Context, key string, value interface{}, matchValue interface{}, expiration time.Duration) *StringCmd { + args := []interface{}{"set", key, value} + + if expiration > 0 { + if usePrecise(expiration) { + args = append(args, "px", formatMs(ctx, expiration)) + } else { + args = append(args, "ex", formatSec(ctx, expiration)) + } + } else if expiration == KeepTTL { + args = append(args, "keepttl") + } + + args = append(args, "ifeq", matchValue, "get") + + cmd := NewStringCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// SetIFNE Redis `SET key value [expiration] IFNE match-value` command. +// Compare-and-set: only sets the value if the current value does not equal matchValue. +// +// Returns "OK" on success. +// Returns nil if the operation was aborted due to condition not matching. +// Zero expiration means the key has no expiration time. +func (c cmdable) SetIFNE(ctx context.Context, key string, value interface{}, matchValue interface{}, expiration time.Duration) *StatusCmd { + args := []interface{}{"set", key, value} + + if expiration > 0 { + if usePrecise(expiration) { + args = append(args, "px", formatMs(ctx, expiration)) + } else { + args = append(args, "ex", formatSec(ctx, expiration)) + } + } else if expiration == KeepTTL { + args = append(args, "keepttl") + } + + args = append(args, "ifne", matchValue) + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// SetIFNEGet Redis `SET key value [expiration] IFNE match-value GET` command. +// Compare-and-set with GET: only sets the value if the current value does not equal matchValue, +// and returns the previous value. +// +// Returns the previous value on success. +// Returns nil if the operation was aborted due to condition not matching. +// Zero expiration means the key has no expiration time. +func (c cmdable) SetIFNEGet(ctx context.Context, key string, value interface{}, matchValue interface{}, expiration time.Duration) *StringCmd { + args := []interface{}{"set", key, value} + + if expiration > 0 { + if usePrecise(expiration) { + args = append(args, "px", formatMs(ctx, expiration)) + } else { + args = append(args, "ex", formatSec(ctx, expiration)) + } + } else if expiration == KeepTTL { + args = append(args, "keepttl") + } + + args = append(args, "ifne", matchValue, "get") + + cmd := NewStringCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// SetIFDEQ Redis `SET key value [expiration] IFDEQ match-digest` command. +// Compare-and-set using digest: only sets the value if the current value's digest equals matchDigest. +// +// Returns "OK" on success. +// Returns nil if the operation was aborted due to condition not matching. +// Zero expiration means the key has no expiration time. +func (c cmdable) SetIFDEQ(ctx context.Context, key string, value interface{}, matchDigest string, expiration time.Duration) *StatusCmd { + args := []interface{}{"set", key, value} + + if expiration > 0 { + if usePrecise(expiration) { + args = append(args, "px", formatMs(ctx, expiration)) + } else { + args = append(args, "ex", formatSec(ctx, expiration)) + } + } else if expiration == KeepTTL { + args = append(args, "keepttl") + } + + args = append(args, "ifdeq", matchDigest) + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// SetIFDEQGet Redis `SET key value [expiration] IFDEQ match-digest GET` command. +// Compare-and-set using digest with GET: only sets the value if the current value's digest equals matchDigest, +// and returns the previous value. +// +// Returns the previous value on success. +// Returns nil if the operation was aborted due to condition not matching. +// Zero expiration means the key has no expiration time. +func (c cmdable) SetIFDEQGet(ctx context.Context, key string, value interface{}, matchDigest string, expiration time.Duration) *StringCmd { + args := []interface{}{"set", key, value} + + if expiration > 0 { + if usePrecise(expiration) { + args = append(args, "px", formatMs(ctx, expiration)) + } else { + args = append(args, "ex", formatSec(ctx, expiration)) + } + } else if expiration == KeepTTL { + args = append(args, "keepttl") + } + + args = append(args, "ifdeq", matchDigest, "get") + + cmd := NewStringCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// SetIFDNE Redis `SET key value [expiration] IFDNE match-digest` command. +// Compare-and-set using digest: only sets the value if the current value's digest does not equal matchDigest. +// +// Returns "OK" on success. +// Returns nil if the operation was aborted due to condition not matching. +// Zero expiration means the key has no expiration time. +func (c cmdable) SetIFDNE(ctx context.Context, key string, value interface{}, matchDigest string, expiration time.Duration) *StatusCmd { + args := []interface{}{"set", key, value} + + if expiration > 0 { + if usePrecise(expiration) { + args = append(args, "px", formatMs(ctx, expiration)) + } else { + args = append(args, "ex", formatSec(ctx, expiration)) + } + } else if expiration == KeepTTL { + args = append(args, "keepttl") + } + + args = append(args, "ifdne", matchDigest) + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// SetIFDNEGet Redis `SET key value [expiration] IFDNE match-digest GET` command. +// Compare-and-set using digest with GET: only sets the value if the current value's digest does not equal matchDigest, +// and returns the previous value. +// +// Returns the previous value on success. +// Returns nil if the operation was aborted due to condition not matching. +// Zero expiration means the key has no expiration time. +func (c cmdable) SetIFDNEGet(ctx context.Context, key string, value interface{}, matchDigest string, expiration time.Duration) *StringCmd { + args := []interface{}{"set", key, value} + + if expiration > 0 { + if usePrecise(expiration) { + args = append(args, "px", formatMs(ctx, expiration)) + } else { + args = append(args, "ex", formatSec(ctx, expiration)) + } + } else if expiration == KeepTTL { + args = append(args, "keepttl") + } + + args = append(args, "ifdne", matchDigest, "get") + + cmd := NewStringCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + func (c cmdable) SetRange(ctx context.Context, key string, offset int64, value string) *IntCmd { cmd := NewIntCmd(ctx, "setrange", key, offset, value) _ = c(ctx, cmd)