Skip to content
5 changes: 5 additions & 0 deletions src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -2328,6 +2328,11 @@ public RedisFuture<Boolean> msetnx(Map<K, V> map) {
return dispatch(commandBuilder.msetnx(map));
}

@Override
public RedisFuture<Boolean> msetex(Map<K, V> map, MSetExArgs args) {
return dispatch(commandBuilder.msetex(map, args));
}

@Override
public RedisFuture<String> multi() {
return dispatch(commandBuilder.multi());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2399,6 +2399,11 @@ public Mono<Boolean> msetnx(Map<K, V> map) {
return createMono(() -> commandBuilder.msetnx(map));
}

@Override
public Mono<Boolean> msetex(Map<K, V> map, MSetExArgs args) {
return createMono(() -> commandBuilder.msetex(map, args));
}

@Override
public Mono<String> multi() {
return createMono(commandBuilder::multi);
Expand Down
265 changes: 265 additions & 0 deletions src/main/java/io/lettuce/core/MSetExArgs.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/*
* Copyright 2011-Present, Redis Ltd. and Contributors
* All rights reserved.
*
* Licensed under the MIT License.
*/
package io.lettuce.core;

import io.lettuce.core.internal.LettuceAssert;
import io.lettuce.core.protocol.CommandArgs;

import java.time.Duration;
import java.time.Instant;
import java.util.Date;

import static io.lettuce.core.protocol.CommandKeyword.NX;
import static io.lettuce.core.protocol.CommandKeyword.XX;

/**
* Argument list builder for the Redis <a href="https://redis.io/commands/msetex">MSETEX</a> command starting from Redis 8.4
* Static import the methods from {@link Builder} and chain the method calls: {@code ex(10).nx()}.
* <p>
* {@link MSetExArgs} is a mutable object and instances should be used only once to avoid shared mutable state.
*
* @author Aleksandar Todorov
* @since 7.1
*/
public class MSetExArgs implements CompositeArgument {

private Long ex;

private Long exAt;

private Long px;

private Long pxAt;

private boolean nx = false;

private boolean xx = false;

private boolean keepttl = false;

/**
* Builder entry points for {@link MSetExArgs}.
*/
public static class Builder {

/**
* Utility constructor.
*/
private Builder() {
}

/**
* Creates new {@link MSetExArgs} and enable {@literal EX}.
*
* @param timeout expire time as duration.
* @return new {@link MSetExArgs} with {@literal EX} enabled.
* @see MSetExArgs#ex(long)
* @since 7.1
*/
public static MSetExArgs ex(Duration timeout) {
return new MSetExArgs().ex(timeout);
}

/**
* Creates new {@link MSetExArgs} and enable {@literal EXAT}.
*
* @param timestamp the timestamp type: posix time in seconds.
* @return new {@link MSetExArgs} with {@literal EXAT} enabled.
* @see MSetExArgs#exAt(Instant)
* @since 7.1
*/
public static MSetExArgs exAt(Instant timestamp) {
return new MSetExArgs().exAt(timestamp);
}

/**
* Creates new {@link MSetExArgs} and enable {@literal PX}.
*
* @param timeout expire time in milliseconds.
* @return new {@link MSetExArgs} with {@literal PX} enabled.
* @see MSetExArgs#px(long)
* @since 7.1
*/
public static MSetExArgs px(Duration timeout) {
return new MSetExArgs().px(timeout);
}

/**
* Creates new {@link MSetExArgs} and enable {@literal PXAT}.
*
* @param timestamp the timestamp type: posix time.
* @return new {@link MSetExArgs} with {@literal PXAT} enabled.
* @see MSetExArgs#pxAt(Instant)
* @since 7.1
*/
public static MSetExArgs pxAt(Instant timestamp) {
return new MSetExArgs().pxAt(timestamp);
}

/**
* Creates new {@link MSetExArgs} and enable {@literal NX}.
*
* @return new {@link MSetExArgs} with {@literal NX} enabled.
* @see MSetExArgs#nx()
* @since 7.1
*/
public static MSetExArgs nx() {
return new MSetExArgs().nx();
}

/**
* Creates new {@link MSetExArgs} and enable {@literal XX}.
*
* @return new {@link MSetExArgs} with {@literal XX} enabled.
* @see MSetExArgs#xx()
*/
public static MSetExArgs xx() {
return new MSetExArgs().xx();
}

/**
* Creates new {@link MSetExArgs} and enable {@literal KEEPTTL}.
*
* @return new {@link MSetExArgs} with {@literal KEEPTTL} enabled.
* @see MSetExArgs#keepttl()
* @since 7.1
*/
public static MSetExArgs keepttl() {
return new MSetExArgs().keepttl();
}

}

/**
* Set the specified expire time, in seconds.
*
* @param timeout expire time in seconds.
* @return {@code this} {@link MSetExArgs}.
* @since 7.1
*/
public MSetExArgs ex(Duration timeout) {

LettuceAssert.notNull(timeout, "Timeout must not be null");

this.ex = timeout.toMillis() / 1000;
return this;
}

/**
* Set the specified expire at time using a posix {@code timestamp}.
*
* @param timestamp the timestamp type: posix time in seconds.
* @return {@code this} {@link MSetExArgs}.
* @since 7.1
*/
public MSetExArgs exAt(Instant timestamp) {

LettuceAssert.notNull(timestamp, "Timestamp must not be null");

this.exAt = timestamp.toEpochMilli() / 1000;
return this;
}

/**
* Set the specified expire time, in milliseconds.
*
* @param timeout expire time in milliseconds.
* @return {@code this} {@link MSetExArgs}.
* @since 7.1
*/
public MSetExArgs px(Duration timeout) {

LettuceAssert.notNull(timeout, "Timeout must not be null");

this.px = timeout.toMillis();
return this;
}

/**
* Set the specified expire at time using a posix {@code timestamp}.
*
* @param timestamp the timestamp type: posix time in milliseconds.
* @return {@code this} {@link MSetExArgs}.
* @since 7.1
*/
public MSetExArgs pxAt(Instant timestamp) {

LettuceAssert.notNull(timestamp, "Timestamp must not be null");

this.pxAt = timestamp.toEpochMilli();
return this;
}

/**
* Only set the key if it does not already exist.
*
* @return {@code this} {@link MSetExArgs}.
* @since 7.1
*/
public MSetExArgs nx() {

this.nx = true;
return this;
}

/**
* Set the value and retain the existing TTL.
*
* @return {@code this} {@link MSetExArgs}.
* @since 7.1
*/
public MSetExArgs keepttl() {

this.keepttl = true;
return this;
}

/**
* Only set the key if it already exists.
*
* @return {@code this} {@link MSetExArgs}.
* @since 7.1
*/
public MSetExArgs xx() {

this.xx = true;
return this;
}

@Override
public <K, V> void build(CommandArgs<K, V> args) {

if (ex != null) {
args.add("EX").add(ex);
}

if (exAt != null) {
args.add("EXAT").add(exAt);
}

if (px != null) {
args.add("PX").add(px);
}

if (pxAt != null) {
args.add("PXAT").add(pxAt);
}

if (nx) {
args.add(NX);
}

if (xx) {
args.add(XX);
}

if (keepttl) {
args.add("KEEPTTL");
}
}

}
11 changes: 11 additions & 0 deletions src/main/java/io/lettuce/core/RedisCommandBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -2249,6 +2249,17 @@ Command<K, V, Boolean> msetnx(Map<K, V> map) {
return createCommand(MSETNX, new BooleanOutput<>(codec), args);
}

Command<K, V, Boolean> msetex(Map<K, V> map, MSetExArgs setArgs) {
LettuceAssert.notNull(map, "Map " + MUST_NOT_BE_NULL);
LettuceAssert.isTrue(!map.isEmpty(), "Map " + MUST_NOT_BE_EMPTY);

CommandArgs<K, V> args = new CommandArgs<>(codec).add(map.size()).add(map);
if (setArgs != null) {
setArgs.build(args);
}
return createCommand(MSETEX, new BooleanOutput<>(codec), args);
}

Command<K, V, String> multi() {
return createCommand(MULTI, new StatusOutput<>(codec));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
import io.lettuce.core.BitFieldArgs;
import io.lettuce.core.GetExArgs;
import io.lettuce.core.KeyValue;
import io.lettuce.core.MSetExArgs;
import io.lettuce.core.RedisFuture;
import io.lettuce.core.SetArgs;
import io.lettuce.core.LcsArgs;
import io.lettuce.core.StrAlgoArgs;
import io.lettuce.core.StringMatchResult;

import io.lettuce.core.output.KeyValueStreamingChannel;

/**
Expand Down Expand Up @@ -364,6 +366,17 @@ public interface RedisStringAsyncCommands<K, V> {
*/
RedisFuture<Boolean> msetnx(Map<K, V> map);

/**
* Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one
* of [EX|PX|EXAT|PXAT|KEEPTTL].
*
* @param map the map of keys and values.
* @param args the {@link MSetExArgs} specifying NX/XX and expiration.
* @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise.
* @since 7.1
*/
RedisFuture<Boolean> msetex(Map<K, V> map, MSetExArgs args);

/**
* Set the string value of a key.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import java.util.Map;

import io.lettuce.core.MSetExArgs;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import io.lettuce.core.BitFieldArgs;
Expand All @@ -31,6 +32,7 @@
import io.lettuce.core.LcsArgs;
import io.lettuce.core.StringMatchResult;
import io.lettuce.core.Value;

import io.lettuce.core.output.KeyValueStreamingChannel;

/**
Expand Down Expand Up @@ -368,6 +370,17 @@ public interface RedisStringReactiveCommands<K, V> {
*/
Mono<Boolean> msetnx(Map<K, V> map);

/**
* Set multiple keys to multiple values with optional conditions and expiration. Emits: numkeys, pairs, then [NX|XX] and one
* of [EX|PX|EXAT|PXAT|KEEPTTL].
*
* @param map the map of keys and values.
* @param args the {@link MSetExArgs} specifying NX/XX and expiration.
* @return Boolean from integer-reply: {@code 1} if all keys were set, {@code 0} otherwise.
* @since 7.1
*/
Mono<Boolean> msetex(Map<K, V> map, MSetExArgs args);

/**
* Set the string value of a key.
*
Expand Down
Loading
Loading