Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1935,6 +1935,137 @@ var _ = Describe("Commands", func() {
Expect(mSetNX.Val()).To(Equal(true))
})

It("should MSetEX", func() {
SkipBeforeRedisVersion(8.3, "MSetEX is available since redis 8.4")
args := redis.MSetEXArgs{
Expiration: &redis.ExpirationOption{
Mode: redis.EX,
Value: 1,
},
}
mSetEX := client.MSetEX(ctx, args, "key1", "hello1", "key2", "hello2")
Expect(mSetEX.Err()).NotTo(HaveOccurred())
Expect(mSetEX.Val()).To(Equal(int64(1)))

// Verify keys were set
val1 := client.Get(ctx, "key1")
Expect(val1.Err()).NotTo(HaveOccurred())
Expect(val1.Val()).To(Equal("hello1"))

val2 := client.Get(ctx, "key2")
Expect(val2.Err()).NotTo(HaveOccurred())
Expect(val2.Val()).To(Equal("hello2"))

// Verify TTL was set
ttl1 := client.TTL(ctx, "key1")
Expect(ttl1.Err()).NotTo(HaveOccurred())
Expect(ttl1.Val()).To(BeNumerically(">", 0))
Expect(ttl1.Val()).To(BeNumerically("<=", 1*time.Second))

ttl2 := client.TTL(ctx, "key2")
Expect(ttl2.Err()).NotTo(HaveOccurred())
Expect(ttl2.Val()).To(BeNumerically(">", 0))
Expect(ttl2.Val()).To(BeNumerically("<=", 1*time.Second))
})

It("should MSetEX with NX mode", func() {
SkipBeforeRedisVersion(8.3, "MSetEX is available since redis 8.4")

client.Set(ctx, "key1", "existing", 0)

// Try to set with NX mode - should fail because key1 exists
args := redis.MSetEXArgs{
Condition: redis.NX,
Expiration: &redis.ExpirationOption{
Mode: redis.EX,
Value: 1,
},
}
mSetEX := client.MSetEX(ctx, args, "key1", "new1", "key2", "new2")
Expect(mSetEX.Err()).NotTo(HaveOccurred())
Expect(mSetEX.Val()).To(Equal(int64(0)))

val1 := client.Get(ctx, "key1")
Expect(val1.Err()).NotTo(HaveOccurred())
Expect(val1.Val()).To(Equal("existing"))

val2 := client.Get(ctx, "key2")
Expect(val2.Err()).To(Equal(redis.Nil))

client.Del(ctx, "key1")

// Now try with NX mode when keys don't exist - should succeed
mSetEX = client.MSetEX(ctx, args, "key1", "new1", "key2", "new2")
Expect(mSetEX.Err()).NotTo(HaveOccurred())
Expect(mSetEX.Val()).To(Equal(int64(1)))

val1 = client.Get(ctx, "key1")
Expect(val1.Err()).NotTo(HaveOccurred())
Expect(val1.Val()).To(Equal("new1"))

val2 = client.Get(ctx, "key2")
Expect(val2.Err()).NotTo(HaveOccurred())
Expect(val2.Val()).To(Equal("new2"))
})

It("should MSetEX with XX mode", func() {
SkipBeforeRedisVersion(8.3, "MSetEX is available since redis 8.4")

args := redis.MSetEXArgs{
Condition: redis.XX,
Expiration: &redis.ExpirationOption{
Mode: redis.EX,
Value: 1,
},
}
mSetEX := client.MSetEX(ctx, args, "key1", "new1", "key2", "new2")
Expect(mSetEX.Err()).NotTo(HaveOccurred())
Expect(mSetEX.Val()).To(Equal(int64(0)))

client.Set(ctx, "key1", "existing1", 0)
client.Set(ctx, "key2", "existing2", 0)

mSetEX = client.MSetEX(ctx, args, "key1", "new1", "key2", "new2")
Expect(mSetEX.Err()).NotTo(HaveOccurred())
Expect(mSetEX.Val()).To(Equal(int64(1)))

val1 := client.Get(ctx, "key1")
Expect(val1.Err()).NotTo(HaveOccurred())
Expect(val1.Val()).To(Equal("new1"))

val2 := client.Get(ctx, "key2")
Expect(val2.Err()).NotTo(HaveOccurred())
Expect(val2.Val()).To(Equal("new2"))

ttl1 := client.TTL(ctx, "key1")
Expect(ttl1.Err()).NotTo(HaveOccurred())
Expect(ttl1.Val()).To(BeNumerically(">", 0))
})

It("should MSetEX with map", func() {
SkipBeforeRedisVersion(8.3, "MSetEX is available since redis 8.4")
args := redis.MSetEXArgs{
Expiration: &redis.ExpirationOption{
Mode: redis.EX,
Value: 1,
},
}
mSetEX := client.MSetEX(ctx, args, map[string]interface{}{
"key1": "value1",
"key2": "value2",
})
Expect(mSetEX.Err()).NotTo(HaveOccurred())
Expect(mSetEX.Val()).To(Equal(int64(1)))

val1 := client.Get(ctx, "key1")
Expect(val1.Err()).NotTo(HaveOccurred())
Expect(val1.Val()).To(Equal("value1"))

val2 := client.Get(ctx, "key2")
Expect(val2.Err()).NotTo(HaveOccurred())
Expect(val2.Val()).To(Equal("value2"))
})

