Skip to content

Commit 4c67b15

Browse files
authored
Implement msetex command (#3510)
* Implement msetex command * Refactor to use SetArgs * Use dedicated MSetExArgs for MSETEX command * Fix formatting * Keep only instant/duration API * Rm not needed license comment. * Fix tests
1 parent a209fba commit 4c67b15

23 files changed

+676
-15
lines changed

src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2328,6 +2328,11 @@ public RedisFuture<Boolean> msetnx(Map<K, V> map) {
23282328
return dispatch(commandBuilder.msetnx(map));
23292329
}
23302330

2331+
@Override
2332+
public RedisFuture<Boolean> msetex(Map<K, V> map, MSetExArgs args) {
2333+
return dispatch(commandBuilder.msetex(map, args));
2334+
}
2335+
23312336
@Override
23322337
public RedisFuture<String> multi() {
23332338
return dispatch(commandBuilder.multi());

src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2399,6 +2399,11 @@ public Mono<Boolean> msetnx(Map<K, V> map) {
23992399
return createMono(() -> commandBuilder.msetnx(map));
24002400
}
24012401

2402+
@Override
2403+
public Mono<Boolean> msetex(Map<K, V> map, MSetExArgs args) {
2404+
return createMono(() -> commandBuilder.msetex(map, args));
2405+
}
2406+
24022407
@Override
24032408
public Mono<String> multi() {
24042409
return createMono(commandBuilder::multi);
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
/*
2+
* Copyright 2011-Present, Redis Ltd. and Contributors
3+
* All rights reserved.
4+
*
5+
* Licensed under the MIT License.
6+
*/
7+
package io.lettuce.core;
8+
9+
import io.lettuce.core.internal.LettuceAssert;
10+
import io.lettuce.core.protocol.CommandArgs;
11+
12+
import java.time.Duration;
13+
import java.time.Instant;
14+
import java.util.Date;
15+
16+
import static io.lettuce.core.protocol.CommandKeyword.NX;
17+
import static io.lettuce.core.protocol.CommandKeyword.XX;
18+
19+
/**
20+
* Argument list builder for the Redis <a href="https://redis.io/commands/msetex">MSETEX</a> command starting from Redis 8.4
21+
* Static import the methods from {@link Builder} and chain the method calls: {@code ex(10).nx()}.
22+
* <p>
23+
* {@link MSetExArgs} is a mutable object and instances should be used only once to avoid shared mutable state.
24+
*
25+
* @author Aleksandar Todorov
26+
* @since 7.1
27+
*/
28+
public class MSetExArgs implements CompositeArgument {
29+
30+
private Long ex;
31+
32+
private Long exAt;
33+
34+
private Long px;
35+
36+
private Long pxAt;
37+
38+
private boolean nx = false;
39+
40+
private boolean xx = false;
41+
42+
private boolean keepttl = false;
43+
44+
/**
45+
* Builder entry points for {@link MSetExArgs}.
46+
*/
47+
public static class Builder {
48+
49+
/**
50+
* Utility constructor.
51+
*/
52+
private Builder() {
53+
}
54+
55+
/**
56+
* Creates new {@link MSetExArgs} and enable {@literal EX}.
57+
*
58+
* @param timeout expire time as duration.
59+
* @return new {@link MSetExArgs} with {@literal EX} enabled.
60+
* @see MSetExArgs#ex(long)
61+
* @since 7.1
62+
*/
63+
public static MSetExArgs ex(Duration timeout) {
64+
return new MSetExArgs().ex(timeout);
65+
}
66+
67+
/**
68+
* Creates new {@link MSetExArgs} and enable {@literal EXAT}.
69+
*
70+
* @param timestamp the timestamp type: posix time in seconds.
71+
* @return new {@link MSetExArgs} with {@literal EXAT} enabled.
72+
* @see MSetExArgs#exAt(Instant)
73+
* @since 7.1
74+
*/
75+
public static MSetExArgs exAt(Instant timestamp) {
76+
return new MSetExArgs().exAt(timestamp);
77+
}
78+
79+
/**
80+
* Creates new {@link MSetExArgs} and enable {@literal PX}.
81+
*
82+
* @param timeout expire time in milliseconds.
83+
* @return new {@link MSetExArgs} with {@literal PX} enabled.
84+
* @see MSetExArgs#px(long)
85+
* @since 7.1
86+
*/
87+
public static MSetExArgs px(Duration timeout) {
88+
return new MSetExArgs().px(timeout);
89+
}
90+
91+
/**
92+
* Creates new {@link MSetExArgs} and enable {@literal PXAT}.
93+
*
94+
* @param timestamp the timestamp type: posix time.
95+
* @return new {@link MSetExArgs} with {@literal PXAT} enabled.
96+
* @see MSetExArgs#pxAt(Instant)
97+
* @since 7.1
98+
*/
99+
public static MSetExArgs pxAt(Instant timestamp) {
100+
return new MSetExArgs().pxAt(timestamp);
101+
}
102+
103+
/**
104+
* Creates new {@link MSetExArgs} and enable {@literal NX}.
105+
*
106+
* @return new {@link MSetExArgs} with {@literal NX} enabled.
107+
* @see MSetExArgs#nx()
108+
* @since 7.1
109+
*/
110+
public static MSetExArgs nx() {
111+
return new MSetExArgs().nx();
112+
}
113+
114+
/**
115+
* Creates new {@link MSetExArgs} and enable {@literal XX}.
116+
*
117+
* @return new {@link MSetExArgs} with {@literal XX} enabled.
118+
* @see MSetExArgs#xx()
119+
*/
120+
public static MSetExArgs xx() {
121+
return new MSetExArgs().xx();
122+
}
123+
124+
/**
125+
* Creates new {@link MSetExArgs} and enable {@literal KEEPTTL}.
126+
*
127+
* @return new {@link MSetExArgs} with {@literal KEEPTTL} enabled.
128+
* @see MSetExArgs#keepttl()
129+
* @since 7.1
130+
*/
131+
public static MSetExArgs keepttl() {
132+
return new MSetExArgs().keepttl();
133+
}
134+
135+
}
136+
137+
/**
138+
* Set the specified expire time, in seconds.
139+
*
140+
* @param timeout expire time in seconds.
141+
* @return {@code this} {@link MSetExArgs}.
142+
* @since 7.1
143+
*/
144+
public MSetExArgs ex(Duration timeout) {
145+
146+
LettuceAssert.notNull(timeout, "Timeout must not be null");
147+
148+
this.ex = timeout.toMillis() / 1000;
149+
return this;
150+
}
151+
152+
/**
153+
* Set the specified expire at time using a posix {@code timestamp}.
154+
*
155+
* @param timestamp the timestamp type: posix time in seconds.
156+
* @return {@code this} {@link MSetExArgs}.
157+
* @since 7.1
158+
*/
159+
public MSetExArgs exAt(Instant timestamp) {
160+
161+
LettuceAssert.notNull(timestamp, "Timestamp must not be null");
162+
163+
this.exAt = timestamp.toEpochMilli() / 1000;
164+
return this;
165+
}
166+
167+
/**
168+
* Set the specified expire time, in milliseconds.
169+
*
170+
* @param timeout expire time in milliseconds.
171+
* @return {@code this} {@link MSetExArgs}.
172+
* @since 7.1
173+
*/
174+
public MSetExArgs px(Duration timeout) {
175+
176+
LettuceAssert.notNull(timeout, "Timeout must not be null");
177+
178+
this.px = timeout.toMillis();
179+
return this;
180+
}
181+
182+
/**
183+
* Set the specified expire at time using a posix {@code timestamp}.
184+
*
185+
* @param timestamp the timestamp type: posix time in milliseconds.
186+
* @return {@code this} {@link MSetExArgs}.
187+
* @since 7.1
188+
*/
189+
public MSetExArgs pxAt(Instant timestamp) {
190+
191+
LettuceAssert.notNull(timestamp, "Timestamp must not be null");
192+
193+
this.pxAt = timestamp.toEpochMilli();
194+
return this;
195+
}
196+
197+
/**
198+
* Only set the key if it does not already exist.
199+
*
200+
* @return {@code this} {@link MSetExArgs}.
201+
* @since 7.1
202+
*/
203+
public MSetExArgs nx() {
204+
205+
this.nx = true;
206+
return this;
207+
}
208+
209+
/**
210+
* Set the value and retain the existing TTL.
211+
*
212+
* @return {@code this} {@link MSetExArgs}.
213+
* @since 7.1
214+
*/
215+
public MSetExArgs keepttl() {
216+
217+
this.keepttl = true;
218+
return this;
219+
}
220+
221+
/**
222+
* Only set the key if it already exists.
223+
*
224+
* @return {@code this} {@link MSetExArgs}.
225+
* @since 7.1
226+
*/
227+
public MSetExArgs xx() {
228+
229+
this.xx = true;
230+
return this;
231+
}
232+
233+
@Override
234+
public <K, V> void build(CommandArgs<K, V> args) {
235+
236+
if (ex != null) {
237+
args.add("EX").add(ex);
238+
}
239+
240+
if (exAt != null) {
241+
args.add("EXAT").add(exAt);
242+
}
243+
244+
if (px != null) {
245+
args.add("PX").add(px);
246+
}
247+
248+
if (pxAt != null) {
249+
args.add("PXAT").add(pxAt);
250+
}
251+
252+
if (nx) {
253+
args.add(NX);
254+
}
255+
256+
if (xx) {
257+
args.add(XX);
258+
}
259+
260+
if (keepttl) {
261+
args.add("KEEPTTL");
262+
}
263+
}
264+
265+
}

src/main/java/io/lettuce/core/RedisCommandBuilder.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2249,6 +2249,17 @@ Command<K, V, Boolean> msetnx(Map<K, V> map) {
22492249
return createCommand(MSETNX, new BooleanOutput<>(codec), args);
22502250
}
22512251

2252+
Command<K, V, Boolean> msetex(Map<K, V> map, MSetExArgs setArgs) {
2253+
LettuceAssert.notNull(map, "Map " + MUST_NOT_BE_NULL);
2254+
LettuceAssert.isTrue(!map.isEmpty(), "Map " + MUST_NOT_BE_EMPTY);
2255+
2256+
CommandArgs<K, V> args = new CommandArgs<>(codec).add(map.size()).add(map);
2257+
if (setArgs != null) {
2258+
setArgs.build(args);
2259+
}
2260+
return createCommand(MSETEX, new BooleanOutput<>(codec), args);
2261+
}
2262+
22522263
Command<K, V, String> multi() {
22532264
return createCommand(MULTI, new StatusOutput<>(codec));
22542265
}

src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@
2525
import io.lettuce.core.BitFieldArgs;
2626
import io.lettuce.core.GetExArgs;
2727
import io.lettuce.core.KeyValue;
28+
import io.lettuce.core.MSetExArgs;
2829
import io.lettuce.core.RedisFuture;
2930
import io.lettuce.core.SetArgs;
3031
import io.lettuce.core.LcsArgs;
3132
import io.lettuce.core.StrAlgoArgs;
3233
import io.lettuce.core.StringMatchResult;
34+
3335
import io.lettuce.core.output.KeyValueStreamingChannel;
3436

3537
/**
@@ -364,6 +366,17 @@ public interface RedisStringAsyncCommands<K, V> {
364366
*/
365367
RedisFuture<Boolean> msetnx(Map<K, V> map);
366368

369+
/**
370+
* Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one
371+
* of [EX|PX|EXAT|PXAT|KEEPTTL].
372+
*
373+
* @param map the map of keys and values.
374+
* @param args the {@link MSetExArgs} specifying NX/XX and expiration.
375+
* @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise.
376+
* @since 7.1
377+
*/
378+
RedisFuture<Boolean> msetex(Map<K, V> map, MSetExArgs args);
379+
367380
/**
368381
* Set the string value of a key.
369382
*

src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import java.util.Map;
2323

24+
import io.lettuce.core.MSetExArgs;
2425
import reactor.core.publisher.Flux;
2526
import reactor.core.publisher.Mono;
2627
import io.lettuce.core.BitFieldArgs;
@@ -31,6 +32,7 @@
3132
import io.lettuce.core.LcsArgs;
3233
import io.lettuce.core.StringMatchResult;
3334
import io.lettuce.core.Value;
35+
3436
import io.lettuce.core.output.KeyValueStreamingChannel;
3537

3638
/**
@@ -368,6 +370,17 @@ public interface RedisStringReactiveCommands<K, V> {
368370
*/
369371
Mono<Boolean> msetnx(Map<K, V> map);
370372

373+
/**
374+
* Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one
375+
* of [EX|PX|EXAT|PXAT|KEEPTTL].
376+
*
377+
* @param map the map of keys and values.
378+
* @param args the {@link MSetExArgs} specifying NX/XX and expiration.
379+
* @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise.
380+
* @since 7.1
381+
*/
382+
Mono<Boolean> msetex(Map<K, V> map, MSetExArgs args);
383+
371384
/**
372385
* Set the string value of a key.
373386
*

0 commit comments

Comments
 (0)