Skip to content
Closed
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

VERSION?=8.2.2
VERSION?=8.4.0
PROJECT?=redis
GH_ORG?=redis
SPRING_PROFILE?=ci
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ void doLock(String name, Object contextualKey, @Nullable Object contextualValue,
Expiration expiration = Expiration.from(this.lockTtl.getTimeToLive(contextualKey, contextualValue));
byte[] cacheLockKey = createCacheLockKey(name);

while (!ObjectUtils.nullSafeEquals(commands.set(cacheLockKey, new byte[0], expiration, SetOption.SET_IF_ABSENT),
while (!ObjectUtils.nullSafeEquals(commands.set(cacheLockKey, new byte[0], expiration, SetOption.ifAbsent()),
true)) {
checkAndPotentiallyWaitUntilUnlocked(name, connection);
}
Expand Down Expand Up @@ -818,7 +818,7 @@ private Mono<Object> doLock(String name, Object contextualKey, @Nullable Object
ByteBuffer value = ByteBuffer.wrap(new byte[0]);
Expiration expiration = Expiration.from(lockTtl.getTimeToLive(contextualKey, contextualValue));

return connection.stringCommands().set(key, value, expiration, SetOption.SET_IF_ABSENT) //
return connection.stringCommands().set(key, value, expiration, SetOption.ifAbsent()) //
// Ensure we emit an object, otherwise, the Mono.usingWhen operator doesn't run the inner resource function.
.thenReturn(Boolean.TRUE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public interface ReactiveStringCommands {
* {@code SET} command parameters.
*
* @author Christoph Strobl
* @author Yordan Tsintsov
* @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
*/
class SetCommand extends KeyCommand {
Expand All @@ -64,8 +65,8 @@ class SetCommand extends KeyCommand {
private final @Nullable Expiration expiration;
private final @Nullable SetOption option;

private SetCommand(@Nullable ByteBuffer key, @Nullable ByteBuffer value, @Nullable Expiration expiration,
@Nullable SetOption option) {
private SetCommand(@Nullable ByteBuffer key, @Nullable ByteBuffer value,
@Nullable Expiration expiration, @Nullable SetOption option) {

super(key);

Expand Down Expand Up @@ -141,11 +142,15 @@ public Optional<Expiration> getExpiration() {
}

/**
* @return
* Returns the {@link SetOption} to apply, if set.
*
* @return {@link Optional} containing the {@link SetOption}, or empty if not set.
* @since 4.1
*/
public Optional<SetOption> getOption() {
return Optional.ofNullable(option);
}

}

/**
Expand All @@ -172,7 +177,8 @@ default Mono<Boolean> set(ByteBuffer key, ByteBuffer value) {
* @param expiration must not be {@literal null}. Use {@link Expiration#persistent()} for no expiration time or
* {@link Expiration#keepTtl()} to keep the existing.
* @param option must not be {@literal null}.
* @return
* @return Mono emitting {@literal true} if {@code SET} was executed and resulted in an update of the value
* or {@literal false} otherwise.
* @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
*/
default Mono<Boolean> set(ByteBuffer key, ByteBuffer value, Expiration expiration, SetOption option) {
Expand Down Expand Up @@ -203,7 +209,7 @@ default Mono<Boolean> set(ByteBuffer key, ByteBuffer value, Expiration expiratio
* @param expiration must not be {@literal null}. Use {@link Expiration#persistent()} for no expiration time or
* {@link Expiration#keepTtl()} to keep the existing.
* @param option must not be {@literal null}.
* @return
* @return Mono emitting the previous value if present.
* @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
* @since 3.5
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@
*/
package org.springframework.data.redis.connection;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullUnmarked;

import org.jspecify.annotations.Nullable;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.util.Assert;

/**
* String/Value-specific commands supported by Redis.
Expand Down Expand Up @@ -135,7 +139,7 @@ enum BitOperation {
* @param value must not be {@literal null}.
* @param expiration must not be {@literal null}. Use {@link Expiration#persistent()} to not set any ttl or
* {@link Expiration#keepTtl()} to keep the existing expiration.
* @param option must not be {@literal null}. Use {@link SetOption#upsert()} to add non existing.
* @param option must not be {@literal null}. Use {@link SetOption#upsert()} to add non-existing.
* @return {@literal null} when used in pipeline / transaction.
* @since 1.7
* @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
Expand All @@ -155,8 +159,7 @@ enum BitOperation {
* @since 3.5
* @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
*/
byte[] setGet(byte @NonNull [] key, byte @NonNull [] value, @NonNull Expiration expiration,
@NonNull SetOption option);
byte[] setGet(byte @NonNull [] key, byte @NonNull [] value, @NonNull Expiration expiration, @NonNull SetOption option);

/**
* Set {@code value} for {@code key}, only if {@code key} does not exist.
Expand Down Expand Up @@ -392,48 +395,184 @@ default Long bitPos(byte @NonNull [] key, boolean bit) {
Long strLen(byte @NonNull [] key);

/**
* {@code SET} command arguments for {@code NX}, {@code XX}.
* {@code SET} command arguments for {@code NX}, {@code XX}, {@code IFEQ}, {@code IFNE}.
* <p>
* Supports compare-and-swap (CAS) semantics introduced in Redis 8.4.
*
* @author Christoph Strobl
* @see <a href="https://redis.io/commands/set">Redis SET command</a>
* @since 1.7
*/
enum SetOption {
@NullUnmarked
class SetOption {

// Cached instances for stateless options
public static final SetOption UPSERT = new SetOption(Type.UPSERT, null);
public static final SetOption SET_IF_ABSENT = new SetOption(Type.SET_IF_ABSENT, null);
public static final SetOption SET_IF_PRESENT = new SetOption(Type.SET_IF_PRESENT, null);

private final Type type;
private final byte @Nullable [] compareValue;

private SetOption(Type type, byte @Nullable [] compareValue) {
this.type = type;
this.compareValue = compareValue;
}

/**
* Do not set any additional command argument.
* Creates a condition that always sets the value, regardless of whether the key exists.
* <p>
* This is the default Redis {@code SET} behavior when no condition is specified.
*
* @return a cached {@link SetOption} instance representing no precondition.
*/
UPSERT,
public static SetOption upsert() {
return UPSERT;
}

/**
* {@code NX}
* Creates a condition that sets the value only if the key does not already exist.
* <p>
* Corresponds to the Redis {@code NX} option.
*
* @return a cached {@link SetOption} instance for the {@code NX} condition.
*/
SET_IF_ABSENT,
public static SetOption ifAbsent() {
return SET_IF_ABSENT;
}

/**
* {@code XX}
* Creates a condition that sets the value only if the key already exists.
* <p>
* Corresponds to the Redis {@code XX} option.
*
* @return a cached {@link SetOption} instance for the {@code XX} condition.
*/
SET_IF_PRESENT;
public static SetOption ifPresent() {
return SET_IF_PRESENT;
}

/**
* Do not set any additional command argument.
* Creates a condition that sets the value only if the current value stored at the key
* equals the specified {@code value}.
* <p>
* The operation will succeed only if:
* <ul>
* <li>The key exists, AND</li>
* <li>The current value exactly matches the provided {@code value}</li>
* </ul>
* <p>
* Corresponds to the Redis {@code IFEQ} option introduced in Redis 8.4.
* <p>
* <b>Note:</b> Empty byte arrays are valid comparison values.
*
* @param value the expected current value to compare against; must not be {@literal null}.
* @return a new {@link SetOption} instance for the {@code IFEQ} condition.
* @throws IllegalArgumentException if {@code value} is {@literal null}.
*/
public static SetOption upsert() {
return UPSERT;
public static SetOption ifEqual(byte[] value) {
Assert.notNull(value, "Value must not be null");
return new SetOption(Type.SET_IF_VALUE_EQUAL, value);
}

/**
* {@code XX}
* Creates a condition that sets the value if the current value stored at the key
* does not equal the specified {@code value} or the key does not exist.
* <p>
* The operation will succeed if:
* <ul>
* <li>The key does not exist, OR</li>
* <li>The current value does not match the provided {@code value}</li>
* </ul>
* <p>
* Corresponds to the Redis {@code IFNE} option introduced in Redis 8.4.
* <p>
* <b>Note:</b> Empty byte arrays are valid comparison values.
*
* @param value the expected current value to compare against; must not be {@literal null}.
* @return a new {@link SetOption} instance for the {@code IFNE} condition.
* @throws IllegalArgumentException if {@code value} is {@literal null}.
*/
public static SetOption ifPresent() {
return SET_IF_PRESENT;
public static SetOption ifNotEqual(byte[] value) {
Assert.notNull(value, "Value must not be null");
return new SetOption(Type.SET_IF_VALUE_NOT_EQUAL, value);
}

/**
* {@code NX}
* Returns the type of condition represented by this instance.
*
* @return the condition {@link Type}; never {@literal null}.
*/
public static SetOption ifAbsent() {
return SET_IF_ABSENT;
public Type getType() {
return this.type;
}

/**
* Returns the comparison value.
*
* @return the comparison value; never {@literal null}
*/
public byte[] getCompareValue() {
Assert.notNull(this.compareValue, "Compare value must not be null");
return this.compareValue;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SetOption that = (SetOption) o;
return type == that.type && Arrays.equals(compareValue, that.compareValue);
}

@Override
public int hashCode() {
return Objects.hash(type, Arrays.hashCode(compareValue));
}

@Override
public String toString() {
return "%s{type=%s, compareValue=<%s>}".formatted(getClass().getSimpleName(), type, compareValue != null ? compareValue.length + " bytes" : "none");
}

/**
* {@code SET} command options for {@code NX}, {@code XX}, {@code IFEQ}.
*
* @author Yordan Tsintsov
* @since 4.1
*/
public enum Type {

/**
* Do not set any additional command argument.
*/
UPSERT,

/**
* {@code NX}
*/
SET_IF_ABSENT,

/**
* {@code XX}
*/
SET_IF_PRESENT,

/**
* {@code IFEQ}
*/
SET_IF_VALUE_EQUAL,

/**
* {@code IFNE}
*/
SET_IF_VALUE_NOT_EQUAL
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ default Boolean pExpireAt(@NonNull String key, long unixTimeInMillis) {
* @param value must not be {@literal null}.
* @param expiration can be {@literal null}. Defaulted to {@link Expiration#persistent()}. Use
* {@link Expiration#keepTtl()} to keep the existing expiration.
* @param option can be {@literal null}. Defaulted to {@link SetOption#UPSERT}.
* @param option can be {@literal null}. Defaulted to {@link SetOption#upsert()}.
* @since 1.7
* @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
* @see RedisStringCommands#set(byte[], byte[], Expiration, SetOption)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public Boolean set(byte @NonNull [] key, byte @NonNull [] value, @NonNull Expira
Assert.notNull(option, "Option must not be null");

SetParams setParams = JedisConverters.toSetCommandExPxArgument(expiration,
JedisConverters.toSetCommandNxXxArgument(option));
JedisConverters.toSetCommandArgument(option));

try {
return Converters.stringToBoolean(connection.getCluster().set(key, value, setParams));
Expand All @@ -162,7 +162,7 @@ public byte[] setGet(byte @NonNull [] key, byte @NonNull [] value, @NonNull Expi
Assert.notNull(option, "Option must not be null");

SetParams setParams = JedisConverters.toSetCommandExPxArgument(expiration,
JedisConverters.toSetCommandNxXxArgument(option));
JedisConverters.toSetCommandArgument(option));

try {
return connection.getCluster().setGet(key, value, setParams);
Expand Down
Loading
Loading