It("should SetWithArgs with TTL", func() {
args := redis.SetArgs{
TTL: 500 * time.Millisecond,
Expand Down
1 change: 0 additions & 1 deletion extra/rediscensus/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,3 @@ retract (
v9.7.2 // This version was accidentally released. Please use version 9.7.3 instead.
v9.5.3 // This version was accidentally released. Please use version 9.6.0 instead.
)

1 change: 0 additions & 1 deletion extra/rediscmd/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,3 @@ retract (
v9.7.2 // This version was accidentally released. Please use version 9.7.3 instead.
v9.5.3 // This version was accidentally released. Please use version 9.6.0 instead.
)

1 change: 0 additions & 1 deletion extra/redisotel/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,3 @@ retract (
v9.7.2 // This version was accidentally released. Please use version 9.7.3 instead.
v9.5.3 // This version was accidentally released. Please use version 9.6.0 instead.
)

1 change: 0 additions & 1 deletion extra/redisprometheus/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,3 @@ retract (
v9.7.2 // This version was accidentally released. Please use version 9.7.3 instead.
v9.5.3 // This version was accidentally released. Please use version 9.6.0 instead.
)

73 changes: 73 additions & 0 deletions string_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type StringCmdable interface {
MGet(ctx context.Context, keys ...string) *SliceCmd
MSet(ctx context.Context, values ...interface{}) *StatusCmd
MSetNX(ctx context.Context, values ...interface{}) *BoolCmd
MSetEX(ctx context.Context, args MSetEXArgs, values ...interface{}) *IntCmd
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
Expand Down Expand Up @@ -112,6 +113,35 @@ func (c cmdable) IncrByFloat(ctx context.Context, key string, value float64) *Fl
return cmd
}

type SetCondition string

const (
// NX only set the keys and their expiration if none exist
NX SetCondition = "NX"
// XX only set the keys and their expiration if all already exist
XX SetCondition = "XX"
)

type ExpirationMode string

const (
// EX sets expiration in seconds
EX ExpirationMode = "EX"
// PX sets expiration in milliseconds
PX ExpirationMode = "PX"
// EXAT sets expiration as Unix timestamp in seconds
EXAT ExpirationMode = "EXAT"
// PXAT sets expiration as Unix timestamp in milliseconds
PXAT ExpirationMode = "PXAT"
// KEEPTTL keeps the existing TTL
KEEPTTL ExpirationMode = "KEEPTTL"
)

type ExpirationOption struct {
Mode ExpirationMode
Value int64
}

func (c cmdable) LCS(ctx context.Context, q *LCSQuery) *LCSCmd {
cmd := NewLCSCmd(ctx, q)
_ = c(ctx, cmd)
Expand Down Expand Up @@ -157,6 +187,49 @@ func (c cmdable) MSetNX(ctx context.Context, values ...interface{}) *BoolCmd {
return cmd
}

type MSetEXArgs struct {
Condition SetCondition
Expiration *ExpirationOption
}

// MSetEX sets the given keys to their respective values.
// This command is an extension of the MSETNX that adds expiration and XX options.
// Available since Redis 8.4
// Important: When this method is used with Cluster clients, all keys
// must be in the same hash slot, otherwise CROSSSLOT error will be returned.
// For more information, see https://redis.io/commands/msetex
func (c cmdable) MSetEX(ctx context.Context, args MSetEXArgs, values ...interface{}) *IntCmd {
expandedArgs := appendArgs([]interface{}{}, values)
numkeys := len(expandedArgs) / 2

cmdArgs := make([]interface{}, 0, 2+len(expandedArgs)+3)
cmdArgs = append(cmdArgs, "msetex", numkeys)
cmdArgs = append(cmdArgs, expandedArgs...)

if args.Condition != "" {
cmdArgs = append(cmdArgs, string(args.Condition))
}

if args.Expiration != nil {
switch args.Expiration.Mode {
case EX:
cmdArgs = append(cmdArgs, "ex", args.Expiration.Value)
case PX:
cmdArgs = append(cmdArgs, "px", args.Expiration.Value)
case EXAT:
cmdArgs = append(cmdArgs, "exat", args.Expiration.Value)
case PXAT:
cmdArgs = append(cmdArgs, "pxat", args.Expiration.Value)
case KEEPTTL:
cmdArgs = append(cmdArgs, "keepttl")
}
}

cmd := NewIntCmd(ctx, cmdArgs...)
_ = c(ctx, cmd)
return cmd
}

// Set Redis `SET key value [expiration]` command.
// Use expiration for `SETEx`-like behavior.
//
Expand Down
Loading