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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,11 @@ public RedisFuture<Long> del(Iterable<K> keys) {
return dispatch(commandBuilder.del(keys));
}

@Override
public RedisFuture<Long> delex(K key, ValueCondition<V> condition) {
return dispatch(commandBuilder.delex(key, condition));
}

@Override
public String digest(String script) {
return digest(encodeScript(script));
Expand Down Expand Up @@ -1265,6 +1270,11 @@ public RedisFuture<V> get(K key) {
return dispatch(commandBuilder.get(key));
}

@Override
public RedisFuture<String> digestKey(K key) {
return dispatch(commandBuilder.digestKey(key));
}

public StatefulConnection<K, V> getConnection() {
return connection;
}
Expand Down Expand Up @@ -2675,6 +2685,26 @@ public RedisFuture<String> set(K key, V value, SetArgs setArgs) {
return dispatch(commandBuilder.set(key, value, setArgs));
}

@Override
public RedisFuture<String> set(K key, V value, ValueCondition<V> condition) {
return dispatch(commandBuilder.set(key, value, condition));
}

@Override
public RedisFuture<String> set(K key, V value, SetArgs setArgs, ValueCondition<V> condition) {
return dispatch(commandBuilder.set(key, value, setArgs, condition));
}

@Override
public RedisFuture<V> setGet(K key, V value, ValueCondition<V> condition) {
return dispatch(commandBuilder.setGet(key, value, condition));
}

@Override
public RedisFuture<V> setGet(K key, V value, SetArgs setArgs, ValueCondition<V> condition) {
return dispatch(commandBuilder.setGet(key, value, setArgs, condition));
}

@Override
public RedisFuture<V> setGet(K key, V value) {
return dispatch(commandBuilder.setGet(key, value));
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,11 @@ public Mono<Long> del(Iterable<K> keys) {
return createMono(() -> commandBuilder.del(keys));
}

@Override
public Mono<Long> delex(K key, ValueCondition<V> condition) {
return createMono(() -> commandBuilder.delex(key, condition));
}

@Override
public String digest(String script) {
return digest(encodeScript(script));
Expand Down Expand Up @@ -1326,6 +1331,11 @@ public Mono<V> get(K key) {
return createMono(() -> commandBuilder.get(key));
}

@Override
public Mono<String> digestKey(K key) {
return createMono(() -> commandBuilder.digestKey(key));
}

public StatefulConnection<K, V> getConnection() {
return connection;
}
Expand Down Expand Up @@ -2758,6 +2768,26 @@ public Mono<String> set(K key, V value, SetArgs setArgs) {
return createMono(() -> commandBuilder.set(key, value, setArgs));
}

@Override
public Mono<String> set(K key, V value, ValueCondition<V> condition) {
return createMono(() -> commandBuilder.set(key, value, condition));
}

@Override
public Mono<String> set(K key, V value, SetArgs setArgs, ValueCondition<V> condition) {
return createMono(() -> commandBuilder.set(key, value, setArgs, condition));
}

@Override
public Mono<V> setGet(K key, V value, ValueCondition<V> condition) {
return createMono(() -> commandBuilder.setGet(key, value, condition));
}

@Override
public Mono<V> setGet(K key, V value, SetArgs setArgs, ValueCondition<V> condition) {
return createMono(() -> commandBuilder.setGet(key, value, setArgs, condition));
}

@Override
public Mono<V> setGet(K key, V value) {
return createMono(() -> commandBuilder.setGet(key, value));
Expand Down
58 changes: 58 additions & 0 deletions src/main/java/io/lettuce/core/RedisCommandBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,15 @@ Command<K, V, Long> del(Iterable<K> keys) {
return createCommand(DEL, new IntegerOutput<>(codec), args);
}

Command<K, V, Long> delex(K key, ValueCondition<V> condition) {
notNullKey(key);
LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
condition.build(args);
return createCommand(DELEX, new IntegerOutput<>(codec), args);
}

Command<K, V, String> discard() {
return createCommand(DISCARD, new StatusOutput<>(codec));
}
Expand Down Expand Up @@ -2702,6 +2711,55 @@ Command<K, V, String> set(K key, V value, SetArgs setArgs) {
return createCommand(SET, new StatusOutput<>(codec), args);
}

Command<K, V, String> digestKey(K key) {
notNullKey(key);

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
return createCommand(DIGEST, new StatusOutput<>(codec), args);
}

Command<K, V, String> set(K key, V value, ValueCondition<V> condition) {
notNullKey(key);
LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key).addValue(value);
condition.build(args);
return createCommand(SET, new StatusOutput<>(codec), args);
}

Command<K, V, String> set(K key, V value, SetArgs setArgs, ValueCondition<V> condition) {
notNullKey(key);
LettuceAssert.notNull(setArgs, "SetArgs " + MUST_NOT_BE_NULL);
LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key).addValue(value);
setArgs.build(args);
condition.build(args);
return createCommand(SET, new StatusOutput<>(codec), args);
}

Command<K, V, V> setGet(K key, V value, ValueCondition<V> condition) {
notNullKey(key);
LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key).addValue(value);
condition.build(args);
args.add(GET);
return createCommand(SET, new ValueOutput<>(codec), args);
}

Command<K, V, V> setGet(K key, V value, SetArgs setArgs, ValueCondition<V> condition) {
notNullKey(key);
LettuceAssert.notNull(setArgs, "SetArgs " + MUST_NOT_BE_NULL);
LettuceAssert.notNull(condition, "ValueCondition " + MUST_NOT_BE_NULL);

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key).addValue(value);
setArgs.build(args);
condition.build(args);
args.add(GET);
return createCommand(SET, new ValueOutput<>(codec), args);
}

