DATAREDIS-696 - Add support for REPLACE option to RESTORE.

We bypass the lack of driver support by directly executing a custom command.

Original pull request: #344.
This commit is contained in:
Christoph Strobl
2018-05-25 13:55:19 +02:00
committed by Mark Paluch
parent 64dee51919
commit 9e74556f74
13 changed files with 134 additions and 31 deletions

View File

@@ -1668,11 +1668,11 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisKeyCommands#restore(byte[], long, byte[])
* @see org.springframework.data.redis.connection.RedisKeyCommands#restore(byte[], long, byte[], boolean)
*/
@Override
public void restore(byte[] key, long ttlInMillis, byte[] serializedValue) {
delegate.restore(key, ttlInMillis, serializedValue);
public void restore(byte[] key, long ttlInMillis, byte[] serializedValue, boolean replace) {
delegate.restore(key, ttlInMillis, serializedValue, replace);
}
/*

View File

@@ -149,8 +149,8 @@ public interface DefaultedRedisConnection extends RedisConnection {
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
@Override
@Deprecated
default void restore(byte[] key, long ttlInMillis, byte[] serializedValue) {
keyCommands().restore(key, ttlInMillis, serializedValue);
default void restore(byte[] key, long ttlInMillis, byte[] serializedValue, boolean replace) {
keyCommands().restore(key, ttlInMillis, serializedValue, replace);
}
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */

View File

@@ -303,7 +303,21 @@ public interface RedisKeyCommands {
* @param serializedValue must not be {@literal null}.
* @see <a href="http://redis.io/commands/restore">Redis Documentation: RESTORE</a>
*/
void restore(byte[] key, long ttlInMillis, byte[] serializedValue);
default void restore(byte[] key, long ttlInMillis, byte[] serializedValue) {
restore(key, ttlInMillis, serializedValue, false);
}
/**
* Create {@code key} using the {@code serializedValue}, previously obtained using {@link #dump(byte[])}.
*
* @param key must not be {@literal null}.
* @param ttlInMillis
* @param serializedValue must not be {@literal null}.
* @param replace use {@literal true} to replace a potentially existing value instead of erroring.
* @since 2.1
* @see <a href="http://redis.io/commands/restore">Redis Documentation: RESTORE</a>
*/
void restore(byte[] key, long ttlInMillis, byte[] serializedValue, boolean replace);
/**
* Get the type of internal representation used for storing the value at the given {@code key}.

View File

@@ -476,10 +476,10 @@ class JedisClusterKeyCommands implements RedisKeyCommands {
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisKeyCommands#restore(byte[], long, byte[])
* @see org.springframework.data.redis.connection.RedisKeyCommands#restore(byte[], long, byte[], boolean)
*/
@Override
public void restore(byte[] key, long ttlInMillis, byte[] serializedValue) {
public void restore(byte[] key, long ttlInMillis, byte[] serializedValue, boolean replace) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(serializedValue, "Serialized value must not be null!");
@@ -488,9 +488,17 @@ class JedisClusterKeyCommands implements RedisKeyCommands {
throw new UnsupportedOperationException("Jedis does not support ttlInMillis exceeding Integer.MAX_VALUE.");
}
connection.getClusterCommandExecutor()
.executeCommandOnSingleNode((JedisClusterCommandCallback<String>) client -> client.restore(key,
Long.valueOf(ttlInMillis).intValue(), serializedValue), connection.clusterGetNodeForKey(key));
connection.getClusterCommandExecutor().executeCommandOnSingleNode((JedisClusterCommandCallback<String>) client -> {
if (!replace) {
return client.restore(key, Long.valueOf(ttlInMillis).intValue(), serializedValue);
}
return JedisConverters.toString(this.connection.execute("RESTORE", key,
Arrays.asList(JedisConverters.toBytes(ttlInMillis), serializedValue, JedisConverters.toBytes
("REPLACE"))));
}, connection.clusterGetNodeForKey(key));
}
/*

View File

@@ -21,6 +21,7 @@ import redis.clients.jedis.ScanParams;
import redis.clients.jedis.SortingParams;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -675,14 +676,22 @@ class JedisKeyCommands implements RedisKeyCommands {
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisKeyCommands#restore(byte[], long, byte[])
* @see org.springframework.data.redis.connection.RedisKeyCommands#restore(byte[], long, byte[], boolean)
*/
@Override
public void restore(byte[] key, long ttlInMillis, byte[] serializedValue) {
public void restore(byte[] key, long ttlInMillis, byte[] serializedValue, boolean replace) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(serializedValue, "Serialized value must not be null!");
if (replace) {
this.connection.execute("RESTORE", new byte[][] { key, JedisConverters.toBytes(ttlInMillis),
serializedValue,
JedisConverters.toBytes("REPLACE") });
return;
}
if (ttlInMillis > Integer.MAX_VALUE) {
throw new IllegalArgumentException("TtlInMillis must be less than Integer.MAX_VALUE for restore in Jedis.");
}

View File

@@ -21,6 +21,10 @@ import io.lettuce.core.ScanCursor;
import io.lettuce.core.SortArgs;
import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands;
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;
import io.lettuce.core.codec.ByteArrayCodec;
import io.lettuce.core.output.StatusOutput;
import io.lettuce.core.protocol.CommandArgs;
import io.lettuce.core.protocol.CommandType;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@@ -671,23 +675,33 @@ class LettuceKeyCommands implements RedisKeyCommands {
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisKeyCommands#restore(byte[], long, byte[])
* @see org.springframework.data.redis.connection.RedisKeyCommands#restore(byte[], long, byte[], boolean)
*/
@Override
public void restore(byte[] key, long ttlInMillis, byte[] serializedValue) {
public void restore(byte[] key, long ttlInMillis, byte[] serializedValue, boolean replace) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(serializedValue, "Serialized value must not be null!");
try {
if (replace) {
this.connection.execute("RESTORE", new byte[][] { key, LettuceConverters.toBytes(ttlInMillis), serializedValue,
LettuceConverters.toBytes("REPLACE") });
return;
}
if (isPipelined()) {
pipeline(connection.newLettuceStatusResult(getAsyncConnection().restore(key, ttlInMillis, serializedValue)));
return;
}
if (isQueueing()) {
transaction(connection.newLettuceStatusResult(getAsyncConnection().restore(key, ttlInMillis, serializedValue)));
return;
}
getConnection().restore(key, ttlInMillis, serializedValue);
} catch (Exception ex) {
throw convertLettuceAccessException(ex);

View File

@@ -328,7 +328,23 @@ public interface RedisOperations<K, V> {
* @param unit must not be {@literal null}.
* @see <a href="http://redis.io/commands/restore">Redis Documentation: RESTORE</a>
*/
void restore(K key, byte[] value, long timeToLive, TimeUnit unit);
default void restore(K key, byte[] value, long timeToLive, TimeUnit unit) {
restore(key, value, timeToLive, unit, false);
}
/**
* Create {@code key} using the {@code serializedValue}, previously obtained using {@link #dump(Object)}.
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @param timeToLive
* @param unit must not be {@literal null}.
* @param replace use {@literal true} to replace a potentially existing value instead of erroring.
* @since 2.1
* @see <a href="http://redis.io/commands/restore">Redis Documentation: RESTORE</a>
*/
void restore(K key, byte[] value, long timeToLive, TimeUnit unit, boolean replace);
/**
* Get the time to live for {@code key} in seconds.

View File

@@ -980,16 +980,18 @@ public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperation
* @param value The value to restore, as returned by {@link #dump(Object)}
* @param timeToLive An expiration for the restored key, or 0 for no expiration
* @param unit The time unit for timeToLive
* @throws RedisSystemException if the key you are attempting to restore already exists.
* @param replace use {@literal true} to replace a potentially existing value instead of erroring.
* @throws RedisSystemException if the key you are attempting to restore already exists and {@code replace} is set to
* {@literal false}.
*/
@Override
public void restore(K key, final byte[] value, long timeToLive, TimeUnit unit) {
public void restore(K key, final byte[] value, long timeToLive, TimeUnit unit, boolean replace) {
byte[] rawKey = rawKey(key);
long rawTimeout = TimeoutUtils.toMillis(timeToLive, unit);
execute(connection -> {
connection.restore(rawKey, rawTimeout, value);
connection.restore(rawKey, rawTimeout, value, replace);
return null;
}, true);
}