diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 7d063b6ab..eacddaa97 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -7,7 +7,7 @@ This section briefly covers items that are new and noteworthy in the latest rele == New in Spring Data Redis 2.6 * Support for `SubscriptionListener` when using `MessageListener` for subscription confirmation callbacks. `ReactiveRedisMessageListenerContainer` and `ReactiveRedisOperations` provide `receiveLater(…)` and `listenToLater(…)` methods to await until Redis acknowledges the subscription. -* Support Redis 6.2 commands (`LPOP`/`RPOP` with `count`). +* Support Redis 6.2 commands (`LPOP`/`RPOP` with `count`, `COPY`). [[new-in-2.5.0]] == New in Spring Data Redis 2.5 diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java index d5d5387e2..6ce7ac173 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -241,6 +241,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco delegate.close(); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#copy(byte[], byte[], boolean) + */ + @Override + public Boolean copy(byte[] sourceKey, byte[] targetKey, boolean replace) { + return convertAndReturn(delegate.copy(sourceKey, targetKey, replace), Converters.identityConverter()); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisServerCommands#dbSize() @@ -268,6 +277,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.decrBy(key, value), Converters.identityConverter()); } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#del(byte[][]) @@ -277,15 +287,6 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.del(keys), Converters.identityConverter()); } - /* - * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisKeyCommands#copy(byte[], byte[]) - */ - @Override - public Boolean copy(byte[] sourceKey, byte[] targetKey) { - return convertAndReturn(delegate.copy(sourceKey, targetKey), Converters.identityConverter()); - } - /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#unlink(byte[][]) @@ -1900,6 +1901,14 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.bRPopLPush(timeout, serialize(srcKey), serialize(dstKey)), bytesToString); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#copy(java.lang.String, java.lang.String, boolean) + */ + @Override + public Boolean copy(String sourceKey, String targetKey, boolean replace) { + return copy(serialize(sourceKey), serialize(targetKey), replace); + } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.StringRedisConnection#decr(java.lang.String) @@ -1927,14 +1936,6 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return del(serializeMulti(keys)); } - /* - * (non-Javadoc) - * @see org.springframework.data.redis.connection.StringRedisConnection#copy(java.lang.String[]) - */ - @Override - public Boolean copy(String sourceKey, String targetKey) { - return copy(serialize(sourceKey), serialize(targetKey)); - } /* * (non-Javadoc) diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java index c963dc25b..f26ba14cd 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -64,6 +64,13 @@ public interface DefaultedRedisConnection extends RedisConnection { // KEY COMMANDS + /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */ + @Override + @Deprecated + default Boolean copy(byte[] sourceKey, byte[] targetKey, boolean replace) { + return keyCommands().copy(sourceKey, targetKey, replace); + } + /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */ @Override @Deprecated @@ -85,13 +92,6 @@ public interface DefaultedRedisConnection extends RedisConnection { return keyCommands().del(keys); } - /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */ - @Override - @Deprecated - default Boolean copy(byte[] sourceKey, byte[] targetKey) { - return keyCommands().copy(sourceKey, targetKey); - } - /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */ @Override @Deprecated diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java index 9eea37b00..bac73b442 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java @@ -43,6 +43,124 @@ import org.springframework.util.Assert; */ public interface ReactiveKeyCommands { + /** + * {@code MOVE} command parameters. + * + * @author Mark Paluch + * @see Redis Documentation: MOVE + */ + class CopyCommand extends KeyCommand { + + private final @Nullable ByteBuffer target; + private final boolean replace; + private final @Nullable Integer database; + + public CopyCommand(ByteBuffer key, @Nullable ByteBuffer target, boolean replace, @Nullable Integer database) { + super(key); + this.target = target; + this.replace = replace; + this.database = database; + } + + /** + * Creates a new {@link CopyCommand} given a {@link ByteBuffer key}. + * + * @param key must not be {@literal null}. + * @return a new {@link CopyCommand} for {@link ByteBuffer key}. + */ + public static CopyCommand key(ByteBuffer key) { + + Assert.notNull(key, "Key must not be null!"); + + return new CopyCommand(key, null, false, null); + } + + /** + * Applies the {@link ByteBuffer targetKey}. Constructs a new command instance with all previously configured + * properties. + * + * @param targetKey must not be {@literal null}. + * @return a new {@link CopyCommand} with {@literal database} applied. + */ + public CopyCommand to(ByteBuffer targetKey) { + + Assert.notNull(targetKey, "Key must not be null!"); + + return new CopyCommand(getKey(), targetKey, isReplace(), database); + } + + /** + * Applies {@code replace}. Constructs a new command instance with all previously configured properties. + * + * @param key must not be {@literal null}. + * @return a new {@link CopyCommand} with {@literal replace} applied. + */ + public CopyCommand replace(boolean replace) { + return new CopyCommand(getKey(), this.target, replace, database); + } + + /** + * Applies the {@literal database} index. Constructs a new command instance with all previously configured + * properties. + * + * @param database + * @return a new {@link CopyCommand} with {@literal database} applied. + */ + public CopyCommand database(int database) { + return new CopyCommand(getKey(), this.target, isReplace(), database); + } + + /** + * @return can be {@literal null}. + */ + @Nullable + public ByteBuffer getTarget() { + return target; + } + + public boolean isReplace() { + return replace; + } + + /** + * @return can be {@literal null}. + */ + @Nullable + public Integer getDatabase() { + return database; + } + + } + + /** + * Copy given {@code key} to a target {@code key}. + * + * @param sourceKey must not be {@literal null}. + * @param targetKey must not be {@literal null}. + * @param replace whether to replace existing keys. + * @return + * @see Redis Documentation: COPY + * @since 2.6 + */ + default Mono copy(ByteBuffer sourceKey, ByteBuffer targetKey, boolean replace) { + + Assert.notNull(sourceKey, "Source key must not be null!"); + Assert.notNull(targetKey, "Targetk ey must not be null!"); + + return copy(Mono.just(CopyCommand.key(sourceKey).to(targetKey).replace(replace))).next() + .map(BooleanResponse::getOutput); + } + + /** + * Copy keys one-by-one. + * + * @param commands must not be {@literal null}. + * @return {@link Flux} of {@link BooleanResponse} holding the {@literal key} to move along with the copy result. + * @see Redis Documentation: COPY + * @since 2.6 + */ + Flux> copy(Publisher commands); + /** * Determine if given {@literal key} exists. * @@ -670,7 +788,7 @@ public interface ReactiveKeyCommands { * Creates a new {@link MoveCommand} given a {@link ByteBuffer key}. * * @param key must not be {@literal null}. - * @return a new {@link ExpireCommand} for {@link ByteBuffer key}. + * @return a new {@link MoveCommand} for {@link ByteBuffer key}. */ public static MoveCommand key(ByteBuffer key) { diff --git a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java index 908c6ed60..d13c18e28 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java @@ -35,6 +35,19 @@ import org.springframework.util.Assert; */ public interface RedisKeyCommands { + /** + * Copy given {@code sourceKey} to {@code targetKey}. + * + * @param sourceKey must not be {@literal null}. + * @param targetKey must not be {@literal null}. + * @param replace whether to replace existing keys. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: COPY + * @since 2.6 + */ + @Nullable + Boolean copy(byte[] sourceKey, byte[] targetKey, boolean replace); + /** * Determine if given {@code key} exists. * @@ -72,17 +85,6 @@ public interface RedisKeyCommands { @Nullable Long del(byte[]... keys); - /** - * Copy given {@code sourceKey} to {@code targetKey}. - * - * @param sourceKey must not be {@literal null}. - * @param targetKey must not be {@literal null}. - * @return - * @see Redis Documentation: COPY - */ - @Nullable - Boolean copy(byte[] sourceKey, byte[] targetKey); - /** * Unlink the {@code keys} from the keyspace. Unlike with {@link #del(byte[]...)} the actual memory reclaiming here * happens asynchronously. diff --git a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java index 261921e90..66c9f2793 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -139,11 +139,12 @@ public interface StringRedisConnection extends RedisConnection { * * @param sourceKey must not be {@literal null}. * @param targetKey must not be {@literal null}. - * @return + * @param replace whether to replace existing keys. + * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: COPY * @see RedisKeyCommands#copy(byte[], byte[]) */ - Boolean copy(String sourceKey, String targetKey); + Boolean copy(String sourceKey, String targetKey, boolean replace); /** * Unlink the {@code keys} from the keyspace. Unlike with {@link #del(String...)} the actual memory reclaiming here diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java index a78937187..9965552e3 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java @@ -64,6 +64,19 @@ class JedisClusterKeyCommands implements RedisKeyCommands { this.connection = connection; } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#copy(byte[], byte[]) + */ + @Override + public Boolean copy(byte[] sourceKey, byte[] targetKey, boolean replace) { + + Assert.notNull(sourceKey, "source key must not be null!"); + Assert.notNull(targetKey, "target key must not be null!"); + + return connection.getCluster().copy(sourceKey, targetKey, replace); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#del(byte[][]) @@ -88,18 +101,6 @@ class JedisClusterKeyCommands implements RedisKeyCommands { .resultsAsList().size(); } - /* - * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisKeyCommands#copy(byte[], byte[]) - */ - @Override - public Boolean copy(byte[] sourceKey, byte[] targetKey) { - Assert.notNull(sourceKey, "source key must not be null!"); - Assert.notNull(targetKey, "target key must not be null!"); - - return connection.getCluster().copy(sourceKey, targetKey, false); - } - /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#unlink(byte[][]) diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java index a78b99aca..e7acd67ef 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java @@ -95,11 +95,12 @@ class JedisKeyCommands implements RedisKeyCommands { * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#copy(byte[], byte[]) */ - public Boolean copy(byte[] sourceKey, byte[] targetKey) { + public Boolean copy(byte[] sourceKey, byte[] targetKey, boolean replace) { + Assert.notNull(sourceKey, "source key must not be null!"); Assert.notNull(targetKey, "target key must not be null!"); - return connection.invoke().just(BinaryJedis::copy, MultiKeyPipelineBase::copy, sourceKey, targetKey, false); + return connection.invoke().just(BinaryJedis::copy, MultiKeyPipelineBase::copy, sourceKey, targetKey, replace); } /* diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java index 3ff5da5f0..f09d2683a 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java @@ -15,6 +15,7 @@ */ package org.springframework.data.redis.connection.lettuce; +import io.lettuce.core.CopyArgs; import io.lettuce.core.KeyScanCursor; import io.lettuce.core.RestoreArgs; import io.lettuce.core.ScanArgs; @@ -52,6 +53,20 @@ class LettuceKeyCommands implements RedisKeyCommands { this.connection = connection; } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#copy(byte[], byte[]) + */ + @Override + public Boolean copy(byte[] sourceKey, byte[] targetKey, boolean replace) { + + Assert.notNull(sourceKey, "source key must not be null!"); + Assert.notNull(targetKey, "target key must not be null!"); + + return connection.invoke().just(RedisKeyAsyncCommands::copy, sourceKey, targetKey, + CopyArgs.Builder.replace(replace)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#exists(byte[]) @@ -91,17 +106,6 @@ class LettuceKeyCommands implements RedisKeyCommands { return connection.invoke().just(RedisKeyAsyncCommands::del, keys); } - /* - * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisKeyCommands#copy(byte[], byte[]) - */ - @Override - public Boolean copy(byte[] sourceKey, byte[] targetKey) { - Assert.notNull(sourceKey, "source key must not be null!"); - Assert.notNull(targetKey, "target key must not be null!"); - - return connection.invoke().just(RedisKeyAsyncCommands::copy, targetKey, sourceKey); - } /* * (non-Javadoc) diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommands.java index c04d957a4..d6bb1c337 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommands.java @@ -15,6 +15,7 @@ */ package org.springframework.data.redis.connection.lettuce; +import io.lettuce.core.CopyArgs; import io.lettuce.core.ScanStream; import io.lettuce.core.api.reactive.RedisKeyReactiveCommands; import reactor.core.publisher.Flux; @@ -59,6 +60,27 @@ class LettuceReactiveKeyCommands implements ReactiveKeyCommands { this.connection = connection; } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#copy(org.reactivestreams.Publisher) + */ + @Override + public Flux> copy(Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).concatMap((command) -> { + + Assert.notNull(command.getKey(), "Key must not be null!"); + + CopyArgs copyArgs = CopyArgs.Builder.replace(command.isReplace()); + if (command.getDatabase() != null) { + copyArgs.destinationDb(command.getDatabase()); + } + + return cmd.copy(command.getKey(), command.getTarget(), copyArgs) + .map((value) -> new BooleanResponse<>(command, value)); + })); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#exists(org.reactivestreams.Publisher) diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java b/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java index b2b7ada82..ad0f204d5 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java @@ -163,6 +163,17 @@ public interface ReactiveRedisOperations { // Methods dealing with Redis Keys // ------------------------------------------------------------------------- + /** + * Copy given {@code sourceKey} to {@code targetKey}. + * + * @param sourceKey must not be {@literal null}. + * @param targetKey must not be {@literal null}. + * @return + * @see Redis Documentation: COPY + * @since 2.6 + */ + Mono copy(K sourceKey, K targetKey, boolean replace); + /** * Determine if given {@code key} exists. * diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java b/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java index c9cf241c0..fba47e095 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java @@ -256,6 +256,19 @@ public class ReactiveRedisTemplate implements ReactiveRedisOperations copy(K sourceKey, K targetKey, boolean replace) { + + Assert.notNull(sourceKey, "Source key must not be null!"); + Assert.notNull(targetKey, "Target key must not be null!"); + + return createMono(connection -> connection.keyCommands().copy(rawKey(sourceKey), rawKey(targetKey), replace)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveRedisOperations#hasKey(java.lang.Object) diff --git a/src/main/java/org/springframework/data/redis/core/RedisOperations.java b/src/main/java/org/springframework/data/redis/core/RedisOperations.java index 5ac17f9e0..fa03490f5 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisOperations.java +++ b/src/main/java/org/springframework/data/redis/core/RedisOperations.java @@ -158,6 +158,19 @@ public interface RedisOperations { // Methods dealing with Redis Keys // ------------------------------------------------------------------------- + /** + * Copy given {@code sourceKey} to {@code targetKey}. + * + * @param sourceKey must not be {@literal null}. + * @param targetKey must not be {@literal null}. + * @param replace whether the key was copied. {@literal null} when used in pipeline / transaction. + * @return + * @see Redis Documentation: COPY + * @since 2.6 + */ + @Nullable + Boolean copy(K sourceKey, K targetKey, boolean replace); + /** * Determine if given {@code key} exists. * @@ -180,6 +193,7 @@ public interface RedisOperations { @Nullable Long countExistingKeys(Collection keys); + /** * Delete given {@code key}. * @@ -200,17 +214,6 @@ public interface RedisOperations { @Nullable Long delete(Collection keys); - /** - * Copy given {@code sourceKey} to {@code targetKey}. - * - * @param sourceKey must not be {@literal null}. - * @param targetKey must not be {@literal null}. - * @return - * @see Redis Documentation: COPY - */ - @Nullable - Boolean copy(K sourceKey, K targetKey); - /** * Unlink the {@code key} from the keyspace. Unlike with {@link #delete(Object)} the actual memory reclaiming here * happens asynchronously. diff --git a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java index 6448a00fc..c6a58a55c 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java @@ -698,6 +698,19 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation return raw == null ? Collections.emptyList() : raw; } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.RedisOperations#delete(java.lang.Object, java.lang.Object, boolean) + */ + @Override + public Boolean copy(K source, K target, boolean replace) { + + byte[] sourceKey = rawKey(source); + byte[] targetKey = rawKey(target); + + return execute(connection -> connection.copy(sourceKey, targetKey, replace), true); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#delete(java.lang.Object) @@ -708,18 +721,9 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation byte[] rawKey = rawKey(key); Long result = execute(connection -> connection.del(rawKey), true); - return result != null && result.intValue() == 1; } - @Override - public Boolean copy(K source, K target) { - byte[] sourceKey = rawKey(source); - byte[] targetKey = rawKey(target); - - return execute(connection -> connection.copy(sourceKey, targetKey), true); - } - /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#delete(java.util.Collection) diff --git a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java index 4c6485fb4..23f741034 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -509,6 +509,18 @@ public abstract class AbstractConnectionIntegrationTests { .isThrownBy(() -> connection.bitOp(BitOperation.NOT, "key3", "key1", "key2")); } + @Test + @EnabledOnCommand("COPY") + public void testCopy() { + + actual.add(connection.set("foo", "bar")); + actual.add(connection.copy("foo", "baz", false)); + + verifyResults(Arrays.asList(true, true)); + assertThat(connection.get("baz")).isEqualTo("bar"); + assertThat(connection.exists("foo")).isTrue(); + } + @Test void testInfo() { diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java index d7bc4474a..2691e66c4 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java @@ -100,6 +100,12 @@ public class DefaultStringRedisConnectionPipelineTxTests extends DefaultStringRe super.testBrPopLPush(); } + @Test + public void testCopy() { + doReturn(Collections.singletonList(Arrays.asList(Boolean.TRUE))).when(nativeConnection).closePipeline(); + super.testCopy(); + } + @Test public void testDbSize() { doReturn(Collections.singletonList(Collections.singletonList(3L))).when(nativeConnection).closePipeline(); @@ -142,13 +148,6 @@ public class DefaultStringRedisConnectionPipelineTxTests extends DefaultStringRe super.testDel(); } - @Test - public void testCopy() { - doReturn(Collections.singletonList(Collections.singletonList(Boolean.TRUE))).when(nativeConnection).closePipeline(); - super.testCopy(); - } - - @Test public void testEchoBytes() { doReturn(Collections.singletonList(Arrays.asList(new Object[] { barBytes }))).when(nativeConnection) diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java index e68204866..fe09f3610 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java @@ -187,6 +187,13 @@ public class DefaultStringRedisConnectionTests { verifyResults(Collections.singletonList(bar)); } + @Test + public void testCopy() { + doReturn(Boolean.TRUE).when(nativeConnection).copy(fooBytes, barBytes, false); + actual.add(connection.copy(foo, bar, false)); + verifyResults(Collections.singletonList(Boolean.TRUE)); + } + @Test public void testDbSize() { doReturn(3L).when(nativeConnection).dbSize(); @@ -236,13 +243,6 @@ public class DefaultStringRedisConnectionTests { verifyResults(Collections.singletonList(1L)); } - @Test - public void testCopy() { - doReturn(Boolean.TRUE).when(nativeConnection).copy(fooBytes, barBytes); - actual.add(connection.copy(foo, bar)); - verifyResults(Collections.singletonList(Boolean.TRUE)); - } - @Test public void testEchoBytes() { doReturn(barBytes).when(nativeConnection).echo(fooBytes); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java index c1ef9034c..c846f0bb2 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java @@ -92,6 +92,12 @@ public class DefaultStringRedisConnectionTxTests extends DefaultStringRedisConne super.testBrPopLPush(); } + @Test + public void testCopy() { + doReturn(Collections.singletonList(Boolean.TRUE)).when(nativeConnection).exec(); + super.testCopy(); + } + @Test public void testDbSize() { doReturn(Collections.singletonList(3L)).when(nativeConnection).exec(); diff --git a/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java b/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java index 318549d09..019f0f491 100644 --- a/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java @@ -187,8 +187,8 @@ class RedisConnectionUnitTests { return delegate.del(keys); } - public Boolean copy(byte[] sourceKey, byte[] targetKey) { - return delegate.copy(sourceKey, targetKey); + public Boolean copy(byte[] sourceKey, byte[] targetKey, boolean replace) { + return delegate.copy(sourceKey, targetKey, replace); } public void close() throws DataAccessException { diff --git a/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java index 7b127b4aa..f84d60a4e 100644 --- a/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java @@ -53,6 +53,7 @@ import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.test.condition.EnabledIfLongRunningTest; +import org.springframework.data.redis.test.condition.EnabledOnCommand; import org.springframework.data.redis.test.extension.parametrized.MethodSource; import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest; @@ -91,6 +92,34 @@ public class ReactiveRedisTemplateIntegrationTests { connection.close(); } + @ParameterizedRedisTest // GH-2040 + @EnabledOnCommand("COPY") + void copy() { + + ReactiveRedisClusterConnection connection = null; + try { + connection = redisTemplate.getConnectionFactory().getReactiveClusterConnection(); + assumeThat(connection == null).isTrue(); + } catch (InvalidDataAccessApiUsageException e) {} finally { + if (connection != null) { + connection.close(); + } + } + + K key = keyFactory.instance(); + K targetKey = keyFactory.instance(); + V value = valueFactory.instance(); + V nextValue = valueFactory.instance(); + + redisTemplate.opsForValue().set(key, value).as(StepVerifier::create).expectNext(true).verifyComplete(); + redisTemplate.copy(key, targetKey, false).as(StepVerifier::create).expectNext(true).verifyComplete(); + redisTemplate.opsForValue().get(targetKey).as(StepVerifier::create).expectNext(value).verifyComplete(); + + redisTemplate.opsForValue().set(key, nextValue).as(StepVerifier::create).expectNext(true).verifyComplete(); + redisTemplate.copy(key, targetKey, true).as(StepVerifier::create).expectNext(true).verifyComplete(); + redisTemplate.opsForValue().get(targetKey).as(StepVerifier::create).expectNext(nextValue).verifyComplete(); + } + @ParameterizedRedisTest // DATAREDIS-602 void exists() { diff --git a/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java index 5a421a1d4..b6344e274 100644 --- a/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java @@ -34,6 +34,8 @@ import org.springframework.data.redis.ObjectFactory; import org.springframework.data.redis.Person; import org.springframework.data.redis.SettingsUtils; import org.springframework.data.redis.connection.DataType; +import org.springframework.data.redis.connection.RedisClusterConnection; +import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.StringRedisConnection; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; @@ -393,16 +395,24 @@ public class RedisTemplateIntegrationTests { @ParameterizedRedisTest void testCopy() { + + assumeThat(redisTemplate.execute((RedisCallback) it -> it)) + .isNotInstanceOf(RedisClusterConnection.class); + K key1 = keyFactory.instance(); K key2 = keyFactory.instance(); V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); redisTemplate.opsForValue().set(key1, value1); - assertThat(redisTemplate.hasKey(key2)).isFalse(); - redisTemplate.copy(key1, key2); - assertThat(redisTemplate.hasKey(key2)).isTrue(); + redisTemplate.copy(key1, key2, false); assertThat(redisTemplate.opsForValue().get(key2)).isEqualTo(value1); + + redisTemplate.opsForValue().set(key1, value2); + + redisTemplate.copy(key1, key2, true); + assertThat(redisTemplate.opsForValue().get(key2)).isEqualTo(value2); } @ParameterizedRedisTest // DATAREDIS-688