Command<K, V, V> setGet(K key, V value) {
return setGet(key, value, new SetArgs());
}
Expand Down
164 changes: 164 additions & 0 deletions src/main/java/io/lettuce/core/ValueCondition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* 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;

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
* DELEX). This abstraction lets callers express value-based or digest-based comparisons without exploding method overloads.
*
* <p>
* Digest-based comparisons use a 64-bit XXH3 digest represented as a 16-character lower-case hexadecimal string.
* </p>
*
* @param <V> value type used for value-based comparisons
*/
@Experimental
public final class ValueCondition<V> {

/**
* The kind of condition represented by this instance.
*/
public enum Kind {

/** current value must equal provided value */
EQUAL(CommandKeyword.IFEQ, Param.VALUE),
/** current value must not equal provided value */
NOT_EQUAL(CommandKeyword.IFNE, Param.VALUE),
/** current value's digest must equal provided digest */
DIGEST_EQUAL(CommandKeyword.IFDEQ, Param.DIGEST),
/** current value's digest must not equal provided digest */
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;

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;
}

/**
* Append this condition's protocol arguments to the given args.
*/
public <K> void build(CommandArgs<K, V> args) {
args.add(kind.keyword());
switch (kind.param()) {
case VALUE:
args.addValue(value);
break;
case DIGEST:
args.add(digestHex);
break;
default:
break;
}
}

// 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 <V> ValueCondition<V> valueEq(V value) {
if (value == null)
throw new IllegalArgumentException("value must not be null");
return new ValueCondition<>(Kind.EQUAL, value, null);
}

/** Create a value-based inequality condition; succeeds only if the current value does not equal the given value. */
public static <V> ValueCondition<V> valueNe(V value) {
if (value == null)
throw new IllegalArgumentException("value must not be null");
return new ValueCondition<>(Kind.NOT_EQUAL, value, null);
}

/**
* 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 <V> ValueCondition<V> digestEq(String hex16Digest) {
if (hex16Digest == null)
throw new IllegalArgumentException("digest must not be null");
return new ValueCondition<>(Kind.DIGEST_EQUAL, null, 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 <V> ValueCondition<V> digestNe(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 + '\'' : "") + '}';
}

}
30 changes: 26 additions & 4 deletions src/main/java/io/lettuce/core/api/async/RedisKeyAsyncCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,24 @@
*/
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.annotations.Experimental;
import io.lettuce.core.RedisFuture;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;

Expand Down Expand Up @@ -68,6 +80,16 @@ public interface RedisKeyAsyncCommands<K, V> {
*/
RedisFuture<Long> del(K... keys);

/**
* 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.
*/
@Experimental
RedisFuture<Long> delex(K key, ValueCondition<V> condition);

/**
* Unlink one or more keys (non blocking DEL).
*
Expand Down
Loading