Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for Redis setGet command #3017

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-GH-2853-SNAPSHOT</version>

<name>Spring Data Redis</name>
<description>Spring Data module for Redis</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,11 @@ public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption op
return convertAndReturn(delegate.set(key, value, expiration, option), Converters.identityConverter());
}

@Override
public byte[] setGet(byte[] key, byte[] value, Expiration expiration, SetOption option) {
return convertAndReturn(delegate.setGet(key, value, expiration, option), Converters.identityConverter());
}

@Override
public Boolean setBit(byte[] key, long offset, boolean value) {
return convertAndReturn(delegate.setBit(key, offset, value), Converters.identityConverter());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,13 @@ default Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption o
return stringCommands().set(key, value, expiration, option);
}

/** @deprecated in favor of {@link RedisConnection#stringCommands()}}. */
@Override
@Deprecated
default byte[] setGet(byte[] key, byte[] value, Expiration expiration, SetOption option) {
return stringCommands().setGet(key, value, expiration, option);
}

/** @deprecated in favor of {@link RedisConnection#stringCommands()}}. */
@Override
@Deprecated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Marcin Grzejszczak
* @since 2.0
*/
public interface ReactiveStringCommands {
Expand Down Expand Up @@ -193,6 +194,41 @@ default Mono<Boolean> set(ByteBuffer key, ByteBuffer value, Expiration expiratio
*/
Flux<BooleanResponse<SetCommand>> set(Publisher<SetCommand> commands);

/**
* Set {@literal value} for {@literal key} with {@literal expiration} and {@literal options}. Return the old
* string stored at key, or nil if key did not exist. An error is returned and SET aborted if the value
* stored at key is not a string.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @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
* @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
* @since 3.4
*/
@Nullable
default Mono<ByteBuffer> setGet(ByteBuffer key, ByteBuffer value, Expiration expiration, SetOption option) {

Assert.notNull(key, "Key must not be null");
Assert.notNull(value, "Value must not be null");

return setGet(Mono.just(SetCommand.set(key).value(value).withSetOption(option).expiring(expiration))).next()
.map(CommandResponse::getOutput);
}

/**
* Set each and every item separately by invoking {@link SetCommand}. Return the old
* string stored at key, or nil if key did not exist. An error is returned and SET aborted if the value
* stored at key is not a string.
*
* @param commands must not be {@literal null}.
* @return {@link Flux} of {@link ByteBufferResponse} holding the {@link SetCommand} along with the command result.
* @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
*/
Flux<ByteBufferResponse<SetCommand>> setGet(Publisher<SetCommand> commands);

/**
* Get single element stored at {@literal key}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,22 @@ enum BitOperation {
@Nullable
Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption option);

/**
* Set {@code value} for {@code key}. Return the old string stored at key, or nil if key did not exist.
* An error is returned and SET aborted if the value stored at key is not a string.
*
* @param key must not be {@literal null}.
* @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.
* @return {@literal null} when used in pipeline / transaction.
* @since 3.4
* @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
*/
@Nullable
byte[] setGet(byte[] key, byte[] value, Expiration expiration, SetOption option);

/**
* Set {@code value} for {@code key}, only if {@code key} does not exist.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,24 @@ public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption op
}
}

@Override
public byte[] setGet(byte[] key, byte[] value, Expiration expiration, SetOption option) {

Assert.notNull(key, "Key must not be null");
Assert.notNull(value, "Value must not be null");
Assert.notNull(expiration, "Expiration must not be null");
Assert.notNull(option, "Option must not be null");

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

try {
return connection.getCluster().setGet(key, value, setParams);
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}

@Override
public Boolean setNX(byte[] key, byte[] value) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,20 @@ public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption op
.getOrElse(Converters.stringToBooleanConverter(), () -> false);
}

@Override
@Nullable
public byte[] setGet(byte[] key, byte[] value, Expiration expiration, SetOption option) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(value, "Value must not be null");
Assert.notNull(expiration, "Expiration must not be null");
Assert.notNull(option, "Option must not be null");

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

return connection.invoke().just(Jedis::setGet, PipelineBinaryCommands::setGet, key, value, params);
}

@Override
public Boolean setNX(byte[] key, byte[] value) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,19 @@ public Flux<BooleanResponse<SetCommand>> set(Publisher<SetCommand> commands) {
}));
}

@Override
public Flux<ByteBufferResponse<SetCommand>> setGet(Publisher<SetCommand> commands) {
return this.connection.execute(reactiveCommands -> Flux.from(commands).concatMap((command) -> {

Assert.notNull(command.getKey(), "Key must not be null");
Assert.notNull(command.getValue(), "Value must not be null");

return reactiveCommands.setGet(command.getKey(), command.getValue())
.map(v -> new ByteBufferResponse<>(command, v))
.defaultIfEmpty(new AbsentByteBufferResponse<>(command));
}));
}

@Override
public Flux<ByteBufferResponse<SetCommand>> getSet(Publisher<SetCommand> commands) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,18 @@ public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption op
.orElse(LettuceConverters.stringToBooleanConverter(), false);
}

@Override
@Nullable
public byte[] setGet(byte[] key, byte[] value, Expiration expiration, SetOption option) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(value, "Value must not be null");
Assert.notNull(expiration, "Expiration must not be null");
Assert.notNull(option, "Option must not be null");

return connection.invoke()
.just(RedisStringAsyncCommands::setGet, key, value, LettuceConverters.toSetArgs(expiration, option));
}

@Override
public Boolean setNX(byte[] key, byte[] value) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ public interface BoundValueOperations<K, V> extends BoundKeyOperations<K> {
*/
void set(V value, long timeout, TimeUnit unit);

/**
* Set the {@code value} and expiration {@code timeout} for the bound key. Return the old
* string stored at key, or nil if key did not exist. An error is returned and SET aborted if the value
* stored at key is not a string.
*
* @param value must not be {@literal null}.
* @param timeout
* @param unit must not be {@literal null}.
* @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
* @since 3.4
*/
V setGet(V value, long timeout, TimeUnit unit);

/**
* Set the {@code value} and expiration {@code timeout} for the bound key.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ public Mono<Boolean> set(K key, V value, Duration timeout) {
stringCommands.set(rawKey(key), rawValue(value), Expiration.from(timeout), SetOption.UPSERT));
}

@Override
public Mono<V> setGet(K key, V value, Duration timeout) {

Assert.notNull(key, "Key must not be null");
Assert.notNull(value, "Value must not be null");
Assert.notNull(timeout, "Duration must not be null");

return createMono(stringCommands ->
stringCommands.setGet(rawKey(key), rawValue(value), Expiration.from(timeout), SetOption.UPSERT))
.map(this::readRequiredValue);
}

@Override
public Mono<Boolean> setIfAbsent(K key, V value) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,27 @@ private boolean failsafeInvokePsetEx(RedisConnection connection) {
});
}

@Override
public V setGet(K key, V value, long timeout, TimeUnit unit) {
return doSetGet(key, value, Expiration.from(timeout, unit));
}

@Override
public V setGet(K key, V value, Duration duration) {
return doSetGet(key, value, Expiration.from(duration));
}

private V doSetGet(K key, V value, Expiration duration) {
byte[] rawValue = rawValue(value);
return execute( new ValueDeserializingRedisCallback(key) {

@Override
protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
return connection.stringCommands().setGet(rawKey, rawValue, duration, SetOption.UPSERT);
}
});
}

@Override
public Boolean setIfAbsent(K key, V value) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ public interface ReactiveValueOperations<K, V> {
*/
Mono<Boolean> set(K key, V value, Duration timeout);

/**
* Set the {@code value} and expiration {@code timeout} for {@code key}. Return the old
* string stored at key, or nil if key did not exist. An error is returned and SET aborted if the value
* stored at key is not a string.
*
* @param key must not be {@literal null}.
* @param value
* @param timeout must not be {@literal null}.
* @see <a href="https://redis.io/commands/setex">Redis Documentation: SETEX</a>
*/
Mono<V> setGet(K key, V value, Duration timeout);

/**
* Set {@code key} to hold the string {@code value} if {@code key} is absent.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
* @author Christoph Strobl
* @author Mark Paluch
* @author Jiahe Cai
* @author Marcin Grzejszczak
*/
public interface ValueOperations<K, V> {

Expand All @@ -44,6 +45,33 @@ public interface ValueOperations<K, V> {
*/
void set(K key, V value);

/**
* Set the {@code value} and expiration {@code timeout} for {@code key}. Return the old
* string stored at key, or nil if key did not exist. An error is returned and SET aborted if the value
* stored at key is not a string.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @param timeout the key expiration timeout.
* @param unit must not be {@literal null}.
* @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
* @since 3.4
*/
V setGet(K key, V value, long timeout, TimeUnit unit);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to update BoundValueOperations (the interface) and ReactiveValueOperations as well.


/**
* Set the {@code value} and expiration {@code timeout} for {@code key}. Return the old
* string stored at key, or nil if key did not exist. An error is returned and SET aborted if the value
* stored at key is not a string.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @param duration expiration duration
* @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
* @since 3.4
*/
V setGet(K key, V value, Duration duration);

/**
* Set the {@code value} and expiration {@code timeout} for {@code key}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,11 @@ public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption op
return delegate.set(key, value, expiration, options);
}

@Override
public byte[] setGet(byte[] key, byte[] value, Expiration expiration, SetOption option) {
return delegate.setGet(key, value, expiration, option);
}

@Override
public List<Long> bitField(byte[] key, BitFieldSubCommands subCommands) {
return delegate.bitField(key, subCommands);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -556,4 +556,29 @@ void setKeepTTL() {
assertThat(nativeBinaryCommands.ttl(KEY_1_BBUFFER)).isCloseTo(expireSeconds, Offset.offset(5L));
assertThat(nativeCommands.get(KEY_1)).isEqualTo(VALUE_2);
}

@ParameterizedRedisTest // GH-2853
void setGetMono() {
nativeCommands.set(KEY_1, VALUE_1);

connection.stringCommands().setGet(KEY_1_BBUFFER, VALUE_2_BBUFFER, Expiration.keepTtl(), SetOption.upsert())
.as(StepVerifier::create) //
.expectNext(VALUE_1_BBUFFER) //
.verifyComplete();

assertThat(nativeCommands.get(KEY_1)).isEqualTo(VALUE_2);
}

@ParameterizedRedisTest // GH-2853
void setGetFlux() {
nativeCommands.set(KEY_1, VALUE_1);

connection.stringCommands().setGet(Mono.just(SetCommand.set(KEY_1_BBUFFER).value(VALUE_2_BBUFFER).expiring(Expiration.keepTtl()).withSetOption( SetOption.upsert())))
.map(CommandResponse::getOutput)
.as(StepVerifier::create) //
.expectNext(VALUE_1_BBUFFER) //
.verifyComplete();

assertThat(nativeCommands.get(KEY_1)).isEqualTo(VALUE_2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,32 @@ void testSetWithExpirationWithTimeUnitMilliseconds() {
await().atMost(Duration.ofMillis(500L)).until(() -> !redisTemplate.hasKey(key));
}

@ParameterizedRedisTest
void testSetGetWithExpiration() {

K key = keyFactory.instance();
V value1 = valueFactory.instance();
V value2 = valueFactory.instance();

valueOps.set(key, value1);

assertThat(valueOps.setGet(key, value2, 1, TimeUnit.SECONDS)).isEqualTo(value1);
assertThat(valueOps.get(key)).isEqualTo(value2);
}

@ParameterizedRedisTest
void testSetGetWithExpirationDuration() {

K key = keyFactory.instance();
V value1 = valueFactory.instance();
V value2 = valueFactory.instance();

valueOps.set(key, value1);

assertThat(valueOps.setGet(key, value2, Duration.ofMillis(1000))).isEqualTo(value1);
assertThat(valueOps.get(key)).isEqualTo(value2);
}

@ParameterizedRedisTest
void testAppend() {

Expand Down
Loading