Skip to content

Commit 284d93a

Browse files
ofekshenawandyakov
andauthored
feat(cmd): Add support for MSetEX command (#3580)
Co-authored-by: Nedyalko Dyakov <1547186+ndyakov@users.noreply.github.com>
1 parent e2f6700 commit 284d93a

File tree

6 files changed

+204
-4
lines changed

6 files changed

+204
-4
lines changed

commands_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1935,6 +1935,137 @@ var _ = Describe("Commands", func() {
19351935
Expect(mSetNX.Val()).To(Equal(true))
19361936
})
19371937

1938+
It("should MSetEX", func() {
1939+
SkipBeforeRedisVersion(8.3, "MSetEX is available since redis 8.4")
1940+
args := redis.MSetEXArgs{
1941+
Expiration: &redis.ExpirationOption{
1942+
Mode: redis.EX,
1943+
Value: 1,
1944+
},
1945+
}
1946+
mSetEX := client.MSetEX(ctx, args, "key1", "hello1", "key2", "hello2")
1947+
Expect(mSetEX.Err()).NotTo(HaveOccurred())
1948+
Expect(mSetEX.Val()).To(Equal(int64(1)))
1949+
1950+
// Verify keys were set
1951+
val1 := client.Get(ctx, "key1")
1952+
Expect(val1.Err()).NotTo(HaveOccurred())
1953+
Expect(val1.Val()).To(Equal("hello1"))
1954+
1955+
val2 := client.Get(ctx, "key2")
1956+
Expect(val2.Err()).NotTo(HaveOccurred())
1957+
Expect(val2.Val()).To(Equal("hello2"))
1958+
1959+
// Verify TTL was set
1960+
ttl1 := client.TTL(ctx, "key1")
1961+
Expect(ttl1.Err()).NotTo(HaveOccurred())
1962+
Expect(ttl1.Val()).To(BeNumerically(">", 0))
1963+
Expect(ttl1.Val()).To(BeNumerically("<=", 1*time.Second))
1964+
1965+
ttl2 := client.TTL(ctx, "key2")
1966+
Expect(ttl2.Err()).NotTo(HaveOccurred())
1967+
Expect(ttl2.Val()).To(BeNumerically(">", 0))
1968+
Expect(ttl2.Val()).To(BeNumerically("<=", 1*time.Second))
1969+
})
1970+
1971+
It("should MSetEX with NX mode", func() {
1972+
SkipBeforeRedisVersion(8.3, "MSetEX is available since redis 8.4")
1973+
1974+
client.Set(ctx, "key1", "existing", 0)
1975+
1976+
// Try to set with NX mode - should fail because key1 exists
1977+
args := redis.MSetEXArgs{
1978+
Condition: redis.NX,
1979+
Expiration: &redis.ExpirationOption{
1980+
Mode: redis.EX,
1981+
Value: 1,
1982+
},
1983+
}
1984+
mSetEX := client.MSetEX(ctx, args, "key1", "new1", "key2", "new2")
1985+
Expect(mSetEX.Err()).NotTo(HaveOccurred())
1986+
Expect(mSetEX.Val()).To(Equal(int64(0)))
1987+
1988+
val1 := client.Get(ctx, "key1")
1989+
Expect(val1.Err()).NotTo(HaveOccurred())
1990+
Expect(val1.Val()).To(Equal("existing"))
1991+
1992+
val2 := client.Get(ctx, "key2")
1993+
Expect(val2.Err()).To(Equal(redis.Nil))
1994+
1995+
client.Del(ctx, "key1")
1996+
1997+
// Now try with NX mode when keys don't exist - should succeed
1998+
mSetEX = client.MSetEX(ctx, args, "key1", "new1", "key2", "new2")
1999+
Expect(mSetEX.Err()).NotTo(HaveOccurred())
2000+
Expect(mSetEX.Val()).To(Equal(int64(1)))
2001+
2002+
val1 = client.Get(ctx, "key1")
2003+
Expect(val1.Err()).NotTo(HaveOccurred())
2004+
Expect(val1.Val()).To(Equal("new1"))
2005+
2006+
val2 = client.Get(ctx, "key2")
2007+
Expect(val2.Err()).NotTo(HaveOccurred())
2008+
Expect(val2.Val()).To(Equal("new2"))
2009+
})
2010+
2011+
It("should MSetEX with XX mode", func() {
2012+
SkipBeforeRedisVersion(8.3, "MSetEX is available since redis 8.4")
2013+
2014+
args := redis.MSetEXArgs{
2015+
Condition: redis.XX,
2016+
Expiration: &redis.ExpirationOption{
2017+
Mode: redis.EX,
2018+
Value: 1,
2019+
},
2020+
}
2021+
mSetEX := client.MSetEX(ctx, args, "key1", "new1", "key2", "new2")
2022+
Expect(mSetEX.Err()).NotTo(HaveOccurred())
2023+
Expect(mSetEX.Val()).To(Equal(int64(0)))
2024+
2025+
client.Set(ctx, "key1", "existing1", 0)
2026+
client.Set(ctx, "key2", "existing2", 0)
2027+
2028+
mSetEX = client.MSetEX(ctx, args, "key1", "new1", "key2", "new2")
2029+
Expect(mSetEX.Err()).NotTo(HaveOccurred())
2030+
Expect(mSetEX.Val()).To(Equal(int64(1)))
2031+
2032+
val1 := client.Get(ctx, "key1")
2033+
Expect(val1.Err()).NotTo(HaveOccurred())
2034+
Expect(val1.Val()).To(Equal("new1"))
2035+
2036+
val2 := client.Get(ctx, "key2")
2037+
Expect(val2.Err()).NotTo(HaveOccurred())
2038+
Expect(val2.Val()).To(Equal("new2"))
2039+
2040+
ttl1 := client.TTL(ctx, "key1")
2041+
Expect(ttl1.Err()).NotTo(HaveOccurred())
2042+
Expect(ttl1.Val()).To(BeNumerically(">", 0))
2043+
})
2044+
2045+
It("should MSetEX with map", func() {
2046+
SkipBeforeRedisVersion(8.3, "MSetEX is available since redis 8.4")
2047+
args := redis.MSetEXArgs{
2048+
Expiration: &redis.ExpirationOption{
2049+
Mode: redis.EX,
2050+
Value: 1,
2051+
},
2052+
}
2053+
mSetEX := client.MSetEX(ctx, args, map[string]interface{}{
2054+
"key1": "value1",
2055+
"key2": "value2",
2056+
})
2057+
Expect(mSetEX.Err()).NotTo(HaveOccurred())
2058+
Expect(mSetEX.Val()).To(Equal(int64(1)))
2059+
2060+
val1 := client.Get(ctx, "key1")
2061+
Expect(val1.Err()).NotTo(HaveOccurred())
2062+
Expect(val1.Val()).To(Equal("value1"))
2063+
2064+
val2 := client.Get(ctx, "key2")
2065+
Expect(val2.Err()).NotTo(HaveOccurred())
2066+
Expect(val2.Val()).To(Equal("value2"))
2067+
})
2068+
19382069
It("should SetWithArgs with TTL", func() {
19392070
args := redis.SetArgs{
19402071
TTL: 500 * time.Millisecond,

extra/rediscensus/go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,3 @@ retract (
2222
v9.7.2 // This version was accidentally released. Please use version 9.7.3 instead.
2323
v9.5.3 // This version was accidentally released. Please use version 9.6.0 instead.
2424
)
25-

extra/rediscmd/go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,3 @@ retract (
1919
v9.7.2 // This version was accidentally released. Please use version 9.7.3 instead.
2020
v9.5.3 // This version was accidentally released. Please use version 9.6.0 instead.
2121
)
22-

extra/redisotel/go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,3 @@ retract (
2727
v9.7.2 // This version was accidentally released. Please use version 9.7.3 instead.
2828
v9.5.3 // This version was accidentally released. Please use version 9.6.0 instead.
2929
)
30-

extra/redisprometheus/go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,3 @@ retract (
2626
v9.7.2 // This version was accidentally released. Please use version 9.7.3 instead.
2727
v9.5.3 // This version was accidentally released. Please use version 9.6.0 instead.
2828
)
29-

string_commands.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type StringCmdable interface {
2121
MGet(ctx context.Context, keys ...string) *SliceCmd
2222
MSet(ctx context.Context, values ...interface{}) *StatusCmd
2323
MSetNX(ctx context.Context, values ...interface{}) *BoolCmd
24+
MSetEX(ctx context.Context, args MSetEXArgs, values ...interface{}) *IntCmd
2425
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd
2526
SetArgs(ctx context.Context, key string, value interface{}, a SetArgs) *StatusCmd
2627
SetEx(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd
@@ -112,6 +113,35 @@ func (c cmdable) IncrByFloat(ctx context.Context, key string, value float64) *Fl
112113
return cmd
113114
}
114115

116+
type SetCondition string
117+
118+
const (
119+
// NX only set the keys and their expiration if none exist
120+
NX SetCondition = "NX"
121+
// XX only set the keys and their expiration if all already exist
122+
XX SetCondition = "XX"
123+
)
124+
125+
type ExpirationMode string
126+
127+
const (
128+
// EX sets expiration in seconds
129+
EX ExpirationMode = "EX"
130+
// PX sets expiration in milliseconds
131+
PX ExpirationMode = "PX"
132+
// EXAT sets expiration as Unix timestamp in seconds
133+
EXAT ExpirationMode = "EXAT"
134+
// PXAT sets expiration as Unix timestamp in milliseconds
135+
PXAT ExpirationMode = "PXAT"
136+
// KEEPTTL keeps the existing TTL
137+
KEEPTTL ExpirationMode = "KEEPTTL"
138+
)
139+
140+
type ExpirationOption struct {
141+
Mode ExpirationMode
142+
Value int64
143+
}
144+
115145
func (c cmdable) LCS(ctx context.Context, q *LCSQuery) *LCSCmd {
116146
cmd := NewLCSCmd(ctx, q)
117147
_ = c(ctx, cmd)
@@ -157,6 +187,49 @@ func (c cmdable) MSetNX(ctx context.Context, values ...interface{}) *BoolCmd {
157187
return cmd
158188
}
159189

190+
type MSetEXArgs struct {
191+
Condition SetCondition
192+
Expiration *ExpirationOption
193+
}
194+
195+
// MSetEX sets the given keys to their respective values.
196+
// This command is an extension of the MSETNX that adds expiration and XX options.
197+
// Available since Redis 8.4
198+
// Important: When this method is used with Cluster clients, all keys
199+
// must be in the same hash slot, otherwise CROSSSLOT error will be returned.
200+
// For more information, see https://redis.io/commands/msetex
201+
func (c cmdable) MSetEX(ctx context.Context, args MSetEXArgs, values ...interface{}) *IntCmd {
202+
expandedArgs := appendArgs([]interface{}{}, values)
203+
numkeys := len(expandedArgs) / 2
204+
205+
cmdArgs := make([]interface{}, 0, 2+len(expandedArgs)+3)
206+
cmdArgs = append(cmdArgs, "msetex", numkeys)
207+
cmdArgs = append(cmdArgs, expandedArgs...)
208+
209+
if args.Condition != "" {
210+
cmdArgs = append(cmdArgs, string(args.Condition))
211+
}
212+
213+
if args.Expiration != nil {
214+
switch args.Expiration.Mode {
215+
case EX:
216+
cmdArgs = append(cmdArgs, "ex", args.Expiration.Value)
217+
case PX:
218+
cmdArgs = append(cmdArgs, "px", args.Expiration.Value)
219+
case EXAT:
220+
cmdArgs = append(cmdArgs, "exat", args.Expiration.Value)
221+
case PXAT:
222+
cmdArgs = append(cmdArgs, "pxat", args.Expiration.Value)
223+
case KEEPTTL:
224+
cmdArgs = append(cmdArgs, "keepttl")
225+
}
226+
}
227+
228+
cmd := NewIntCmd(ctx, cmdArgs...)
229+
_ = c(ctx, cmd)
230+
return cmd
231+
}
232+
160233
// Set Redis `SET key value [expiration]` command.
161234
// Use expiration for `SETEx`-like behavior.
162235
//

0 commit comments

Comments
 (0)