From f8b396e9aa5942625bd568759078223caecdaa41 Mon Sep 17 00:00:00 2001
From: "aleksandar.todorov"
Date: Thu, 6 Nov 2025 12:17:10 +0200
Subject: [PATCH 1/8] Add initial impl of CAS/CAD
---
.../core/AbstractRedisAsyncCommands.java | 35 +++++
.../core/AbstractRedisReactiveCommands.java | 35 +++++
.../io/lettuce/core/RedisCommandBuilder.java | 92 ++++++++++++
.../java/io/lettuce/core/ValueCondition.java | 132 ++++++++++++++++++
.../core/api/async/RedisKeyAsyncCommands.java | 36 ++++-
.../api/async/RedisStringAsyncCommands.java | 57 +++++++-
.../reactive/RedisKeyReactiveCommands.java | 26 +++-
.../reactive/RedisStringReactiveCommands.java | 59 +++++++-
.../core/api/sync/RedisKeyCommands.java | 24 +++-
.../core/api/sync/RedisStringCommands.java | 55 +++++++-
.../async/NodeSelectionKeyAsyncCommands.java | 24 +++-
.../NodeSelectionStringAsyncCommands.java | 55 +++++++-
.../api/sync/NodeSelectionKeyCommands.java | 24 +++-
.../api/sync/NodeSelectionStringCommands.java | 55 +++++++-
.../lettuce/core/protocol/CommandKeyword.java | 2 +-
.../io/lettuce/core/protocol/CommandType.java | 4 +-
.../core/protocol/ReadOnlyCommands.java | 2 +-
.../coroutines/RedisKeyCoroutinesCommands.kt | 38 ++++-
.../RedisKeyCoroutinesCommandsImpl.kt | 6 +
.../RedisStringCoroutinesCommands.kt | 59 +++++++-
.../RedisStringCoroutinesCommandsImpl.kt | 23 +++
.../io/lettuce/core/api/RedisKeyCommands.java | 35 ++++-
.../lettuce/core/api/RedisStringCommands.java | 61 +++++++-
23 files changed, 893 insertions(+), 46 deletions(-)
create mode 100644 src/main/java/io/lettuce/core/ValueCondition.java
diff --git a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
index 68245ab529..f1452671f3 100644
--- a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
@@ -741,6 +741,16 @@ public RedisFuture del(Iterable keys) {
return dispatch(commandBuilder.del(keys));
}
+ @Override
+ public RedisFuture delex(K key) {
+ return dispatch(commandBuilder.delex(key));
+ }
+
+ @Override
+ public RedisFuture delex(K key, ValueCondition condition) {
+ return dispatch(commandBuilder.delex(key, condition));
+ }
+
@Override
public String digest(String script) {
return digest(encodeScript(script));
@@ -1265,6 +1275,11 @@ public RedisFuture get(K key) {
return dispatch(commandBuilder.get(key));
}
+ @Override
+ public RedisFuture digestKey(K key) {
+ return dispatch(commandBuilder.digestKey(key));
+ }
+
public StatefulConnection getConnection() {
return connection;
}
@@ -2675,6 +2690,26 @@ public RedisFuture set(K key, V value, SetArgs setArgs) {
return dispatch(commandBuilder.set(key, value, setArgs));
}
+ @Override
+ public RedisFuture set(K key, V value, ValueCondition condition) {
+ return dispatch(commandBuilder.set(key, value, condition));
+ }
+
+ @Override
+ public RedisFuture set(K key, V value, SetArgs setArgs, ValueCondition condition) {
+ return dispatch(commandBuilder.set(key, value, setArgs, condition));
+ }
+
+ @Override
+ public RedisFuture setGet(K key, V value, ValueCondition condition) {
+ return dispatch(commandBuilder.setGet(key, value, condition));
+ }
+
+ @Override
+ public RedisFuture setGet(K key, V value, SetArgs setArgs, ValueCondition condition) {
+ return dispatch(commandBuilder.setGet(key, value, setArgs, condition));
+ }
+
@Override
public RedisFuture setGet(K key, V value) {
return dispatch(commandBuilder.setGet(key, value));
diff --git a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
index b0374474a7..43963f5ad1 100644
--- a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
@@ -811,6 +811,16 @@ public Mono del(Iterable keys) {
return createMono(() -> commandBuilder.del(keys));
}
+ @Override
+ public Mono delex(K key) {
+ return createMono(() -> commandBuilder.delex(key));
+ }
+
+ @Override
+ public Mono delex(K key, ValueCondition condition) {
+ return createMono(() -> commandBuilder.delex(key, condition));
+ }
+
@Override
public String digest(String script) {
return digest(encodeScript(script));
@@ -1326,6 +1336,11 @@ public Mono get(K key) {
return createMono(() -> commandBuilder.get(key));
}
+ @Override
+ public Mono digestKey(K key) {
+ return createMono(() -> commandBuilder.digestKey(key));
+ }
+
public StatefulConnection getConnection() {
return connection;
}
@@ -2758,6 +2773,26 @@ public Mono set(K key, V value, SetArgs setArgs) {
return createMono(() -> commandBuilder.set(key, value, setArgs));
}
+ @Override
+ public Mono set(K key, V value, ValueCondition condition) {
+ return createMono(() -> commandBuilder.set(key, value, condition));
+ }
+
+ @Override
+ public Mono set(K key, V value, SetArgs setArgs, ValueCondition condition) {
+ return createMono(() -> commandBuilder.set(key, value, setArgs, condition));
+ }
+
+ @Override
+ public Mono setGet(K key, V value, ValueCondition condition) {
+ return createMono(() -> commandBuilder.setGet(key, value, condition));
+ }
+
+ @Override
+ public Mono setGet(K key, V value, SetArgs setArgs, ValueCondition condition) {
+ return createMono(() -> commandBuilder.setGet(key, value, setArgs, condition));
+ }
+
@Override
public Mono setGet(K key, V value) {
return createMono(() -> commandBuilder.setGet(key, value));
diff --git a/src/main/java/io/lettuce/core/RedisCommandBuilder.java b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
index 7a4d44ec30..8ec63544a2 100644
--- a/src/main/java/io/lettuce/core/RedisCommandBuilder.java
+++ b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
@@ -940,6 +940,22 @@ Command del(Iterable keys) {
return createCommand(DEL, new IntegerOutput<>(codec), args);
}
+ Command delex(K key) {
+ notNullKey(key);
+
+ CommandArgs args = new CommandArgs<>(codec).addKey(key);
+ return createCommand(DELEX, new IntegerOutput<>(codec), args);
+ }
+
+ Command delex(K key, ValueCondition condition) {
+ notNullKey(key);
+ LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);
+
+ CommandArgs args = new CommandArgs<>(codec).addKey(key);
+ buildConditionArgs(args, condition);
+ return createCommand(DELEX, new IntegerOutput<>(codec), args);
+ }
+
Command discard() {
return createCommand(DISCARD, new StatusOutput<>(codec));
}
@@ -2702,6 +2718,55 @@ Command set(K key, V value, SetArgs setArgs) {
return createCommand(SET, new StatusOutput<>(codec), args);
}
+ Command digestKey(K key) {
+ notNullKey(key);
+
+ CommandArgs args = new CommandArgs<>(codec).addKey(key);
+ return createCommand(DIGEST, new StatusOutput<>(codec), args);
+ }
+
+ Command set(K key, V value, ValueCondition condition) {
+ notNullKey(key);
+ LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);
+
+ CommandArgs args = new CommandArgs<>(codec).addKey(key).addValue(value);
+ buildConditionArgs(args, condition);
+ return createCommand(SET, new StatusOutput<>(codec), args);
+ }
+
+ Command set(K key, V value, SetArgs setArgs, ValueCondition condition) {
+ notNullKey(key);
+ LettuceAssert.notNull(setArgs, "SetArgs " + MUST_NOT_BE_NULL);
+ LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);
+
+ CommandArgs args = new CommandArgs<>(codec).addKey(key).addValue(value);
+ setArgs.build(args);
+ buildConditionArgs(args, condition);
+ return createCommand(SET, new StatusOutput<>(codec), args);
+ }
+
+ Command setGet(K key, V value, ValueCondition condition) {
+ notNullKey(key);
+ LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);
+
+ CommandArgs args = new CommandArgs<>(codec).addKey(key).addValue(value);
+ buildConditionArgs(args, condition);
+ args.add(GET);
+ return createCommand(SET, new ValueOutput<>(codec), args);
+ }
+
+ Command setGet(K key, V value, SetArgs setArgs, ValueCondition condition) {
+ notNullKey(key);
+ LettuceAssert.notNull(setArgs, "SetArgs " + MUST_NOT_BE_NULL);
+ LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);
+
+ CommandArgs args = new CommandArgs<>(codec).addKey(key).addValue(value);
+ setArgs.build(args);
+ buildConditionArgs(args, condition);
+ args.add(GET);
+ return createCommand(SET, new ValueOutput<>(codec), args);
+ }
+
Command setGet(K key, V value) {
return setGet(key, value, new SetArgs());
}
@@ -4564,6 +4629,33 @@ Command zscanStreaming(ScoredValueStreamingChannel ch
return zscanStreaming(channel, key, ScanCursor.INITIAL, scanArgs);
}
+ private void buildConditionArgs(CommandArgs args, ValueCondition condition) {
+ switch (condition.kind()) {
+ case EXISTS:
+ args.add(XX);
+ break;
+ case NOT_EXISTS:
+ args.add(NX);
+ break;
+ case EQUAL:
+ args.add(IFEQ).addValue(condition.value());
+ break;
+ case NOT_EQUAL:
+ args.add(IFNE).addValue(condition.value());
+ break;
+ case DIGEST_EQUAL:
+ args.add(IFDEQ).add(condition.digestHex());
+ break;
+ case DIGEST_NOT_EQUAL:
+ args.add(IFDNE).add(condition.digestHex());
+ break;
+ case ALWAYS:
+ default:
+ // no additional args
+ break;
+ }
+ }
+
Command zscanStreaming(ScoredValueStreamingChannel channel, K key, ScanCursor scanCursor,
ScanArgs scanArgs) {
notNullKey(key);
diff --git a/src/main/java/io/lettuce/core/ValueCondition.java b/src/main/java/io/lettuce/core/ValueCondition.java
new file mode 100644
index 0000000000..b367f421a0
--- /dev/null
+++ b/src/main/java/io/lettuce/core/ValueCondition.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2017-Present, Redis Ltd. and Contributors
+ * All rights reserved.
+ *
+ * Licensed under the MIT License.
+ *
+ * This file contains contributions from third-party contributors
+ * licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.lettuce.core;
+
+/**
+ * A compare condition to be used with commands that support conditional value checks (e.g. SET with IFEQ/IFNE/IFDEQ/IFDNE and
+ * DELEX). This abstraction lets callers express value-based or digest-based comparisons without exploding method overloads.
+ *
+ *
+ * Digest-based comparisons use a 64-bit XXH3 digest represented as a 16-character lower-case hexadecimal string.
+ *
+ *
+ * @param value type used for value-based comparisons
+ * @since 8.x
+ */
+public final class ValueCondition {
+
+ /**
+ * The kind of condition represented by this instance.
+ */
+ public enum Kind {
+ /** unconditional */
+ ALWAYS,
+ /** key must exist */
+ EXISTS,
+ /** key must not exist */
+ NOT_EXISTS,
+ /** current value must equal provided value */
+ EQUAL,
+ /** current value must not equal provided value */
+ NOT_EQUAL,
+ /** current value's digest must equal provided digest */
+ DIGEST_EQUAL,
+ /** current value's digest must not equal provided digest */
+ DIGEST_NOT_EQUAL
+ }
+
+ private final Kind kind;
+
+ private final V value; // used for EQUAL/NOT_EQUAL
+
+ private final String digestHex; // used for DIGEST_EQUAL/DIGEST_NOT_EQUAL
+
+ private ValueCondition(Kind kind, V value, String digestHex) {
+ this.kind = kind;
+ this.value = value;
+ this.digestHex = digestHex;
+ }
+
+ /** A condition that always applies (no comparison). */
+ public static ValueCondition always() {
+ return new ValueCondition<>(Kind.ALWAYS, null, null);
+
+ }
+
+ /** A condition that requires the key to exist (equivalent to XX in SET). */
+ public static ValueCondition exists() {
+ return new ValueCondition<>(Kind.EXISTS, null, null);
+ }
+
+ /** A condition that requires the key to not exist (equivalent to NX in SET). */
+ public static ValueCondition notExists() {
+ return new ValueCondition<>(Kind.NOT_EXISTS, null, null);
+ }
+
+ /** A value-based comparison: set/delete only if the current value equals {@code value}. */
+ public static ValueCondition equal(V value) {
+ if (value == null)
+ throw new IllegalArgumentException("value must not be null");
+ return new ValueCondition<>(Kind.EQUAL, value, null);
+ }
+
+ /** A value-based comparison: set/delete only if the current value does not equal {@code value}. */
+ public static ValueCondition notEqual(V value) {
+ if (value == null)
+ throw new IllegalArgumentException("value must not be null");
+ return new ValueCondition<>(Kind.NOT_EQUAL, value, null);
+ }
+
+ /** A digest-based comparison: set/delete only if the current value's digest equals {@code hex16Digest}. */
+ public static ValueCondition digestEqualHex(String hex16Digest) {
+ if (hex16Digest == null)
+ throw new IllegalArgumentException("digest must not be null");
+ return new ValueCondition<>(Kind.DIGEST_EQUAL, null, hex16Digest);
+ }
+
+ /** A digest-based comparison: set/delete only if the current value's digest does not equal {@code hex16Digest}. */
+ public static ValueCondition digestNotEqualHex(String hex16Digest) {
+ if (hex16Digest == null)
+ throw new IllegalArgumentException("digest must not be null");
+ return new ValueCondition<>(Kind.DIGEST_NOT_EQUAL, null, hex16Digest);
+ }
+
+ /** The kind of this condition. */
+ public Kind kind() {
+ return kind;
+ }
+
+ /** The value for value-based comparisons, or {@code null}. */
+ public V value() {
+ return value;
+ }
+
+ /** The 16-character lower-case hex digest for digest-based comparisons, or {@code null}. */
+ public String digestHex() {
+ return digestHex;
+ }
+
+ @Override
+ public String toString() {
+ return "ValueCondition{" + "kind=" + kind + (value != null ? ", value=" + value : "")
+ + (digestHex != null ? ", digestHex='" + digestHex + '\'' : "") + '}';
+ }
+
+}
diff --git a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
index e5eab6dfbd..c66ddd68e6 100644
--- a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
@@ -19,12 +19,23 @@
*/
package io.lettuce.core.api.async;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
import java.util.List;
+import java.util.Date;
+import java.time.Instant;
+import java.time.Duration;
-import io.lettuce.core.*;
+import io.lettuce.core.CopyArgs;
+import io.lettuce.core.ExpireArgs;
+import io.lettuce.core.KeyScanArgs;
+import io.lettuce.core.KeyScanCursor;
+import io.lettuce.core.MigrateArgs;
+import io.lettuce.core.RestoreArgs;
+import io.lettuce.core.ScanArgs;
+import io.lettuce.core.ScanCursor;
+import io.lettuce.core.SortArgs;
+import io.lettuce.core.StreamScanCursor;
+import io.lettuce.core.ValueCondition;
+import io.lettuce.core.RedisFuture;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;
@@ -68,6 +79,23 @@ public interface RedisKeyAsyncCommands {
*/
RedisFuture del(K... keys);
+ /**
+ * Delete the specified key conditionally.
+ *
+ * @param key the key.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ RedisFuture delex(K key);
+
+ /**
+ * Delete the specified key if the compare condition matches.
+ *
+ * @param key the key.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ RedisFuture delex(K key, ValueCondition condition);
+
/**
* Unlink one or more keys (non blocking DEL).
*
diff --git a/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java
index bcacfa731e..e6a2677c5e 100644
--- a/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java
@@ -19,18 +19,19 @@
*/
package io.lettuce.core.api.async;
-import java.util.List;
import java.util.Map;
+import java.util.List;
import io.lettuce.core.BitFieldArgs;
import io.lettuce.core.GetExArgs;
import io.lettuce.core.KeyValue;
-import io.lettuce.core.RedisFuture;
-import io.lettuce.core.SetArgs;
import io.lettuce.core.LcsArgs;
+import io.lettuce.core.SetArgs;
import io.lettuce.core.StrAlgoArgs;
import io.lettuce.core.StringMatchResult;
+import io.lettuce.core.ValueCondition;
import io.lettuce.core.output.KeyValueStreamingChannel;
+import io.lettuce.core.RedisFuture;
/**
* Asynchronous executed commands for Strings.
@@ -256,6 +257,14 @@ public interface RedisStringAsyncCommands {
*/
RedisFuture get(K key);
+ /**
+ * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string.
+ *
+ * @param key the key.
+ * @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist.
+ */
+ RedisFuture digestKey(K key);
+
/**
* Returns the bit value at offset in the string value stored at key.
*
@@ -373,6 +382,48 @@ public interface RedisStringAsyncCommands {
*/
RedisFuture set(K key, V value);
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
+ */
+ RedisFuture set(K key, V value, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the setArgs.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
+ */
+ RedisFuture set(K key, V value, SetArgs setArgs, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
+ */
+ RedisFuture setGet(K key, V value, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the command arguments.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
+ */
+ RedisFuture setGet(K key, V value, SetArgs setArgs, ValueCondition condition);
+
/**
* Set the string value of a key.
*
diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
index 8fad8dde43..8f335b1c04 100644
--- a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
@@ -19,12 +19,10 @@
*/
package io.lettuce.core.api.reactive;
-import java.time.Duration;
-import java.time.Instant;
import java.util.Date;
+import java.time.Instant;
+import java.time.Duration;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
import io.lettuce.core.CopyArgs;
import io.lettuce.core.ExpireArgs;
import io.lettuce.core.KeyScanArgs;
@@ -35,8 +33,11 @@
import io.lettuce.core.ScanCursor;
import io.lettuce.core.SortArgs;
import io.lettuce.core.StreamScanCursor;
+import io.lettuce.core.ValueCondition;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
/**
* Reactive executed commands for Keys (Key manipulation/querying).
@@ -78,6 +79,23 @@ public interface RedisKeyReactiveCommands {
*/
Mono del(K... keys);
+ /**
+ * Delete the specified key conditionally.
+ *
+ * @param key the key.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ Mono delex(K key);
+
+ /**
+ * Delete the specified key if the compare condition matches.
+ *
+ * @param key the key.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ Mono delex(K key, ValueCondition condition);
+
/**
* Unlink one or more keys (non blocking DEL).
*
diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java
index 60e2d5294c..81a5b58443 100644
--- a/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java
@@ -21,17 +21,18 @@
import java.util.Map;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
import io.lettuce.core.BitFieldArgs;
import io.lettuce.core.GetExArgs;
import io.lettuce.core.KeyValue;
+import io.lettuce.core.LcsArgs;
import io.lettuce.core.SetArgs;
import io.lettuce.core.StrAlgoArgs;
-import io.lettuce.core.LcsArgs;
import io.lettuce.core.StringMatchResult;
-import io.lettuce.core.Value;
+import io.lettuce.core.ValueCondition;
import io.lettuce.core.output.KeyValueStreamingChannel;
+import io.lettuce.core.Value;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
/**
* Reactive executed commands for Strings.
@@ -257,6 +258,14 @@ public interface RedisStringReactiveCommands {
*/
Mono get(K key);
+ /**
+ * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string.
+ *
+ * @param key the key.
+ * @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist.
+ */
+ Mono digestKey(K key);
+
/**
* Returns the bit value at offset in the string value stored at key.
*
@@ -377,6 +386,48 @@ public interface RedisStringReactiveCommands {
*/
Mono set(K key, V value);
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
+ */
+ Mono set(K key, V value, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the setArgs.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
+ */
+ Mono set(K key, V value, SetArgs setArgs, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
+ */
+ Mono setGet(K key, V value, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the command arguments.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
+ */
+ Mono setGet(K key, V value, SetArgs setArgs, ValueCondition condition);
+
/**
* Set the string value of a key.
*
diff --git a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
index 777dc966f5..f9a5a3b980 100644
--- a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
+++ b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
@@ -19,10 +19,10 @@
*/
package io.lettuce.core.api.sync;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
import java.util.List;
+import java.util.Date;
+import java.time.Instant;
+import java.time.Duration;
import io.lettuce.core.CopyArgs;
import io.lettuce.core.ExpireArgs;
@@ -34,6 +34,7 @@
import io.lettuce.core.ScanCursor;
import io.lettuce.core.SortArgs;
import io.lettuce.core.StreamScanCursor;
+import io.lettuce.core.ValueCondition;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;
@@ -77,6 +78,23 @@ public interface RedisKeyCommands {
*/
Long del(K... keys);
+ /**
+ * Delete the specified key conditionally.
+ *
+ * @param key the key.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ Long delex(K key);
+
+ /**
+ * Delete the specified key if the compare condition matches.
+ *
+ * @param key the key.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ Long delex(K key, ValueCondition condition);
+
/**
* Unlink one or more keys (non blocking DEL).
*
diff --git a/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java
index bae38567b8..651215bc91 100644
--- a/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java
+++ b/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java
@@ -19,16 +19,17 @@
*/
package io.lettuce.core.api.sync;
-import java.util.List;
import java.util.Map;
+import java.util.List;
import io.lettuce.core.BitFieldArgs;
import io.lettuce.core.GetExArgs;
import io.lettuce.core.KeyValue;
+import io.lettuce.core.LcsArgs;
import io.lettuce.core.SetArgs;
import io.lettuce.core.StrAlgoArgs;
-import io.lettuce.core.LcsArgs;
import io.lettuce.core.StringMatchResult;
+import io.lettuce.core.ValueCondition;
import io.lettuce.core.output.KeyValueStreamingChannel;
/**
@@ -255,6 +256,14 @@ public interface RedisStringCommands {
*/
V get(K key);
+ /**
+ * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string.
+ *
+ * @param key the key.
+ * @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist.
+ */
+ String digestKey(K key);
+
/**
* Returns the bit value at offset in the string value stored at key.
*
@@ -372,6 +381,48 @@ public interface RedisStringCommands {
*/
String set(K key, V value);
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
+ */
+ String set(K key, V value, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the setArgs.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
+ */
+ String set(K key, V value, SetArgs setArgs, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
+ */
+ V setGet(K key, V value, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the command arguments.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
+ */
+ V setGet(K key, V value, SetArgs setArgs, ValueCondition condition);
+
/**
* Set the string value of a key.
*
diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
index 7233e5ae9d..c224ad93c1 100644
--- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
@@ -19,10 +19,10 @@
*/
package io.lettuce.core.cluster.api.async;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
import java.util.List;
+import java.util.Date;
+import java.time.Instant;
+import java.time.Duration;
import io.lettuce.core.CopyArgs;
import io.lettuce.core.ExpireArgs;
@@ -34,6 +34,7 @@
import io.lettuce.core.ScanCursor;
import io.lettuce.core.SortArgs;
import io.lettuce.core.StreamScanCursor;
+import io.lettuce.core.ValueCondition;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;
@@ -77,6 +78,23 @@ public interface NodeSelectionKeyAsyncCommands {
*/
AsyncExecutions del(K... keys);
+ /**
+ * Delete the specified key conditionally.
+ *
+ * @param key the key.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ AsyncExecutions delex(K key);
+
+ /**
+ * Delete the specified key if the compare condition matches.
+ *
+ * @param key the key.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ AsyncExecutions delex(K key, ValueCondition condition);
+
/**
* Unlink one or more keys (non blocking DEL).
*
diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionStringAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionStringAsyncCommands.java
index 44672f3810..a6dd197ba9 100644
--- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionStringAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionStringAsyncCommands.java
@@ -19,16 +19,17 @@
*/
package io.lettuce.core.cluster.api.async;
-import java.util.List;
import java.util.Map;
+import java.util.List;
import io.lettuce.core.BitFieldArgs;
import io.lettuce.core.GetExArgs;
import io.lettuce.core.KeyValue;
+import io.lettuce.core.LcsArgs;
import io.lettuce.core.SetArgs;
import io.lettuce.core.StrAlgoArgs;
import io.lettuce.core.StringMatchResult;
-import io.lettuce.core.LcsArgs;
+import io.lettuce.core.ValueCondition;
import io.lettuce.core.output.KeyValueStreamingChannel;
/**
@@ -255,6 +256,14 @@ public interface NodeSelectionStringAsyncCommands {
*/
AsyncExecutions get(K key);
+ /**
+ * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string.
+ *
+ * @param key the key.
+ * @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist.
+ */
+ AsyncExecutions digestKey(K key);
+
/**
* Returns the bit value at offset in the string value stored at key.
*
@@ -372,6 +381,48 @@ public interface NodeSelectionStringAsyncCommands {
*/
AsyncExecutions set(K key, V value);
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
+ */
+ AsyncExecutions set(K key, V value, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the setArgs.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
+ */
+ AsyncExecutions set(K key, V value, SetArgs setArgs, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
+ */
+ AsyncExecutions setGet(K key, V value, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the command arguments.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
+ */
+ AsyncExecutions setGet(K key, V value, SetArgs setArgs, ValueCondition condition);
+
/**
* Set the string value of a key.
*
diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
index 663a8aeadb..6e6b7c8c45 100644
--- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
@@ -19,10 +19,10 @@
*/
package io.lettuce.core.cluster.api.sync;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
import java.util.List;
+import java.util.Date;
+import java.time.Instant;
+import java.time.Duration;
import io.lettuce.core.CopyArgs;
import io.lettuce.core.ExpireArgs;
@@ -34,6 +34,7 @@
import io.lettuce.core.ScanCursor;
import io.lettuce.core.SortArgs;
import io.lettuce.core.StreamScanCursor;
+import io.lettuce.core.ValueCondition;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;
@@ -77,6 +78,23 @@ public interface NodeSelectionKeyCommands {
*/
Executions del(K... keys);
+ /**
+ * Delete the specified key conditionally.
+ *
+ * @param key the key.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ Executions delex(K key);
+
+ /**
+ * Delete the specified key if the compare condition matches.
+ *
+ * @param key the key.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ Executions delex(K key, ValueCondition condition);
+
/**
* Unlink one or more keys (non blocking DEL).
*
diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionStringCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionStringCommands.java
index d52a332d55..d9136b99eb 100644
--- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionStringCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionStringCommands.java
@@ -19,16 +19,17 @@
*/
package io.lettuce.core.cluster.api.sync;
-import java.util.List;
import java.util.Map;
+import java.util.List;
import io.lettuce.core.BitFieldArgs;
import io.lettuce.core.GetExArgs;
import io.lettuce.core.KeyValue;
+import io.lettuce.core.LcsArgs;
import io.lettuce.core.SetArgs;
import io.lettuce.core.StrAlgoArgs;
import io.lettuce.core.StringMatchResult;
-import io.lettuce.core.LcsArgs;
+import io.lettuce.core.ValueCondition;
import io.lettuce.core.output.KeyValueStreamingChannel;
/**
@@ -255,6 +256,14 @@ public interface NodeSelectionStringCommands {
*/
Executions get(K key);
+ /**
+ * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string.
+ *
+ * @param key the key.
+ * @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist.
+ */
+ Executions digestKey(K key);
+
/**
* Returns the bit value at offset in the string value stored at key.
*
@@ -372,6 +381,48 @@ public interface NodeSelectionStringCommands {
*/
Executions set(K key, V value);
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
+ */
+ Executions set(K key, V value, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the setArgs.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
+ */
+ Executions set(K key, V value, SetArgs setArgs, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
+ */
+ Executions setGet(K key, V value, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the command arguments.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
+ */
+ Executions setGet(K key, V value, SetArgs setArgs, ValueCondition condition);
+
/**
* Set the string value of a key.
*
diff --git a/src/main/java/io/lettuce/core/protocol/CommandKeyword.java b/src/main/java/io/lettuce/core/protocol/CommandKeyword.java
index b25e15f785..028fc98503 100644
--- a/src/main/java/io/lettuce/core/protocol/CommandKeyword.java
+++ b/src/main/java/io/lettuce/core/protocol/CommandKeyword.java
@@ -58,7 +58,7 @@ CAS, EF, ELE, SETATTR, M, NOQUANT, BIN, Q8, FILTER, FILTER_EF(
NOSTEM, PHONETIC, WEIGHT, SEPARATOR, CASESENSITIVE, WITHSUFFIXTRIE, INDEXEMPTY, INDEXMISSING, DD, SORTBY, WITHCOUNT, SUMMARIZE, FRAGS, HIGHLIGHT, TAGS, DIALECT, PARAMS, TIMEOUT, SLOP, EXPLAINSCORE, PAYLOAD,
- SCORER, EXPANDER, INORDER, RETURN, INFIELDS, INKEYS, WITHSORTKEYS, WITHPAYLOADS, NOSTOPWORDS, VERBATIM, NOCONTENT, FLAT, SPHERICAL, HNSW, DIM, DISTANCE_METRIC, FLOAT32, FLOAT64, L2, COSINE, IP, WITHCURSOR, MAXIDLE, ADDSCORES, GROUPBY, APPLY, READ, DEL, TERMS, DISTANCE;
+ SCORER, EXPANDER, INORDER, RETURN, INFIELDS, INKEYS, WITHSORTKEYS, WITHPAYLOADS, NOSTOPWORDS, VERBATIM, NOCONTENT, FLAT, SPHERICAL, HNSW, DIM, DISTANCE_METRIC, FLOAT32, FLOAT64, L2, COSINE, IP, WITHCURSOR, MAXIDLE, ADDSCORES, GROUPBY, APPLY, READ, DEL, TERMS, DISTANCE, IFEQ, IFNE, IFDEQ, IFDNE;
public final byte[] bytes;
diff --git a/src/main/java/io/lettuce/core/protocol/CommandType.java b/src/main/java/io/lettuce/core/protocol/CommandType.java
index a6be8b4ee7..d24bfb8d74 100644
--- a/src/main/java/io/lettuce/core/protocol/CommandType.java
+++ b/src/main/java/io/lettuce/core/protocol/CommandType.java
@@ -46,11 +46,11 @@ public enum CommandType implements ProtocolKeyword {
// Keys
- COPY, DEL, DUMP, EXISTS, HEXPIRE, EXPIRE, HEXPIREAT, EXPIREAT, HEXPIRETIME, EXPIRETIME, KEYS, MIGRATE, MOVE, OBJECT, HPERSIST, PERSIST, PEXPIRE, HPEXPIRE, PEXPIREAT, HPEXPIREAT, PEXPIRETIME, HPEXPIRETIME, PTTL, HPTTL, RANDOMKEY, RENAME, RENAMENX, RESTORE, TOUCH, TTL, HTTL, TYPE, SCAN, UNLINK,
+ COPY, DEL, DELEX, DUMP, EXISTS, HEXPIRE, EXPIRE, HEXPIREAT, EXPIREAT, HEXPIRETIME, EXPIRETIME, KEYS, MIGRATE, MOVE, OBJECT, HPERSIST, PERSIST, PEXPIRE, HPEXPIRE, PEXPIREAT, HPEXPIREAT, PEXPIRETIME, HPEXPIRETIME, PTTL, HPTTL, RANDOMKEY, RENAME, RENAMENX, RESTORE, TOUCH, TTL, HTTL, TYPE, SCAN, UNLINK,
// String
- APPEND, GET, GETDEL, GETEX, GETRANGE, GETSET, MGET, MSET, MSETNX, SET, SETEX, PSETEX, SETNX, SETRANGE, STRLEN, STRALGO, LCS,
+ APPEND, GET, GETDEL, GETEX, GETRANGE, GETSET, DIGEST, MGET, MSET, MSETNX, SET, SETEX, PSETEX, SETNX, SETRANGE, STRLEN, STRALGO, LCS,
// Numeric
diff --git a/src/main/java/io/lettuce/core/protocol/ReadOnlyCommands.java b/src/main/java/io/lettuce/core/protocol/ReadOnlyCommands.java
index 46d2a6650c..62515e8ada 100644
--- a/src/main/java/io/lettuce/core/protocol/ReadOnlyCommands.java
+++ b/src/main/java/io/lettuce/core/protocol/ReadOnlyCommands.java
@@ -72,7 +72,7 @@ public static ReadOnlyPredicate asPredicate() {
enum CommandName {
ASKING, BITCOUNT, BITPOS, CLIENT, COMMAND, DUMP, ECHO, EVAL_RO, EVALSHA_RO, EXISTS, FCALL_RO, //
- GEODIST, GEOPOS, GEORADIUS_RO, GEORADIUSBYMEMBER_RO, GEOSEARCH, GEOHASH, GET, GETBIT, //
+ GEODIST, GEOPOS, GEORADIUS_RO, GEORADIUSBYMEMBER_RO, GEOSEARCH, GEOHASH, GET, DIGEST, GETBIT, //
GETRANGE, HEXISTS, HGET, HGETALL, HKEYS, HLEN, HMGET, HRANDFIELD, HSCAN, HSTRLEN, //
HVALS, INFO, KEYS, LINDEX, LLEN, LPOS, LRANGE, SORT_RO, MGET, PFCOUNT, PTTL, //
RANDOMKEY, READWRITE, SCAN, SCARD, SCRIPT, //
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
index acd55b4d40..8346e10079 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2020-Present, Redis Ltd. and Contributors
+ * Copyright 2017-Present, Redis Ltd. and Contributors
* All rights reserved.
*
* Licensed under the MIT License.
@@ -20,11 +20,22 @@
package io.lettuce.core.api.coroutines
-import io.lettuce.core.*
-import kotlinx.coroutines.flow.Flow
-import java.time.Duration
+import java.util.Date
import java.time.Instant
-import java.util.*
+import java.time.Duration
+
+import io.lettuce.core.CopyArgs
+import io.lettuce.core.ExpireArgs
+import io.lettuce.core.KeyScanArgs
+import io.lettuce.core.KeyScanCursor
+import io.lettuce.core.MigrateArgs
+import io.lettuce.core.RestoreArgs
+import io.lettuce.core.ScanArgs
+import io.lettuce.core.ScanCursor
+import io.lettuce.core.SortArgs
+import io.lettuce.core.ValueCondition
+import io.lettuce.core.ExperimentalLettuceCoroutinesApi
+import kotlinx.coroutines.flow.Flow
/**
* Coroutine executed commands for Keys (Key manipulation/querying).
@@ -67,6 +78,23 @@ interface RedisKeyCoroutinesCommands {
*/
suspend fun del(vararg keys: K): Long?
+ /**
+ * Delete the specified key conditionally.
+ *
+ * @param key the key.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ suspend fun delex(key: K): Long?
+
+ /**
+ * Delete the specified key if the compare condition matches.
+ *
+ * @param key the key.
+ * @param condition the compare condition, must not be `null`.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ suspend fun delex(key: K, condition: ValueCondition): Long?
+
/**
* Unlink one or more keys (non blocking DEL).
*
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt
index 5195e705e0..62feab2046 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt
@@ -52,6 +52,12 @@ internal class RedisKeyCoroutinesCommandsImpl(internal val ops
override suspend fun unlink(vararg keys: K): Long? =
ops.unlink(*keys).awaitFirstOrNull()
+ override suspend fun delex(key: K): Long? =
+ ops.delex(key).awaitFirstOrNull()
+
+ override suspend fun delex(key: K, condition: ValueCondition): Long? =
+ ops.delex(key, condition).awaitFirstOrNull()
+
override suspend fun dump(key: K): ByteArray? = ops.dump(key).awaitFirstOrNull()
override suspend fun exists(vararg keys: K): Long? =
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt
index f6500da836..1911043327 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt
@@ -20,7 +20,14 @@
package io.lettuce.core.api.coroutines
-import io.lettuce.core.*
+import io.lettuce.core.BitFieldArgs
+import io.lettuce.core.GetExArgs
+import io.lettuce.core.KeyValue
+import io.lettuce.core.LcsArgs
+import io.lettuce.core.SetArgs
+import io.lettuce.core.StringMatchResult
+import io.lettuce.core.ValueCondition
+import io.lettuce.core.ExperimentalLettuceCoroutinesApi
import kotlinx.coroutines.flow.Flow
/**
@@ -201,6 +208,14 @@ interface RedisStringCoroutinesCommands {
*/
suspend fun get(key: K): V?
+ /**
+ * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string.
+ *
+ * @param key the key.
+ * @return String bulk-string-reply the hex digest of the key's value, or `null` when `key` does not exist.
+ */
+ suspend fun digestKey(key: K): String?
+
/**
* Returns the bit value at offset in the string value stored at key.
*
@@ -309,6 +324,48 @@ interface RedisStringCoroutinesCommands {
*/
suspend fun set(key: K, value: V): String?
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be `null`.
+ * @return String simple-string-reply `OK` if `SET` was executed; `null` if the operation was aborted.
+ */
+ suspend fun set(key: K, value: V, condition: ValueCondition): String?
+
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the setArgs.
+ * @param condition the compare condition, must not be `null`.
+ * @return String simple-string-reply `OK` if `SET` was executed; `null` if the operation was aborted.
+ */
+ suspend fun set(key: K, value: V, setArgs: SetArgs, condition: ValueCondition): String?
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be `null`.
+ * @return V bulk-string-reply the previous value if the key existed, or `null` when `key` did not exist.
+ */
+ suspend fun setGet(key: K, value: V, condition: ValueCondition): V?
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the command arguments.
+ * @param condition the compare condition, must not be `null`.
+ * @return V bulk-string-reply the previous value if the key existed, or `null` when `key` did not exist.
+ */
+ suspend fun setGet(key: K, value: V, setArgs: SetArgs, condition: ValueCondition): V?
+
/**
* Set the string value of a key.
*
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommandsImpl.kt
index 8bfbb56d4d..45c0507150 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommandsImpl.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommandsImpl.kt
@@ -70,6 +70,9 @@ internal class RedisStringCoroutinesCommandsImpl(internal val
override suspend fun get(key: K): V? = ops.get(key).awaitFirstOrNull()
+ override suspend fun digestKey(key: K): String? =
+ ops.digestKey(key).awaitFirstOrNull()
+
override suspend fun getbit(key: K, offset: Long): Long? =
ops.getbit(key, offset).awaitFirstOrNull()
@@ -98,6 +101,26 @@ internal class RedisStringCoroutinesCommandsImpl(internal val
override suspend fun msetnx(map: Map): Boolean? = ops.msetnx(map).awaitFirstOrNull()
+ override suspend fun set(key: K, value: V, condition: ValueCondition): String? =
+ ops.set(key, value, condition).awaitFirstOrNull()
+
+ override suspend fun set(
+ key: K,
+ value: V,
+ setArgs: SetArgs,
+ condition: ValueCondition
+ ): String? = ops.set(key, value, setArgs, condition).awaitFirstOrNull()
+
+ override suspend fun setGet(key: K, value: V, condition: ValueCondition): V? =
+ ops.setGet(key, value, condition).awaitFirstOrNull()
+
+ override suspend fun setGet(
+ key: K,
+ value: V,
+ setArgs: SetArgs,
+ condition: ValueCondition
+ ): V? = ops.setGet(key, value, setArgs, condition).awaitFirstOrNull()
+
override suspend fun set(key: K, value: V): String? = ops.set(key, value).awaitFirstOrNull()
override suspend fun set(key: K, value: V, setArgs: SetArgs): String? = ops.set(key, value, setArgs).awaitFirstOrNull()
diff --git a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
index 81217833fb..cb8706e872 100644
--- a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
+++ b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
@@ -19,12 +19,22 @@
*/
package io.lettuce.core.api;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
import java.util.List;
+import java.util.Date;
+import java.time.Instant;
+import java.time.Duration;
-import io.lettuce.core.*;
+import io.lettuce.core.CopyArgs;
+import io.lettuce.core.ExpireArgs;
+import io.lettuce.core.KeyScanArgs;
+import io.lettuce.core.KeyScanCursor;
+import io.lettuce.core.MigrateArgs;
+import io.lettuce.core.RestoreArgs;
+import io.lettuce.core.ScanArgs;
+import io.lettuce.core.ScanCursor;
+import io.lettuce.core.SortArgs;
+import io.lettuce.core.StreamScanCursor;
+import io.lettuce.core.ValueCondition;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;
@@ -67,6 +77,23 @@ public interface RedisKeyCommands {
*/
Long del(K... keys);
+ /**
+ * Delete the specified key conditionally.
+ *
+ * @param key the key.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ Long delex(K key);
+
+ /**
+ * Delete the specified key if the compare condition matches.
+ *
+ * @param key the key.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return Long integer-reply the number of keys that were removed.
+ */
+ Long delex(K key, ValueCondition condition);
+
/**
* Unlink one or more keys (non blocking DEL).
*
diff --git a/src/main/templates/io/lettuce/core/api/RedisStringCommands.java b/src/main/templates/io/lettuce/core/api/RedisStringCommands.java
index d4530aa886..ab88715adf 100644
--- a/src/main/templates/io/lettuce/core/api/RedisStringCommands.java
+++ b/src/main/templates/io/lettuce/core/api/RedisStringCommands.java
@@ -19,10 +19,17 @@
*/
package io.lettuce.core.api;
-import java.util.List;
import java.util.Map;
+import java.util.List;
-import io.lettuce.core.*;
+import io.lettuce.core.BitFieldArgs;
+import io.lettuce.core.GetExArgs;
+import io.lettuce.core.KeyValue;
+import io.lettuce.core.LcsArgs;
+import io.lettuce.core.SetArgs;
+import io.lettuce.core.StrAlgoArgs;
+import io.lettuce.core.StringMatchResult;
+import io.lettuce.core.ValueCondition;
import io.lettuce.core.output.KeyValueStreamingChannel;
/**
@@ -248,6 +255,14 @@ public interface RedisStringCommands {
*/
V get(K key);
+ /**
+ * Return the XXH3 64-bit digest of the string value stored at a key as a 16-character hex string.
+ *
+ * @param key the key.
+ * @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist.
+ */
+ String digestKey(K key);
+
/**
* Returns the bit value at offset in the string value stored at key.
*
@@ -365,6 +380,48 @@ public interface RedisStringCommands {
*/
String set(K key, V value);
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
+ */
+ String set(K key, V value, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the setArgs.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
+ */
+ String set(K key, V value, SetArgs setArgs, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
+ */
+ V setGet(K key, V value, ValueCondition condition);
+
+ /**
+ * Set the string value of a key with a compare condition and return its old value.
+ *
+ * @param key the key.
+ * @param value the value.
+ * @param setArgs the command arguments.
+ * @param condition the compare condition, must not be {@code null}.
+ * @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
+ */
+ V setGet(K key, V value, SetArgs setArgs, ValueCondition condition);
+
/**
* Set the string value of a key.
*
From 2f1c65285c01c690536f802f70fa30d8982c1ab4 Mon Sep 17 00:00:00 2001
From: "aleksandar.todorov"
Date: Thu, 6 Nov 2025 12:20:36 +0200
Subject: [PATCH 2/8] Add tests
---
.../KeyClusterCommandIntegrationTests.java | 52 ++++++
.../commands/KeyCommandIntegrationTests.java | 55 +++++-
.../StringCommandIntegrationTests.java | 164 +++++++++++++++++-
3 files changed, 263 insertions(+), 8 deletions(-)
diff --git a/src/test/java/io/lettuce/core/cluster/commands/KeyClusterCommandIntegrationTests.java b/src/test/java/io/lettuce/core/cluster/commands/KeyClusterCommandIntegrationTests.java
index 757df6ea79..78dbab3272 100644
--- a/src/test/java/io/lettuce/core/cluster/commands/KeyClusterCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/cluster/commands/KeyClusterCommandIntegrationTests.java
@@ -16,6 +16,11 @@
import io.lettuce.core.cluster.ClusterTestUtil;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.test.LettuceExtension;
+import io.lettuce.core.ValueCondition;
+
+import io.lettuce.test.condition.RedisConditions;
+import org.junit.jupiter.api.Assumptions;
+
import io.lettuce.test.condition.EnabledOnCommand;
/**
@@ -90,6 +95,53 @@ void unlink() {
assertThat(redis.unlink(key, "a", "b")).isEqualTo(3);
assertThat(redis.exists(key)).isEqualTo(0);
+
+ }
+
+ @Test
+ @EnabledOnCommand("DELEX")
+ void delex_unconditional_and_digest_guarded_cluster() {
+ String k = "k:delex";
+ // unconditional delete
+
+ redis.set(k, "v");
+ assertThat(redis.delex(k)).isEqualTo(1);
+ assertThat(redis.exists(k)).isEqualTo(0);
+
+ // digest-guarded delete
+ redis.set(k, "bar");
+ String d = redis.digestKey(k);
+ // wrong condition: digestNotEqualHex (should abort)
+ assertThat(redis.delex(k, ValueCondition. digestNotEqualHex(d))).isEqualTo(0);
+ assertThat(redis.exists(k)).isEqualTo(1);
+ // right condition: digestEqualHex (should delete)
+ assertThat(redis.delex(k, ValueCondition. digestEqualHex(d))).isEqualTo(1);
+ assertThat(redis.exists(k)).isEqualTo(0);
+ }
+
+ @Test
+ @EnabledOnCommand("DELEX")
+ void delex_missing_cluster_returns_0() {
+
+ String k = "k:missing-cluster";
+ assertThat(redis.delex(k)).isEqualTo(0);
+ }
+
+ @Test
+ @EnabledOnCommand("DELEX")
+ void delex_value_equal_notEqual_cluster() {
+
+ String k = "k:delex-eq-cluster";
+ redis.set(k, "v1");
+ // wrong equality -> abort
+ assertThat(redis.delex(k, ValueCondition.equal("nope"))).isEqualTo(0);
+ // correct equality -> delete
+ assertThat(redis.delex(k, ValueCondition.equal("v1"))).isEqualTo(1);
+ // not-equal that fails (after deletion, recreate)
+ redis.set(k, "v2");
+ assertThat(redis.delex(k, ValueCondition.notEqual("v2"))).isEqualTo(0);
+ // not-equal that succeeds
+ assertThat(redis.delex(k, ValueCondition.notEqual("other"))).isEqualTo(1);
}
}
diff --git a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
index 2e1ba647c3..dc5fda3df8 100644
--- a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
@@ -33,10 +33,12 @@
import javax.inject.Inject;
-import org.junit.Ignore;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
+import io.lettuce.test.condition.RedisConditions;
+import org.junit.jupiter.api.Assumptions;
+
import io.lettuce.core.CopyArgs;
import io.lettuce.core.ExpireArgs;
import io.lettuce.core.KeyScanArgs;
@@ -46,6 +48,8 @@
import io.lettuce.core.ScanCursor;
import io.lettuce.core.StreamScanCursor;
import io.lettuce.core.TestSupport;
+import io.lettuce.core.ValueCondition;
+
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.test.LettuceExtension;
import io.lettuce.test.ListStreamingAdapter;
@@ -612,4 +616,53 @@ void setup100KeyValues(Set expect) {
}
}
+ @Test
+ @EnabledOnCommand("DELEX")
+ void delex_unconditional_and_digest_guarded() {
+ String k = "k:delex";
+ // unconditional delete
+
+ redis.set(k, value);
+ assertThat(redis.delex(k)).isEqualTo(1);
+ assertThat(redis.exists(k)).isEqualTo(0);
+
+ // digest-guarded delete
+ redis.set(k, "bar");
+ String d = redis.digestKey(k); // new DIGEST command
+ // wrong condition: digestNotEqualHex (should abort)
+ assertThat(redis.delex(k, ValueCondition. digestNotEqualHex(d))).isEqualTo(0);
+ assertThat(redis.exists(k)).isEqualTo(1);
+ // right condition: digestEqualHex (should delete)
+ assertThat(redis.delex(k, ValueCondition. digestEqualHex(d))).isEqualTo(1);
+ assertThat(redis.exists(k)).isEqualTo(0);
+ }
+
+ @Test
+ @EnabledOnCommand("DELEX")
+ void delex_with_value_equal_notEqual() {
+
+ String k = "k:delex-eq";
+ redis.set(k, "v1");
+ // wrong equality -> abort
+ assertThat(redis.delex(k, ValueCondition.equal("nope"))).isEqualTo(0);
+ // correct equality -> delete
+ assertThat(redis.delex(k, ValueCondition.equal("v1"))).isEqualTo(1);
+ // not-equal that fails (after deletion, recreate)
+ redis.set(k, "v2");
+ assertThat(redis.delex(k, ValueCondition.notEqual("v2"))).isEqualTo(0);
+ // not-equal that succeeds
+ assertThat(redis.delex(k, ValueCondition.notEqual("other"))).isEqualTo(1);
+ }
+
+ @Test
+ @EnabledOnCommand("DELEX")
+ void delex_on_missing_returns_0_for_any_condition() {
+ String k = "k:missing";
+ assertThat(redis.delex(k)).isEqualTo(0);
+
+ assertThat(redis.delex(k, ValueCondition.exists())).isEqualTo(0);
+ assertThat(redis.delex(k, ValueCondition.equal("x"))).isEqualTo(0);
+ assertThat(redis.delex(k, ValueCondition. digestEqualHex("0000000000000000"))).isEqualTo(0);
+ }
+
}
diff --git a/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java
index a90c3c27ad..590315e69a 100644
--- a/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java
@@ -25,13 +25,14 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import java.lang.reflect.Proxy;
import java.time.Duration;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import io.lettuce.test.condition.RedisConditions;
+
import javax.inject.Inject;
import org.junit.jupiter.api.Assumptions;
@@ -42,13 +43,7 @@
import org.junit.jupiter.api.extension.ExtendWith;
import io.lettuce.core.*;
-import io.lettuce.core.api.StatefulConnection;
-import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
-import io.lettuce.core.dynamic.Commands;
-import io.lettuce.core.dynamic.RedisCommandFactory;
-import io.lettuce.core.dynamic.annotation.Command;
-import io.lettuce.core.dynamic.annotation.Param;
import io.lettuce.test.KeyValueStreamingAdapter;
import io.lettuce.test.LettuceExtension;
import io.lettuce.test.condition.EnabledOnCommand;
@@ -487,4 +482,159 @@ void lcsMinMatchLenIdxMatchLen() {
assertThat(matchResult.getMatchString()).isNullOrEmpty();
}
+ @Test
+ @EnabledOnCommand("DIGEST")
+ void digestKey_returnsHex_and_changesOnValueChange() {
+
+ assertThat(redis.digestKey("d")).isNull();
+ redis.set("d", "v1");
+ String d1 = redis.digestKey("d");
+ assertThat(d1).isNotNull().matches("[0-9a-f]{16}");
+ assertThat(redis.digestKey("d")).isEqualTo(d1);
+ redis.set("d", "v2");
+ assertThat(redis.digestKey("d")).isNotEqualTo(d1);
+ }
+
+ @Test
+ @EnabledOnCommand("DELEX")
+ void set_with_ValueCondition_exists_notExists() {
+
+ String k = "k:nx-xx";
+ // key does not exist: EXISTS/XX should abort
+ assertThat(redis.set(k, "v", ValueCondition. exists())).isNull();
+ // NOT_EXISTS/NX should succeed
+ assertThat(redis.set(k, "v", ValueCondition. notExists())).isEqualTo("OK");
+ // NOT_EXISTS/NX should now abort
+ assertThat(redis.set(k, "v2", ValueCondition. notExists())).isNull();
+ // EXISTS/XX should succeed
+ assertThat(redis.set(k, "v2", ValueCondition. exists())).isEqualTo("OK");
+ assertThat(redis.get(k)).isEqualTo("v2");
+ }
+
+ @Test
+ @EnabledOnCommand("DELEX")
+ void set_with_ValueCondition_equal_notEqual() {
+
+ String k = "k:ifeq-ifne";
+ redis.set(k, "v1");
+ // wrong equality value -> abort
+ assertThat(redis.set(k, "v2", ValueCondition.equal("nope"))).isNull();
+ assertThat(redis.get(k)).isEqualTo("v1");
+ // correct equality value -> success
+ assertThat(redis.set(k, "v2", ValueCondition.equal("v1"))).isEqualTo("OK");
+ assertThat(redis.get(k)).isEqualTo("v2");
+ // not-equal that fails (current is v2)
+ assertThat(redis.set(k, "v3", ValueCondition.notEqual("v2"))).isNull();
+ assertThat(redis.get(k)).isEqualTo("v2");
+ // not-equal that succeeds
+ assertThat(redis.set(k, "v3", ValueCondition.notEqual("other"))).isEqualTo("OK");
+ assertThat(redis.get(k)).isEqualTo("v3");
+ }
+
+ @Test
+ @EnabledOnCommand("DELEX")
+ void setget_with_ValueCondition_returnsPreviousValue() {
+
+ String k = "k:setget";
+ redis.set(k, "A");
+ // condition mismatch -> server returns previous value, value unchanged
+ assertThat(redis.setGet(k, "B", ValueCondition.equal("X"))).isEqualTo("A");
+ assertThat(redis.get(k)).isEqualTo("A");
+ // condition match -> returns previous and updates
+ assertThat(redis.setGet(k, "C", ValueCondition.equal("A"))).isEqualTo("A");
+ assertThat(redis.get(k)).isEqualTo("C");
+ }
+
+ @Test
+ @EnabledOnCommand("DIGEST")
+ void set_with_digest_conditions() {
+
+ String k = "k:digest";
+ redis.set(k, "C");
+ String d = redis.digestKey(k);
+ assertThat(d).isNotNull();
+ // digest equal -> success
+ assertThat(redis.set(k, "D", ValueCondition. digestEqualHex(d))).isEqualTo("OK");
+ // reusing old digest equal -> abort
+ assertThat(redis.set(k, "E", ValueCondition. digestEqualHex(d))).isNull();
+ // digest not equal (against old digest) -> success
+ assertThat(redis.set(k, "F", ValueCondition. digestNotEqualHex(d))).isEqualTo("OK");
+ }
+
+ @Test
+ @EnabledOnCommand("DIGEST")
+ void set_with_digestNotEqual_fails_when_equal() {
+ String k = "k:dig-not-eq-fail";
+
+ redis.set(k, "X");
+ String d = redis.digestKey(k);
+ assertThat(redis.set(k, "Y", ValueCondition. digestNotEqualHex(d))).isNull();
+ assertThat(redis.get(k)).isEqualTo("X");
+ }
+
+ @Test
+ @EnabledOnCommand("DIGEST")
+ void setget_with_digest_conditions() {
+ String k = "k:setget-dig";
+
+ redis.set(k, "A");
+ String dA = redis.digestKey(k);
+ // match -> returns previous and updates
+ assertThat(redis.setGet(k, "B", ValueCondition. digestEqualHex(dA))).isEqualTo("A");
+ assertThat(redis.get(k)).isEqualTo("B");
+ // mismatch (using old digest) -> returns previous and does not update
+ assertThat(redis.setGet(k, "C", ValueCondition. digestEqualHex(dA))).isEqualTo("B");
+ assertThat(redis.get(k)).isEqualTo("B");
+ }
+
+ @Test
+ @EnabledOnCommand("DIGEST")
+ void digestKey_emptyValue() {
+ String k = "k:empty";
+ redis.set(k, "");
+ String d = redis.digestKey(k);
+
+ assertThat(d).isNotNull().matches("[0-9a-f]{16}");
+ assertThat(redis.digestKey(k)).isEqualTo(d);
+ }
+
+ @Test
+ void setget_with_exists_notExists_conditions() {
+ Assumptions.assumeTrue(RedisConditions.of(redis).hasVersionGreaterOrEqualsTo("8.3.224"));
+
+ String k1 = "k:setget-nx";
+ // NX when missing -> perform set, returns previous (null)
+ assertThat(redis.setGet(k1, "A", ValueCondition.notExists())).isNull();
+ assertThat(redis.get(k1)).isEqualTo("A");
+ // NX when present -> no set, returns previous
+ assertThat(redis.setGet(k1, "B", ValueCondition.notExists())).isEqualTo("A");
+ assertThat(redis.get(k1)).isEqualTo("A");
+
+ String k2 = "k:setget-xx";
+ // XX when missing -> no-op, returns previous (null)
+ redis.del(k2);
+ assertThat(redis.setGet(k2, "X", ValueCondition.exists())).isNull();
+ assertThat(redis.get(k2)).isNull();
+ // XX when present -> set, returns previous
+ redis.set(k2, "Y");
+ assertThat(redis.setGet(k2, "Z", ValueCondition.exists())).isEqualTo("Y");
+ assertThat(redis.get(k2)).isEqualTo("Z");
+ }
+
+ @Test
+ void set_with_SetArgs_and_ValueCondition_combination() {
+ String k = "k:set-args-cond";
+ // NX + EX should set with TTL
+ Assumptions.assumeTrue(RedisConditions.of(redis).hasVersionGreaterOrEqualsTo("8.3.224"));
+
+ assertThat(redis.set(k, "v1", ex(100), ValueCondition.notExists())).isEqualTo("OK");
+ assertThat(redis.ttl(k)).isGreaterThan(0);
+ // NX when present should abort and keep value/ttl
+ Long ttlBefore = redis.ttl(k);
+ assertThat(redis.set(k, "v2", ex(100), ValueCondition.notExists())).isNull();
+ assertThat(redis.get(k)).isEqualTo("v1");
+ assertThat(redis.ttl(k)).isGreaterThan(0);
+ assertThat(redis.ttl(k)).isLessThanOrEqualTo(ttlBefore);
+ }
+
}
From 5c8ad5e3283136bf69957613332b240a42b00e93 Mon Sep 17 00:00:00 2001
From: "aleksandar.todorov"
Date: Thu, 6 Nov 2025 12:42:33 +0200
Subject: [PATCH 3/8] Remove unconditional delex - use del instead
---
.../lettuce/core/AbstractRedisAsyncCommands.java | 5 -----
.../core/AbstractRedisReactiveCommands.java | 5 -----
.../io/lettuce/core/RedisCommandBuilder.java | 7 -------
.../core/api/async/RedisKeyAsyncCommands.java | 8 --------
.../api/reactive/RedisKeyReactiveCommands.java | 8 --------
.../lettuce/core/api/sync/RedisKeyCommands.java | 8 --------
.../api/async/NodeSelectionKeyAsyncCommands.java | 16 +++-------------
.../api/sync/NodeSelectionKeyCommands.java | 8 --------
.../api/coroutines/RedisKeyCoroutinesCommands.kt | 8 --------
.../coroutines/RedisKeyCoroutinesCommandsImpl.kt | 3 ---
.../io/lettuce/core/api/RedisKeyCommands.java | 8 --------
.../java/io/lettuce/apigenerator/Constants.java | 6 +-----
.../KeyClusterCommandIntegrationTests.java | 5 ++---
.../commands/KeyCommandIntegrationTests.java | 4 ++--
14 files changed, 8 insertions(+), 91 deletions(-)
diff --git a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
index f1452671f3..40d523973d 100644
--- a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
@@ -741,11 +741,6 @@ public RedisFuture del(Iterable keys) {
return dispatch(commandBuilder.del(keys));
}
- @Override
- public RedisFuture delex(K key) {
- return dispatch(commandBuilder.delex(key));
- }
-
@Override
public RedisFuture delex(K key, ValueCondition condition) {
return dispatch(commandBuilder.delex(key, condition));
diff --git a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
index 43963f5ad1..b21a81fd84 100644
--- a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
@@ -811,11 +811,6 @@ public Mono del(Iterable keys) {
return createMono(() -> commandBuilder.del(keys));
}
- @Override
- public Mono delex(K key) {
- return createMono(() -> commandBuilder.delex(key));
- }
-
@Override
public Mono delex(K key, ValueCondition condition) {
return createMono(() -> commandBuilder.delex(key, condition));
diff --git a/src/main/java/io/lettuce/core/RedisCommandBuilder.java b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
index 8ec63544a2..8ed7506444 100644
--- a/src/main/java/io/lettuce/core/RedisCommandBuilder.java
+++ b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
@@ -940,13 +940,6 @@ Command del(Iterable keys) {
return createCommand(DEL, new IntegerOutput<>(codec), args);
}
- Command delex(K key) {
- notNullKey(key);
-
- CommandArgs args = new CommandArgs<>(codec).addKey(key);
- return createCommand(DELEX, new IntegerOutput<>(codec), args);
- }
-
Command delex(K key, ValueCondition condition) {
notNullKey(key);
LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);
diff --git a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
index c66ddd68e6..f82b2219e1 100644
--- a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
@@ -79,14 +79,6 @@ public interface RedisKeyAsyncCommands {
*/
RedisFuture del(K... keys);
- /**
- * Delete the specified key conditionally.
- *
- * @param key the key.
- * @return Long integer-reply the number of keys that were removed.
- */
- RedisFuture delex(K key);
-
/**
* Delete the specified key if the compare condition matches.
*
diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
index 8f335b1c04..ecc0a55da7 100644
--- a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
@@ -79,14 +79,6 @@ public interface RedisKeyReactiveCommands {
*/
Mono del(K... keys);
- /**
- * Delete the specified key conditionally.
- *
- * @param key the key.
- * @return Long integer-reply the number of keys that were removed.
- */
- Mono delex(K key);
-
/**
* Delete the specified key if the compare condition matches.
*
diff --git a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
index f9a5a3b980..c74ae69ef2 100644
--- a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
+++ b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
@@ -78,14 +78,6 @@ public interface RedisKeyCommands {
*/
Long del(K... keys);
- /**
- * Delete the specified key conditionally.
- *
- * @param key the key.
- * @return Long integer-reply the number of keys that were removed.
- */
- Long delex(K key);
-
/**
* Delete the specified key if the compare condition matches.
*
diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
index c224ad93c1..0fc25bcac8 100644
--- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
@@ -19,14 +19,12 @@
*/
package io.lettuce.core.cluster.api.async;
-import java.util.List;
-import java.util.Date;
-import java.time.Instant;
import java.time.Duration;
-
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
import io.lettuce.core.CopyArgs;
import io.lettuce.core.ExpireArgs;
-import io.lettuce.core.KeyScanArgs;
import io.lettuce.core.KeyScanCursor;
import io.lettuce.core.MigrateArgs;
import io.lettuce.core.RestoreArgs;
@@ -78,14 +76,6 @@ public interface NodeSelectionKeyAsyncCommands {
*/
AsyncExecutions del(K... keys);
- /**
- * Delete the specified key conditionally.
- *
- * @param key the key.
- * @return Long integer-reply the number of keys that were removed.
- */
- AsyncExecutions delex(K key);
-
/**
* Delete the specified key if the compare condition matches.
*
diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
index 6e6b7c8c45..f1eaaa89cd 100644
--- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
@@ -78,14 +78,6 @@ public interface NodeSelectionKeyCommands {
*/
Executions del(K... keys);
- /**
- * Delete the specified key conditionally.
- *
- * @param key the key.
- * @return Long integer-reply the number of keys that were removed.
- */
- Executions delex(K key);
-
/**
* Delete the specified key if the compare condition matches.
*
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
index 8346e10079..5b7d1c4350 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
@@ -78,14 +78,6 @@ interface RedisKeyCoroutinesCommands {
*/
suspend fun del(vararg keys: K): Long?
- /**
- * Delete the specified key conditionally.
- *
- * @param key the key.
- * @return Long integer-reply the number of keys that were removed.
- */
- suspend fun delex(key: K): Long?
-
/**
* Delete the specified key if the compare condition matches.
*
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt
index 62feab2046..3c056d1adf 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommandsImpl.kt
@@ -52,9 +52,6 @@ internal class RedisKeyCoroutinesCommandsImpl(internal val ops
override suspend fun unlink(vararg keys: K): Long? =
ops.unlink(*keys).awaitFirstOrNull()
- override suspend fun delex(key: K): Long? =
- ops.delex(key).awaitFirstOrNull()
-
override suspend fun delex(key: K, condition: ValueCondition): Long? =
ops.delex(key, condition).awaitFirstOrNull()
diff --git a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
index cb8706e872..6ffba2d0f5 100644
--- a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
+++ b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
@@ -77,14 +77,6 @@ public interface RedisKeyCommands {
*/
Long del(K... keys);
- /**
- * Delete the specified key conditionally.
- *
- * @param key the key.
- * @return Long integer-reply the number of keys that were removed.
- */
- Long delex(K key);
-
/**
* Delete the specified key if the compare condition matches.
*
diff --git a/src/test/java/io/lettuce/apigenerator/Constants.java b/src/test/java/io/lettuce/apigenerator/Constants.java
index 7cee3129ac..2d457f912d 100644
--- a/src/test/java/io/lettuce/apigenerator/Constants.java
+++ b/src/test/java/io/lettuce/apigenerator/Constants.java
@@ -27,11 +27,7 @@
*/
class Constants {
- public static final String[] TEMPLATE_NAMES = { "BaseRedisCommands", "RedisAclCommands", "RedisFunctionCommands",
- "RedisGeoCommands", "RedisHashCommands", "RedisHLLCommands", "RedisKeyCommands", "RedisListCommands",
- "RedisScriptingCommands", "RedisSentinelCommands", "RedisServerCommands", "RedisSetCommands",
- "RedisSortedSetCommands", "RedisStreamCommands", "RedisStringCommands", "RedisTransactionalCommands",
- "RedisJsonCommands", "RedisVectorSetCommands", "RediSearchCommands" };
+ public static final String[] TEMPLATE_NAMES = { "RedisKeyCommands" };
public static final File TEMPLATES = new File("src/main/templates");
diff --git a/src/test/java/io/lettuce/core/cluster/commands/KeyClusterCommandIntegrationTests.java b/src/test/java/io/lettuce/core/cluster/commands/KeyClusterCommandIntegrationTests.java
index 78dbab3272..1110c6b3a4 100644
--- a/src/test/java/io/lettuce/core/cluster/commands/KeyClusterCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/cluster/commands/KeyClusterCommandIntegrationTests.java
@@ -95,7 +95,6 @@ void unlink() {
assertThat(redis.unlink(key, "a", "b")).isEqualTo(3);
assertThat(redis.exists(key)).isEqualTo(0);
-
}
@Test
@@ -105,7 +104,7 @@ void delex_unconditional_and_digest_guarded_cluster() {
// unconditional delete
redis.set(k, "v");
- assertThat(redis.delex(k)).isEqualTo(1);
+ assertThat(redis.del(k)).isEqualTo(1);
assertThat(redis.exists(k)).isEqualTo(0);
// digest-guarded delete
@@ -124,7 +123,7 @@ void delex_unconditional_and_digest_guarded_cluster() {
void delex_missing_cluster_returns_0() {
String k = "k:missing-cluster";
- assertThat(redis.delex(k)).isEqualTo(0);
+ assertThat(redis.del(k)).isEqualTo(0);
}
@Test
diff --git a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
index dc5fda3df8..4a4f382098 100644
--- a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
@@ -623,7 +623,7 @@ void delex_unconditional_and_digest_guarded() {
// unconditional delete
redis.set(k, value);
- assertThat(redis.delex(k)).isEqualTo(1);
+ assertThat(redis.del(k)).isEqualTo(1);
assertThat(redis.exists(k)).isEqualTo(0);
// digest-guarded delete
@@ -658,7 +658,7 @@ void delex_with_value_equal_notEqual() {
@EnabledOnCommand("DELEX")
void delex_on_missing_returns_0_for_any_condition() {
String k = "k:missing";
- assertThat(redis.delex(k)).isEqualTo(0);
+ assertThat(redis.del(k)).isEqualTo(0);
assertThat(redis.delex(k, ValueCondition.exists())).isEqualTo(0);
assertThat(redis.delex(k, ValueCondition.equal("x"))).isEqualTo(0);
From 9dcad7393f0c53ee7ffd8fc1d2e972d3e3330c2a Mon Sep 17 00:00:00 2001
From: "aleksandar.todorov"
Date: Thu, 6 Nov 2025 13:27:03 +0200
Subject: [PATCH 4/8] Fix failing test
---
.../lettuce/core/cluster/ClusterReadOnlyCommandsUnitTests.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/java/io/lettuce/core/cluster/ClusterReadOnlyCommandsUnitTests.java b/src/test/java/io/lettuce/core/cluster/ClusterReadOnlyCommandsUnitTests.java
index f34205de3d..28bc753066 100644
--- a/src/test/java/io/lettuce/core/cluster/ClusterReadOnlyCommandsUnitTests.java
+++ b/src/test/java/io/lettuce/core/cluster/ClusterReadOnlyCommandsUnitTests.java
@@ -20,7 +20,7 @@ class ClusterReadOnlyCommandsUnitTests {
@Test
void testCount() {
- assertThat(ClusterReadOnlyCommands.getReadOnlyCommands()).hasSize(100);
+ assertThat(ClusterReadOnlyCommands.getReadOnlyCommands()).hasSize(101);
}
@Test
From 29b48eb3ffe00fec1c7f572e8bc774c9f6f2fab8 Mon Sep 17 00:00:00 2001
From: "aleksandar.todorov"
Date: Thu, 6 Nov 2025 13:51:49 +0200
Subject: [PATCH 5/8] Mark new API as experimental
---
src/main/java/io/lettuce/core/ValueCondition.java | 4 +++-
.../io/lettuce/core/api/async/RedisKeyAsyncCommands.java | 2 ++
.../io/lettuce/core/api/async/RedisStringAsyncCommands.java | 6 ++++++
.../lettuce/core/api/reactive/RedisKeyReactiveCommands.java | 2 ++
.../core/api/reactive/RedisStringReactiveCommands.java | 6 ++++++
.../java/io/lettuce/core/api/sync/RedisKeyCommands.java | 2 ++
.../java/io/lettuce/core/api/sync/RedisStringCommands.java | 6 ++++++
.../cluster/api/async/NodeSelectionKeyAsyncCommands.java | 3 +++
.../core/cluster/api/sync/NodeSelectionKeyCommands.java | 2 ++
.../core/api/coroutines/RedisKeyCoroutinesCommands.kt | 2 ++
.../core/api/coroutines/RedisStringCoroutinesCommands.kt | 6 ++++++
.../templates/io/lettuce/core/api/RedisKeyCommands.java | 2 ++
.../templates/io/lettuce/core/api/RedisStringCommands.java | 6 ++++++
13 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/src/main/java/io/lettuce/core/ValueCondition.java b/src/main/java/io/lettuce/core/ValueCondition.java
index b367f421a0..369750ec03 100644
--- a/src/main/java/io/lettuce/core/ValueCondition.java
+++ b/src/main/java/io/lettuce/core/ValueCondition.java
@@ -19,6 +19,8 @@
*/
package io.lettuce.core;
+import io.lettuce.core.annotations.Experimental;
+
/**
* A compare condition to be used with commands that support conditional value checks (e.g. SET with IFEQ/IFNE/IFDEQ/IFDNE and
* DELEX). This abstraction lets callers express value-based or digest-based comparisons without exploding method overloads.
@@ -28,8 +30,8 @@
*
*
* @param value type used for value-based comparisons
- * @since 8.x
*/
+@Experimental
public final class ValueCondition {
/**
diff --git a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
index f82b2219e1..fd6bb1f3d1 100644
--- a/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
@@ -35,6 +35,7 @@
import io.lettuce.core.SortArgs;
import io.lettuce.core.StreamScanCursor;
import io.lettuce.core.ValueCondition;
+import io.lettuce.core.annotations.Experimental;
import io.lettuce.core.RedisFuture;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;
@@ -86,6 +87,7 @@ public interface RedisKeyAsyncCommands {
* @param condition the compare condition, must not be {@code null}.
* @return Long integer-reply the number of keys that were removed.
*/
+ @Experimental
RedisFuture delex(K key, ValueCondition condition);
/**
diff --git a/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java
index e6a2677c5e..bc864994c8 100644
--- a/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/api/async/RedisStringAsyncCommands.java
@@ -30,6 +30,7 @@
import io.lettuce.core.StrAlgoArgs;
import io.lettuce.core.StringMatchResult;
import io.lettuce.core.ValueCondition;
+import io.lettuce.core.annotations.Experimental;
import io.lettuce.core.output.KeyValueStreamingChannel;
import io.lettuce.core.RedisFuture;
@@ -263,6 +264,7 @@ public interface RedisStringAsyncCommands {
* @param key the key.
* @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist.
*/
+ @Experimental
RedisFuture digestKey(K key);
/**
@@ -390,6 +392,7 @@ public interface RedisStringAsyncCommands {
* @param condition the compare condition, must not be {@code null}.
* @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
*/
+ @Experimental
RedisFuture set(K key, V value, ValueCondition condition);
/**
@@ -401,6 +404,7 @@ public interface RedisStringAsyncCommands {
* @param condition the compare condition, must not be {@code null}.
* @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
*/
+ @Experimental
RedisFuture set(K key, V value, SetArgs setArgs, ValueCondition condition);
/**
@@ -411,6 +415,7 @@ public interface RedisStringAsyncCommands {
* @param condition the compare condition, must not be {@code null}.
* @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
*/
+ @Experimental
RedisFuture setGet(K key, V value, ValueCondition condition);
/**
@@ -422,6 +427,7 @@ public interface RedisStringAsyncCommands {
* @param condition the compare condition, must not be {@code null}.
* @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
*/
+ @Experimental
RedisFuture setGet(K key, V value, SetArgs setArgs, ValueCondition condition);
/**
diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
index ecc0a55da7..c9aa02e921 100644
--- a/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/api/reactive/RedisKeyReactiveCommands.java
@@ -34,6 +34,7 @@
import io.lettuce.core.SortArgs;
import io.lettuce.core.StreamScanCursor;
import io.lettuce.core.ValueCondition;
+import io.lettuce.core.annotations.Experimental;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;
import reactor.core.publisher.Flux;
@@ -86,6 +87,7 @@ public interface RedisKeyReactiveCommands {
* @param condition the compare condition, must not be {@code null}.
* @return Long integer-reply the number of keys that were removed.
*/
+ @Experimental
Mono delex(K key, ValueCondition condition);
/**
diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java
index 81a5b58443..64fdc1afe2 100644
--- a/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/api/reactive/RedisStringReactiveCommands.java
@@ -29,6 +29,7 @@
import io.lettuce.core.StrAlgoArgs;
import io.lettuce.core.StringMatchResult;
import io.lettuce.core.ValueCondition;
+import io.lettuce.core.annotations.Experimental;
import io.lettuce.core.output.KeyValueStreamingChannel;
import io.lettuce.core.Value;
import reactor.core.publisher.Flux;
@@ -264,6 +265,7 @@ public interface RedisStringReactiveCommands {
* @param key the key.
* @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist.
*/
+ @Experimental
Mono digestKey(K key);
/**
@@ -394,6 +396,7 @@ public interface RedisStringReactiveCommands {
* @param condition the compare condition, must not be {@code null}.
* @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
*/
+ @Experimental
Mono set(K key, V value, ValueCondition condition);
/**
@@ -405,6 +408,7 @@ public interface RedisStringReactiveCommands {
* @param condition the compare condition, must not be {@code null}.
* @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
*/
+ @Experimental
Mono set(K key, V value, SetArgs setArgs, ValueCondition condition);
/**
@@ -415,6 +419,7 @@ public interface RedisStringReactiveCommands {
* @param condition the compare condition, must not be {@code null}.
* @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
*/
+ @Experimental
Mono setGet(K key, V value, ValueCondition condition);
/**
@@ -426,6 +431,7 @@ public interface RedisStringReactiveCommands {
* @param condition the compare condition, must not be {@code null}.
* @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
*/
+ @Experimental
Mono setGet(K key, V value, SetArgs setArgs, ValueCondition condition);
/**
diff --git a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
index c74ae69ef2..3ad08ab476 100644
--- a/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
+++ b/src/main/java/io/lettuce/core/api/sync/RedisKeyCommands.java
@@ -35,6 +35,7 @@
import io.lettuce.core.SortArgs;
import io.lettuce.core.StreamScanCursor;
import io.lettuce.core.ValueCondition;
+import io.lettuce.core.annotations.Experimental;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;
@@ -85,6 +86,7 @@ public interface RedisKeyCommands {
* @param condition the compare condition, must not be {@code null}.
* @return Long integer-reply the number of keys that were removed.
*/
+ @Experimental
Long delex(K key, ValueCondition condition);
/**
diff --git a/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java
index 651215bc91..c00a5d3d27 100644
--- a/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java
+++ b/src/main/java/io/lettuce/core/api/sync/RedisStringCommands.java
@@ -30,6 +30,7 @@
import io.lettuce.core.StrAlgoArgs;
import io.lettuce.core.StringMatchResult;
import io.lettuce.core.ValueCondition;
+import io.lettuce.core.annotations.Experimental;
import io.lettuce.core.output.KeyValueStreamingChannel;
/**
@@ -262,6 +263,7 @@ public interface RedisStringCommands {
* @param key the key.
* @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist.
*/
+ @Experimental
String digestKey(K key);
/**
@@ -389,6 +391,7 @@ public interface RedisStringCommands {
* @param condition the compare condition, must not be {@code null}.
* @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
*/
+ @Experimental
String set(K key, V value, ValueCondition condition);
/**
@@ -400,6 +403,7 @@ public interface RedisStringCommands {
* @param condition the compare condition, must not be {@code null}.
* @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
*/
+ @Experimental
String set(K key, V value, SetArgs setArgs, ValueCondition condition);
/**
@@ -410,6 +414,7 @@ public interface RedisStringCommands {
* @param condition the compare condition, must not be {@code null}.
* @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
*/
+ @Experimental
V setGet(K key, V value, ValueCondition condition);
/**
@@ -421,6 +426,7 @@ public interface RedisStringCommands {
* @param condition the compare condition, must not be {@code null}.
* @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
*/
+ @Experimental
V setGet(K key, V value, SetArgs setArgs, ValueCondition condition);
/**
diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
index 0fc25bcac8..810fcbe8a2 100644
--- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionKeyAsyncCommands.java
@@ -33,6 +33,8 @@
import io.lettuce.core.SortArgs;
import io.lettuce.core.StreamScanCursor;
import io.lettuce.core.ValueCondition;
+import io.lettuce.core.KeyScanArgs;
+import io.lettuce.core.annotations.Experimental;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;
@@ -83,6 +85,7 @@ public interface NodeSelectionKeyAsyncCommands {
* @param condition the compare condition, must not be {@code null}.
* @return Long integer-reply the number of keys that were removed.
*/
+ @Experimental
AsyncExecutions delex(K key, ValueCondition condition);
/**
diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
index f1eaaa89cd..5b0c318f1d 100644
--- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionKeyCommands.java
@@ -35,6 +35,7 @@
import io.lettuce.core.SortArgs;
import io.lettuce.core.StreamScanCursor;
import io.lettuce.core.ValueCondition;
+import io.lettuce.core.annotations.Experimental;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;
@@ -85,6 +86,7 @@ public interface NodeSelectionKeyCommands {
* @param condition the compare condition, must not be {@code null}.
* @return Long integer-reply the number of keys that were removed.
*/
+ @Experimental
Executions delex(K key, ValueCondition condition);
/**
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
index 5b7d1c4350..4d9efde582 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisKeyCoroutinesCommands.kt
@@ -35,6 +35,7 @@ import io.lettuce.core.ScanCursor
import io.lettuce.core.SortArgs
import io.lettuce.core.ValueCondition
import io.lettuce.core.ExperimentalLettuceCoroutinesApi
+import io.lettuce.core.annotations.Experimental
import kotlinx.coroutines.flow.Flow
/**
@@ -85,6 +86,7 @@ interface RedisKeyCoroutinesCommands {
* @param condition the compare condition, must not be `null`.
* @return Long integer-reply the number of keys that were removed.
*/
+ @Experimental
suspend fun delex(key: K, condition: ValueCondition): Long?
/**
diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt
index 1911043327..6d72ed0dfc 100644
--- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt
+++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisStringCoroutinesCommands.kt
@@ -28,6 +28,7 @@ import io.lettuce.core.SetArgs
import io.lettuce.core.StringMatchResult
import io.lettuce.core.ValueCondition
import io.lettuce.core.ExperimentalLettuceCoroutinesApi
+import io.lettuce.core.annotations.Experimental
import kotlinx.coroutines.flow.Flow
/**
@@ -214,6 +215,7 @@ interface RedisStringCoroutinesCommands {
* @param key the key.
* @return String bulk-string-reply the hex digest of the key's value, or `null` when `key` does not exist.
*/
+ @Experimental
suspend fun digestKey(key: K): String?
/**
@@ -332,6 +334,7 @@ interface RedisStringCoroutinesCommands {
* @param condition the compare condition, must not be `null`.
* @return String simple-string-reply `OK` if `SET` was executed; `null` if the operation was aborted.
*/
+ @Experimental
suspend fun set(key: K, value: V, condition: ValueCondition): String?
/**
@@ -343,6 +346,7 @@ interface RedisStringCoroutinesCommands {
* @param condition the compare condition, must not be `null`.
* @return String simple-string-reply `OK` if `SET` was executed; `null` if the operation was aborted.
*/
+ @Experimental
suspend fun set(key: K, value: V, setArgs: SetArgs, condition: ValueCondition): String?
/**
@@ -353,6 +357,7 @@ interface RedisStringCoroutinesCommands {
* @param condition the compare condition, must not be `null`.
* @return V bulk-string-reply the previous value if the key existed, or `null` when `key` did not exist.
*/
+ @Experimental
suspend fun setGet(key: K, value: V, condition: ValueCondition): V?
/**
@@ -364,6 +369,7 @@ interface RedisStringCoroutinesCommands {
* @param condition the compare condition, must not be `null`.
* @return V bulk-string-reply the previous value if the key existed, or `null` when `key` did not exist.
*/
+ @Experimental
suspend fun setGet(key: K, value: V, setArgs: SetArgs, condition: ValueCondition): V?
/**
diff --git a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
index 6ffba2d0f5..398a6bad39 100644
--- a/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
+++ b/src/main/templates/io/lettuce/core/api/RedisKeyCommands.java
@@ -35,6 +35,7 @@
import io.lettuce.core.SortArgs;
import io.lettuce.core.StreamScanCursor;
import io.lettuce.core.ValueCondition;
+import io.lettuce.core.annotations.Experimental;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;
@@ -84,6 +85,7 @@ public interface RedisKeyCommands {
* @param condition the compare condition, must not be {@code null}.
* @return Long integer-reply the number of keys that were removed.
*/
+ @Experimental
Long delex(K key, ValueCondition condition);
/**
diff --git a/src/main/templates/io/lettuce/core/api/RedisStringCommands.java b/src/main/templates/io/lettuce/core/api/RedisStringCommands.java
index ab88715adf..191fff6c6c 100644
--- a/src/main/templates/io/lettuce/core/api/RedisStringCommands.java
+++ b/src/main/templates/io/lettuce/core/api/RedisStringCommands.java
@@ -30,6 +30,7 @@
import io.lettuce.core.StrAlgoArgs;
import io.lettuce.core.StringMatchResult;
import io.lettuce.core.ValueCondition;
+import io.lettuce.core.annotations.Experimental;
import io.lettuce.core.output.KeyValueStreamingChannel;
/**
@@ -261,6 +262,7 @@ public interface RedisStringCommands {
* @param key the key.
* @return String bulk-string-reply the hex digest of the key's value, or {@code null} when {@code key} does not exist.
*/
+ @Experimental
String digestKey(K key);
/**
@@ -388,6 +390,7 @@ public interface RedisStringCommands {
* @param condition the compare condition, must not be {@code null}.
* @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
*/
+ @Experimental
String set(K key, V value, ValueCondition condition);
/**
@@ -399,6 +402,7 @@ public interface RedisStringCommands {
* @param condition the compare condition, must not be {@code null}.
* @return String simple-string-reply {@code OK} if {@code SET} was executed; {@code null} if the operation was aborted.
*/
+ @Experimental
String set(K key, V value, SetArgs setArgs, ValueCondition condition);
/**
@@ -409,6 +413,7 @@ public interface RedisStringCommands {
* @param condition the compare condition, must not be {@code null}.
* @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
*/
+ @Experimental
V setGet(K key, V value, ValueCondition condition);
/**
@@ -420,6 +425,7 @@ public interface RedisStringCommands {
* @param condition the compare condition, must not be {@code null}.
* @return V bulk-string-reply the previous value if the key existed, or {@code null} when {@code key} did not exist.
*/
+ @Experimental
V setGet(K key, V value, SetArgs setArgs, ValueCondition condition);
/**
From b921774ed3477dd47a44cc65c1b6fc927098ca5a Mon Sep 17 00:00:00 2001
From: "aleksandar.todorov"
Date: Thu, 6 Nov 2025 15:56:58 +0200
Subject: [PATCH 6/8] Refactor ValueCondition
---
.../io/lettuce/core/RedisCommandBuilder.java | 37 +-------
.../java/io/lettuce/core/ValueCondition.java | 93 ++++++++++++-------
.../KeyClusterCommandIntegrationTests.java | 12 +--
.../commands/KeyCommandIntegrationTests.java | 17 ++--
.../StringCommandIntegrationTests.java | 58 ++++++------
.../MasterReplicaIntegrationTests.java | 25 +++++
6 files changed, 134 insertions(+), 108 deletions(-)
diff --git a/src/main/java/io/lettuce/core/RedisCommandBuilder.java b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
index 8ed7506444..8a572442de 100644
--- a/src/main/java/io/lettuce/core/RedisCommandBuilder.java
+++ b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
@@ -945,7 +945,7 @@ Command delex(K key, ValueCondition condition) {
LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);
CommandArgs args = new CommandArgs<>(codec).addKey(key);
- buildConditionArgs(args, condition);
+ condition.build(args);
return createCommand(DELEX, new IntegerOutput<>(codec), args);
}
@@ -2723,7 +2723,7 @@ Command set(K key, V value, ValueCondition condition) {
LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);
CommandArgs args = new CommandArgs<>(codec).addKey(key).addValue(value);
- buildConditionArgs(args, condition);
+ condition.build(args);
return createCommand(SET, new StatusOutput<>(codec), args);
}
@@ -2734,7 +2734,7 @@ Command set(K key, V value, SetArgs setArgs, ValueCondition con
CommandArgs args = new CommandArgs<>(codec).addKey(key).addValue(value);
setArgs.build(args);
- buildConditionArgs(args, condition);
+ condition.build(args);
return createCommand(SET, new StatusOutput<>(codec), args);
}
@@ -2743,7 +2743,7 @@ Command setGet(K key, V value, ValueCondition condition) {
LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);
CommandArgs args = new CommandArgs<>(codec).addKey(key).addValue(value);
- buildConditionArgs(args, condition);
+ condition.build(args);
args.add(GET);
return createCommand(SET, new ValueOutput<>(codec), args);
}
@@ -2755,7 +2755,7 @@ Command setGet(K key, V value, SetArgs setArgs, ValueCondition condi
CommandArgs args = new CommandArgs<>(codec).addKey(key).addValue(value);
setArgs.build(args);
- buildConditionArgs(args, condition);
+ condition.build(args);
args.add(GET);
return createCommand(SET, new ValueOutput<>(codec), args);
}
@@ -4622,33 +4622,6 @@ Command zscanStreaming(ScoredValueStreamingChannel ch
return zscanStreaming(channel, key, ScanCursor.INITIAL, scanArgs);
}
- private void buildConditionArgs(CommandArgs args, ValueCondition condition) {
- switch (condition.kind()) {
- case EXISTS:
- args.add(XX);
- break;
- case NOT_EXISTS:
- args.add(NX);
- break;
- case EQUAL:
- args.add(IFEQ).addValue(condition.value());
- break;
- case NOT_EQUAL:
- args.add(IFNE).addValue(condition.value());
- break;
- case DIGEST_EQUAL:
- args.add(IFDEQ).add(condition.digestHex());
- break;
- case DIGEST_NOT_EQUAL:
- args.add(IFDNE).add(condition.digestHex());
- break;
- case ALWAYS:
- default:
- // no additional args
- break;
- }
- }
-
Command zscanStreaming(ScoredValueStreamingChannel channel, K key, ScanCursor scanCursor,
ScanArgs scanArgs) {
notNullKey(key);
diff --git a/src/main/java/io/lettuce/core/ValueCondition.java b/src/main/java/io/lettuce/core/ValueCondition.java
index 369750ec03..47b719ebf3 100644
--- a/src/main/java/io/lettuce/core/ValueCondition.java
+++ b/src/main/java/io/lettuce/core/ValueCondition.java
@@ -20,6 +20,8 @@
package io.lettuce.core;
import io.lettuce.core.annotations.Experimental;
+import io.lettuce.core.protocol.CommandArgs;
+import io.lettuce.core.protocol.CommandKeyword;
/**
* A compare condition to be used with commands that support conditional value checks (e.g. SET with IFEQ/IFNE/IFDEQ/IFDNE and
@@ -38,20 +40,39 @@ public final class ValueCondition {
* The kind of condition represented by this instance.
*/
public enum Kind {
- /** unconditional */
- ALWAYS,
- /** key must exist */
- EXISTS,
- /** key must not exist */
- NOT_EXISTS,
+
/** current value must equal provided value */
- EQUAL,
+ EQUAL(CommandKeyword.IFEQ, Param.VALUE),
/** current value must not equal provided value */
- NOT_EQUAL,
+ NOT_EQUAL(CommandKeyword.IFNE, Param.VALUE),
/** current value's digest must equal provided digest */
- DIGEST_EQUAL,
+ DIGEST_EQUAL(CommandKeyword.IFDEQ, Param.DIGEST),
/** current value's digest must not equal provided digest */
- DIGEST_NOT_EQUAL
+ DIGEST_NOT_EQUAL(CommandKeyword.IFDNE, Param.DIGEST);
+
+ private final CommandKeyword keyword;
+
+ private final Param param;
+
+ Kind(CommandKeyword keyword, Param param) {
+ this.keyword = keyword;
+ this.param = param;
+ }
+
+ /** The protocol keyword to emit for this condition. */
+ public CommandKeyword keyword() {
+ return keyword;
+ }
+
+ /** Indicates whether this condition uses a value or a digest parameter. */
+ public Param param() {
+ return param;
+ }
+ }
+
+ /** Parameter kind for condition arguments. */
+ enum Param {
+ VALUE, DIGEST
}
private final Kind kind;
@@ -66,45 +87,53 @@ private ValueCondition(Kind kind, V value, String digestHex) {
this.digestHex = digestHex;
}
- /** A condition that always applies (no comparison). */
- public static ValueCondition always() {
- return new ValueCondition<>(Kind.ALWAYS, null, null);
-
- }
-
- /** A condition that requires the key to exist (equivalent to XX in SET). */
- public static ValueCondition exists() {
- return new ValueCondition<>(Kind.EXISTS, null, null);
- }
-
- /** A condition that requires the key to not exist (equivalent to NX in SET). */
- public static ValueCondition notExists() {
- return new ValueCondition<>(Kind.NOT_EXISTS, null, null);
+ /**
+ * Append this condition's protocol arguments to the given args.
+ */
+ public void build(CommandArgs args) {
+ args.add(kind.keyword());
+ switch (kind.param()) {
+ case VALUE:
+ args.addValue(value);
+ break;
+ case DIGEST:
+ args.add(digestHex);
+ break;
+ default:
+ break;
+ }
}
- /** A value-based comparison: set/delete only if the current value equals {@code value}. */
- public static ValueCondition equal(V value) {
+ // Factory methods for creating value- and digest-based conditions
+ /** Create a value-based equality condition; succeeds only if the current value equals the given value. */
+ public static ValueCondition valueEq(V value) {
if (value == null)
throw new IllegalArgumentException("value must not be null");
return new ValueCondition<>(Kind.EQUAL, value, null);
}
- /** A value-based comparison: set/delete only if the current value does not equal {@code value}. */
- public static ValueCondition notEqual(V value) {
+ /** Create a value-based inequality condition; succeeds only if the current value does not equal the given value. */
+ public static ValueCondition valueNe(V value) {
if (value == null)
throw new IllegalArgumentException("value must not be null");
return new ValueCondition<>(Kind.NOT_EQUAL, value, null);
}
- /** A digest-based comparison: set/delete only if the current value's digest equals {@code hex16Digest}. */
- public static ValueCondition digestEqualHex(String hex16Digest) {
+ /**
+ * Create a digest-based equality condition; succeeds only if the current value's digest matches the given 16-character
+ * lower-case hex digest.
+ */
+ public static ValueCondition digestEq(String hex16Digest) {
if (hex16Digest == null)
throw new IllegalArgumentException("digest must not be null");
return new ValueCondition<>(Kind.DIGEST_EQUAL, null, hex16Digest);
}
- /** A digest-based comparison: set/delete only if the current value's digest does not equal {@code hex16Digest}. */
- public static ValueCondition digestNotEqualHex(String hex16Digest) {
+ /**
+ * Create a digest-based inequality condition; succeeds only if the current value's digest does not match the given
+ * 16-character lower-case hex digest.
+ */
+ public static ValueCondition digestNe(String hex16Digest) {
if (hex16Digest == null)
throw new IllegalArgumentException("digest must not be null");
return new ValueCondition<>(Kind.DIGEST_NOT_EQUAL, null, hex16Digest);
diff --git a/src/test/java/io/lettuce/core/cluster/commands/KeyClusterCommandIntegrationTests.java b/src/test/java/io/lettuce/core/cluster/commands/KeyClusterCommandIntegrationTests.java
index 1110c6b3a4..c80148c5bb 100644
--- a/src/test/java/io/lettuce/core/cluster/commands/KeyClusterCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/cluster/commands/KeyClusterCommandIntegrationTests.java
@@ -111,10 +111,10 @@ void delex_unconditional_and_digest_guarded_cluster() {
redis.set(k, "bar");
String d = redis.digestKey(k);
// wrong condition: digestNotEqualHex (should abort)
- assertThat(redis.delex(k, ValueCondition. digestNotEqualHex(d))).isEqualTo(0);
+ assertThat(redis.delex(k, ValueCondition.digestNe(d))).isEqualTo(0);
assertThat(redis.exists(k)).isEqualTo(1);
// right condition: digestEqualHex (should delete)
- assertThat(redis.delex(k, ValueCondition. digestEqualHex(d))).isEqualTo(1);
+ assertThat(redis.delex(k, ValueCondition.digestEq(d))).isEqualTo(1);
assertThat(redis.exists(k)).isEqualTo(0);
}
@@ -133,14 +133,14 @@ void delex_value_equal_notEqual_cluster() {
String k = "k:delex-eq-cluster";
redis.set(k, "v1");
// wrong equality -> abort
- assertThat(redis.delex(k, ValueCondition.equal("nope"))).isEqualTo(0);
+ assertThat(redis.delex(k, ValueCondition.valueEq("nope"))).isEqualTo(0);
// correct equality -> delete
- assertThat(redis.delex(k, ValueCondition.equal("v1"))).isEqualTo(1);
+ assertThat(redis.delex(k, ValueCondition.valueEq("v1"))).isEqualTo(1);
// not-equal that fails (after deletion, recreate)
redis.set(k, "v2");
- assertThat(redis.delex(k, ValueCondition.notEqual("v2"))).isEqualTo(0);
+ assertThat(redis.delex(k, ValueCondition.valueNe("v2"))).isEqualTo(0);
// not-equal that succeeds
- assertThat(redis.delex(k, ValueCondition.notEqual("other"))).isEqualTo(1);
+ assertThat(redis.delex(k, ValueCondition.valueNe("other"))).isEqualTo(1);
}
}
diff --git a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
index 4a4f382098..377b0acca7 100644
--- a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java
@@ -630,10 +630,10 @@ void delex_unconditional_and_digest_guarded() {
redis.set(k, "bar");
String d = redis.digestKey(k); // new DIGEST command
// wrong condition: digestNotEqualHex (should abort)
- assertThat(redis.delex(k, ValueCondition. digestNotEqualHex(d))).isEqualTo(0);
+ assertThat(redis.delex(k, ValueCondition.digestNe(d))).isEqualTo(0);
assertThat(redis.exists(k)).isEqualTo(1);
// right condition: digestEqualHex (should delete)
- assertThat(redis.delex(k, ValueCondition. digestEqualHex(d))).isEqualTo(1);
+ assertThat(redis.delex(k, ValueCondition.digestEq(d))).isEqualTo(1);
assertThat(redis.exists(k)).isEqualTo(0);
}
@@ -644,14 +644,14 @@ void delex_with_value_equal_notEqual() {
String k = "k:delex-eq";
redis.set(k, "v1");
// wrong equality -> abort
- assertThat(redis.delex(k, ValueCondition.equal("nope"))).isEqualTo(0);
+ assertThat(redis.delex(k, ValueCondition.valueEq("nope"))).isEqualTo(0);
// correct equality -> delete
- assertThat(redis.delex(k, ValueCondition.equal("v1"))).isEqualTo(1);
+ assertThat(redis.delex(k, ValueCondition.valueEq("v1"))).isEqualTo(1);
// not-equal that fails (after deletion, recreate)
redis.set(k, "v2");
- assertThat(redis.delex(k, ValueCondition.notEqual("v2"))).isEqualTo(0);
+ assertThat(redis.delex(k, ValueCondition.valueNe("v2"))).isEqualTo(0);
// not-equal that succeeds
- assertThat(redis.delex(k, ValueCondition.notEqual("other"))).isEqualTo(1);
+ assertThat(redis.delex(k, ValueCondition.valueNe("other"))).isEqualTo(1);
}
@Test
@@ -660,9 +660,8 @@ void delex_on_missing_returns_0_for_any_condition() {
String k = "k:missing";
assertThat(redis.del(k)).isEqualTo(0);
- assertThat(redis.delex(k, ValueCondition.exists())).isEqualTo(0);
- assertThat(redis.delex(k, ValueCondition.equal("x"))).isEqualTo(0);
- assertThat(redis.delex(k, ValueCondition. digestEqualHex("0000000000000000"))).isEqualTo(0);
+ assertThat(redis.delex(k, ValueCondition.valueEq("x"))).isEqualTo(0);
+ assertThat(redis.delex(k, ValueCondition.digestEq("0000000000000000"))).isEqualTo(0);
}
}
diff --git a/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java
index 590315e69a..d8d186a15f 100644
--- a/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/commands/StringCommandIntegrationTests.java
@@ -497,17 +497,17 @@ void digestKey_returnsHex_and_changesOnValueChange() {
@Test
@EnabledOnCommand("DELEX")
- void set_with_ValueCondition_exists_notExists() {
+ void set_with_SetArgs_nx_xx() {
String k = "k:nx-xx";
- // key does not exist: EXISTS/XX should abort
- assertThat(redis.set(k, "v", ValueCondition. exists())).isNull();
- // NOT_EXISTS/NX should succeed
- assertThat(redis.set(k, "v", ValueCondition. notExists())).isEqualTo("OK");
- // NOT_EXISTS/NX should now abort
- assertThat(redis.set(k, "v2", ValueCondition. notExists())).isNull();
- // EXISTS/XX should succeed
- assertThat(redis.set(k, "v2", ValueCondition. exists())).isEqualTo("OK");
+ // key does not exist: XX should abort
+ assertThat(redis.set(k, "v", xx())).isNull();
+ // NX should succeed
+ assertThat(redis.set(k, "v", nx())).isEqualTo("OK");
+ // NX should now abort
+ assertThat(redis.set(k, "v2", nx())).isNull();
+ // XX should succeed
+ assertThat(redis.set(k, "v2", xx())).isEqualTo("OK");
assertThat(redis.get(k)).isEqualTo("v2");
}
@@ -518,16 +518,16 @@ void set_with_ValueCondition_equal_notEqual() {
String k = "k:ifeq-ifne";
redis.set(k, "v1");
// wrong equality value -> abort
- assertThat(redis.set(k, "v2", ValueCondition.equal("nope"))).isNull();
+ assertThat(redis.set(k, "v2", ValueCondition.valueEq("nope"))).isNull();
assertThat(redis.get(k)).isEqualTo("v1");
// correct equality value -> success
- assertThat(redis.set(k, "v2", ValueCondition.equal("v1"))).isEqualTo("OK");
+ assertThat(redis.set(k, "v2", ValueCondition.valueEq("v1"))).isEqualTo("OK");
assertThat(redis.get(k)).isEqualTo("v2");
// not-equal that fails (current is v2)
- assertThat(redis.set(k, "v3", ValueCondition.notEqual("v2"))).isNull();
+ assertThat(redis.set(k, "v3", ValueCondition.valueNe("v2"))).isNull();
assertThat(redis.get(k)).isEqualTo("v2");
// not-equal that succeeds
- assertThat(redis.set(k, "v3", ValueCondition.notEqual("other"))).isEqualTo("OK");
+ assertThat(redis.set(k, "v3", ValueCondition.valueNe("other"))).isEqualTo("OK");
assertThat(redis.get(k)).isEqualTo("v3");
}
@@ -538,10 +538,10 @@ void setget_with_ValueCondition_returnsPreviousValue() {
String k = "k:setget";
redis.set(k, "A");
// condition mismatch -> server returns previous value, value unchanged
- assertThat(redis.setGet(k, "B", ValueCondition.equal("X"))).isEqualTo("A");
+ assertThat(redis.setGet(k, "B", ValueCondition.valueEq("X"))).isEqualTo("A");
assertThat(redis.get(k)).isEqualTo("A");
// condition match -> returns previous and updates
- assertThat(redis.setGet(k, "C", ValueCondition.equal("A"))).isEqualTo("A");
+ assertThat(redis.setGet(k, "C", ValueCondition.valueEq("A"))).isEqualTo("A");
assertThat(redis.get(k)).isEqualTo("C");
}
@@ -554,11 +554,11 @@ void set_with_digest_conditions() {
String d = redis.digestKey(k);
assertThat(d).isNotNull();
// digest equal -> success
- assertThat(redis.set(k, "D", ValueCondition. digestEqualHex(d))).isEqualTo("OK");
+ assertThat(redis.set(k, "D", ValueCondition.digestEq(d))).isEqualTo("OK");
// reusing old digest equal -> abort
- assertThat(redis.set(k, "E", ValueCondition. digestEqualHex(d))).isNull();
+ assertThat(redis.set(k, "E", ValueCondition.digestEq(d))).isNull();
// digest not equal (against old digest) -> success
- assertThat(redis.set(k, "F", ValueCondition. digestNotEqualHex(d))).isEqualTo("OK");
+ assertThat(redis.set(k, "F", ValueCondition.digestNe(d))).isEqualTo("OK");
}
@Test
@@ -568,7 +568,7 @@ void set_with_digestNotEqual_fails_when_equal() {
redis.set(k, "X");
String d = redis.digestKey(k);
- assertThat(redis.set(k, "Y", ValueCondition. digestNotEqualHex(d))).isNull();
+ assertThat(redis.set(k, "Y", ValueCondition.digestNe(d))).isNull();
assertThat(redis.get(k)).isEqualTo("X");
}
@@ -580,10 +580,10 @@ void setget_with_digest_conditions() {
redis.set(k, "A");
String dA = redis.digestKey(k);
// match -> returns previous and updates
- assertThat(redis.setGet(k, "B", ValueCondition. digestEqualHex(dA))).isEqualTo("A");
+ assertThat(redis.setGet(k, "B", ValueCondition.digestEq(dA))).isEqualTo("A");
assertThat(redis.get(k)).isEqualTo("B");
// mismatch (using old digest) -> returns previous and does not update
- assertThat(redis.setGet(k, "C", ValueCondition. digestEqualHex(dA))).isEqualTo("B");
+ assertThat(redis.setGet(k, "C", ValueCondition.digestEq(dA))).isEqualTo("B");
assertThat(redis.get(k)).isEqualTo("B");
}
@@ -599,39 +599,39 @@ void digestKey_emptyValue() {
}
@Test
- void setget_with_exists_notExists_conditions() {
+ void setget_with_SetArgs_nx_xx() {
Assumptions.assumeTrue(RedisConditions.of(redis).hasVersionGreaterOrEqualsTo("8.3.224"));
String k1 = "k:setget-nx";
// NX when missing -> perform set, returns previous (null)
- assertThat(redis.setGet(k1, "A", ValueCondition.notExists())).isNull();
+ assertThat(redis.setGet(k1, "A", nx())).isNull();
assertThat(redis.get(k1)).isEqualTo("A");
// NX when present -> no set, returns previous
- assertThat(redis.setGet(k1, "B", ValueCondition.notExists())).isEqualTo("A");
+ assertThat(redis.setGet(k1, "B", nx())).isEqualTo("A");
assertThat(redis.get(k1)).isEqualTo("A");
String k2 = "k:setget-xx";
// XX when missing -> no-op, returns previous (null)
redis.del(k2);
- assertThat(redis.setGet(k2, "X", ValueCondition.exists())).isNull();
+ assertThat(redis.setGet(k2, "X", xx())).isNull();
assertThat(redis.get(k2)).isNull();
// XX when present -> set, returns previous
redis.set(k2, "Y");
- assertThat(redis.setGet(k2, "Z", ValueCondition.exists())).isEqualTo("Y");
+ assertThat(redis.setGet(k2, "Z", xx())).isEqualTo("Y");
assertThat(redis.get(k2)).isEqualTo("Z");
}
@Test
- void set_with_SetArgs_and_ValueCondition_combination() {
+ void set_with_SetArgs_nx_and_expiration() {
String k = "k:set-args-cond";
// NX + EX should set with TTL
Assumptions.assumeTrue(RedisConditions.of(redis).hasVersionGreaterOrEqualsTo("8.3.224"));
- assertThat(redis.set(k, "v1", ex(100), ValueCondition.notExists())).isEqualTo("OK");
+ assertThat(redis.set(k, "v1", ex(100).nx())).isEqualTo("OK");
assertThat(redis.ttl(k)).isGreaterThan(0);
// NX when present should abort and keep value/ttl
Long ttlBefore = redis.ttl(k);
- assertThat(redis.set(k, "v2", ex(100), ValueCondition.notExists())).isNull();
+ assertThat(redis.set(k, "v2", ex(100).nx())).isNull();
assertThat(redis.get(k)).isEqualTo("v1");
assertThat(redis.ttl(k)).isGreaterThan(0);
assertThat(redis.ttl(k)).isLessThanOrEqualTo(ttlBefore);
diff --git a/src/test/java/io/lettuce/core/masterreplica/MasterReplicaIntegrationTests.java b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaIntegrationTests.java
index d5aa5b82bb..2c7dc1c744 100644
--- a/src/test/java/io/lettuce/core/masterreplica/MasterReplicaIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaIntegrationTests.java
@@ -152,6 +152,31 @@ void testConnectToReplica() {
sync.set(key, value);
}
+ @Test
+ @EnabledOnCommand("DIGEST")
+ void digestKey_reads_from_replica() {
+
+ RedisCommands commands = connection.sync();
+ String k = "k:digest-replica";
+ String v = "replica-value";
+
+ // Write to upstream and wait for replica to catch up
+ commands.set(k, v);
+ commands.waitForReplication(1, 1000);
+
+ // Compute digest on upstream
+ connection.setReadFrom(ReadFrom.UPSTREAM);
+ String digestUpstream = commands.digestKey(k);
+
+ // Read digest from replica (read-only)
+ connection.setReadFrom(ReadFrom.REPLICA);
+ String digestReplica = commands.digestKey(k);
+
+ assertThat(digestReplica).isNotNull();
+ assertThat(digestReplica).hasSize(16);
+ assertThat(digestReplica).isEqualTo(digestUpstream);
+ }
+
@Test
void noReplicaForRead() {
From 893d3f12950094e489a789f6105aeb134a556cc5 Mon Sep 17 00:00:00 2001
From: "aleksandar.todorov"
Date: Thu, 6 Nov 2025 16:06:29 +0200
Subject: [PATCH 7/8] Format
---
src/main/java/io/lettuce/core/ValueCondition.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/java/io/lettuce/core/ValueCondition.java b/src/main/java/io/lettuce/core/ValueCondition.java
index 47b719ebf3..c9f7d6c0b2 100644
--- a/src/main/java/io/lettuce/core/ValueCondition.java
+++ b/src/main/java/io/lettuce/core/ValueCondition.java
@@ -68,6 +68,7 @@ public CommandKeyword keyword() {
public Param param() {
return param;
}
+
}
/** Parameter kind for condition arguments. */
From 77cd5549cb703fac7e2adcfc9a679f429373c72b Mon Sep 17 00:00:00 2001
From: "aleksandar.todorov"
Date: Thu, 6 Nov 2025 16:29:21 +0200
Subject: [PATCH 8/8] Revert unintentional changes
---
src/test/java/io/lettuce/apigenerator/Constants.java | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/test/java/io/lettuce/apigenerator/Constants.java b/src/test/java/io/lettuce/apigenerator/Constants.java
index 2d457f912d..7cee3129ac 100644
--- a/src/test/java/io/lettuce/apigenerator/Constants.java
+++ b/src/test/java/io/lettuce/apigenerator/Constants.java
@@ -27,7 +27,11 @@
*/
class Constants {
- public static final String[] TEMPLATE_NAMES = { "RedisKeyCommands" };
+ public static final String[] TEMPLATE_NAMES = { "BaseRedisCommands", "RedisAclCommands", "RedisFunctionCommands",
+ "RedisGeoCommands", "RedisHashCommands", "RedisHLLCommands", "RedisKeyCommands", "RedisListCommands",
+ "RedisScriptingCommands", "RedisSentinelCommands", "RedisServerCommands", "RedisSetCommands",
+ "RedisSortedSetCommands", "RedisStreamCommands", "RedisStringCommands", "RedisTransactionalCommands",
+ "RedisJsonCommands", "RedisVectorSetCommands", "RediSearchCommands" };
public static final File TEMPLATES = new File("src/main/templates");