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 088bb528f..e39332073 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java @@ -264,31 +264,6 @@ public interface ReactiveKeyCommands { */ Flux> del(Publisher keys); - /** - * Unlink {@literal key}. - * - * @param keys must not be {@literal null}. - * @return - * @see Redis Documentation: UNLINK - * @since 2.1 - */ - default Mono unlink(List keys) { - - Assert.notNull(keys, "Key must not be null!"); - - return unlink(Mono.just(keys)).next().map(NumericResponse::getOutput); - } - - /** - * Unlink {@literal keys}. - * - * @param keys must not be {@literal null}. - * @return {@link Flux} of {@link NumericResponse} holding the {@literal key} removed along with the deletion result. - * @see Redis Documentation: UNLINK - * @since 2.1 - */ - Flux, Long>> unlink(Publisher> keys); - /** * Delete multiple {@literal keys} one in one batch. * @@ -312,6 +287,60 @@ public interface ReactiveKeyCommands { */ Flux, Long>> mDel(Publisher> keys); + /** + * Unlink the {@code key} from the keyspace. Unlike with {@link #del(ByteBuffer)} the actual memory reclaiming here + * happens asynchronously. + * + * @param key must not be {@literal null}. + * @return + * @see Redis Documentation: UNLINK + * @since 2.1 + */ + default Mono unlink(ByteBuffer key) { + + Assert.notNull(key, "Keys must not be null!"); + + return unlink(Mono.just(key).map(KeyCommand::new)).next().map(NumericResponse::getOutput); + } + + /** + * Unlink the {@code key} from the keyspace. Unlike with {@link #del(ByteBuffer)} the actual memory reclaiming here + * happens asynchronously. + * + * @param keys must not be {@literal null}. + * @return {@link Flux} of {@link NumericResponse} holding the {@literal key} removed along with the unlink result. + * @see Redis Documentation: UNLINK + * @since 2.1 + */ + Flux> unlink(Publisher keys); + + /** + * Unlink the {@code keys} from the keyspace. Unlike with {@link #mDel(List)} the actual memory reclaiming here + * happens asynchronously. + * + * @param keys must not be {@literal null}. + * @return + * @see Redis Documentation: UNLINK + * @since 2.1 + */ + default Mono mUnlink(List keys) { + + Assert.notNull(keys, "Keys must not be null!"); + + return mUnlink(Mono.just(keys)).next().map(NumericResponse::getOutput); + } + + /** + * Unlink the {@code keys} from the keyspace. Unlike with {@link #mDel(Publisher)} the actual memory reclaiming here + * happens asynchronously. + * + * @param keys must not be {@literal null}. + * @return {@link Flux} of {@link NumericResponse} holding the {@literal key} removed along with the deletion result. + * @see Redis Documentation: UNLINK + * @since 2.1 + */ + Flux, Long>> mUnlink(Publisher> keys); + /** * {@code EXPIRE}/{@code PEXPIRE} command parameters. * 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 a60f1e13f..8706f7663 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java @@ -71,8 +71,8 @@ public interface RedisKeyCommands { Long del(byte[]... keys); /** - * Unlinks the {@code keys} from the keyspace. Unlike with {@link #del(byte[]...)} the actual removal here happens - * asynchronously. + * Unlink the {@code keys} from the keyspace. Unlike with {@link #del(byte[]...)} the actual memory reclaiming here + * happens asynchronously. * * @param keys must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. 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 c752214e7..4018ca98c 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -114,8 +114,8 @@ public interface StringRedisConnection extends RedisConnection { Long del(String... keys); /** - * Unlinks the {@code keys} from the keyspace. Unlike with {@link #del(byte[]...)} the actual removal here happens - * asynchronously. + * Unlink the {@code keys} from the keyspace. Unlike with {@link #del(String...)} the actual memory reclaiming here + * happens asynchronously. * * @param keys must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. 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 9b60972ea..d4e2696a6 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 @@ -22,7 +22,6 @@ import reactor.core.publisher.Mono; import java.nio.ByteBuffer; import java.util.Collection; import java.util.List; -import java.util.stream.Collectors; import org.reactivestreams.Publisher; import org.springframework.data.redis.connection.DataType; @@ -175,22 +174,6 @@ class LettuceReactiveKeyCommands implements ReactiveKeyCommands { })); } - /* - * (non-Javadoc) - * @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#unlink(org.reactivestreams.Publisher) - */ - @Override - public Flux, Long>> unlink(Publisher> keysCollection) { - - return connection.execute(cmd -> Flux.from(keysCollection).flatMap((keys) -> { - - Assert.notEmpty(keys, "Keys must not be null!"); - - return cmd.unlink(keys.stream().collect(Collectors.toList()).toArray(new ByteBuffer[keys.size()])) - .map((value) -> new NumericResponse<>(keys, value)); - })); - } - /* * (non-Javadoc) * @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#mDel(org.reactivestreams.Publisher) @@ -202,11 +185,41 @@ class LettuceReactiveKeyCommands implements ReactiveKeyCommands { Assert.notEmpty(keys, "Keys must not be null!"); - return cmd.del(keys.stream().collect(Collectors.toList()).toArray(new ByteBuffer[keys.size()])) + return cmd.del(keys.toArray(new ByteBuffer[keys.size()])) .map((value) -> new NumericResponse<>(keys, value)); })); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#unlink(org.reactivestreams.Publisher) + */ + @Override + public Flux> unlink(Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).concatMap((command) -> { + + Assert.notNull(command.getKey(), "Key must not be null!"); + + return cmd.unlink(command.getKey()).map((value) -> new NumericResponse<>(command, value)); + })); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#mUnlink(org.reactivestreams.Publisher) + */ + @Override + public Flux, Long>> mUnlink(Publisher> keysCollection) { + + return connection.execute(cmd -> Flux.from(keysCollection).concatMap((keys) -> { + + Assert.notEmpty(keys, "Keys must not be null!"); + + return cmd.unlink(keys.toArray(new ByteBuffer[keys.size()])).map((value) -> new NumericResponse<>(keys, value)); + })); + } + /* (non-Javadoc) * @see org.springframework.data.redis.connection.ReactiveKeyCommands#expire(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 e89263f57..a0f0de824 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java @@ -132,6 +132,28 @@ public interface ReactiveRedisOperations { */ Mono delete(Publisher keys); + /** + * Unlink the {@code key} from the keyspace. Unlike with {@link #delete(Object[])} the actual memory reclaiming here + * happens asynchronously. + * + * @param key must not be {@literal null}. + * @return The number of keys that were removed. {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: UNLINK + * @since 2.1 + */ + Mono unlink(K... key); + + /** + * Unlink the {@code keys} from the keyspace. Unlike with {@link #delete(Publisher)} the actual memory reclaiming here + * happens asynchronously. + * + * @param keys must not be {@literal null}. + * @return The number of keys that were removed. {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: UNLINK + * @since 2.1 + */ + Mono unlink(Publisher keys); + /** * Set time to live for given {@code key}. * 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 61f8a973f..031c5bbc3 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java @@ -304,6 +304,40 @@ public class ReactiveRedisTemplate implements ReactiveRedisOperations unlink(K... keys) { + + Assert.notNull(keys, "Keys must not be null!"); + Assert.notEmpty(keys, "Keys must not be empty!"); + Assert.noNullElements(keys, "Keys must not contain null elements!"); + + if (keys.length == 1) { + return createMono(connection -> connection.keyCommands().unlink(rawKey(keys[0]))); + } + + Mono> listOfKeys = Flux.fromArray(keys).map(this::rawKey).collectList(); + return createMono(connection -> listOfKeys.flatMap(rawKeys -> connection.keyCommands().mUnlink(rawKeys))); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveRedisOperations#unlink(org.reactivestreams.Publisher) + */ + @Override + public Mono unlink(Publisher keys) { + + Assert.notNull(keys, "Keys must not be null!"); + + return createMono(connection -> connection.keyCommands() // + .unlink(Flux.from(keys).map(this::rawKey).map(KeyCommand::new)) // + .map(CommandResponse::getOutput)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveRedisOperations#expire(java.lang.Object, java.time.Duration) 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 2c48ee1ab..e6fa95e16 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisOperations.java +++ b/src/main/java/org/springframework/data/redis/core/RedisOperations.java @@ -194,6 +194,30 @@ public interface RedisOperations { @Nullable Long delete(Collection keys); + /** + * Unlink the {@code key} from the keyspace. Unlike with {@link #delete(Object)} the actual memory reclaiming here + * happens asynchronously. + * + * @param key must not be {@literal null}. + * @return The number of keys that were removed. {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: UNLINK + * @since 2.1 + */ + @Nullable + Boolean unlink(K key); + + /** + * Unlink the {@code keys} from the keyspace. Unlike with {@link #delete(Collection)} the actual memory reclaiming + * here happens asynchronously. + * + * @param keys must not be {@literal null}. + * @return The number of keys that were removed. {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: UNLINK + * @since 2.1 + */ + @Nullable + Long unlink(Collection keys); + /** * Determine the type stored at {@code key}. * 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 e315fa962..613d0b060 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java @@ -727,6 +727,36 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation return execute(connection -> connection.del(rawKeys), true); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.RedisOperations#unlink(java.lang.Object) + */ + @Override + public Boolean unlink(K key) { + + byte[] rawKey = rawKey(key); + + Long result = execute(connection -> connection.unlink(rawKey), true); + + return result != null && result.intValue() == 1; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.RedisOperations#unlink(java.util.Collection) + */ + @Override + public Long unlink(Collection keys) { + + if (CollectionUtils.isEmpty(keys)) { + return 0L; + } + + byte[][] rawKeys = rawKeys(keys); + + return execute(connection -> connection.unlink(rawKeys), true); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#hasKey(java.lang.Object) 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 6b7c2902b..237ab0ff3 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -1128,6 +1128,38 @@ public abstract class AbstractConnectionIntegrationTests { verifyResults(Arrays.asList(true, 1L, false)); } + @Test // DATAREDIS-693 + @IfProfileValue(name = "redisVersion", value = "4.0+") + public void unlinkReturnsNrOfKeysRemoved() { + + connection.set("unlink.this", "Can't track this!"); + + actual.add(connection.unlink("unlink.this", "unlink.that")); + + verifyResults(Arrays.asList(new Object[] { 1L })); + } + + @Test // DATAREDIS-693 + @IfProfileValue(name = "redisVersion", value = "4.0+") + public void testUnlinkBatch() { + + actual.add(connection.set("testing", "123")); + actual.add(connection.set("foo", "bar")); + actual.add(connection.unlink("testing", "foo")); + actual.add(connection.exists("testing")); + + verifyResults(Arrays.asList(true, true, 2L, false)); + } + + @Test // DATAREDIS-693 + @IfProfileValue(name = "redisVersion", value = "4.0+") + public void unlinkReturnsZeroIfNoKeysRemoved() { + + actual.add(connection.unlink("unlink.this")); + + verifyResults(Arrays.asList(new Object[] { 0L })); + } + @Test public void testType() { @@ -2790,26 +2822,6 @@ public abstract class AbstractConnectionIntegrationTests { verifyResults(Arrays.asList(new Object[] { 0L })); } - @Test // DATAREDIS-693 - @IfProfileValue(name = "redisVersion", value = "4.0+") - public void unlinkReturnsNrOfKeysRemoved() { - - connection.set("unlink.this", "Can't track this!"); - - actual.add(connection.unlink("unlink.this", "unlink.that")); - - verifyResults(Arrays.asList(new Object[] { 1L })); - } - - @Test // DATAREDIS-693 - @IfProfileValue(name = "redisVersion", value = "4.0+") - public void unlinkReturnsZeroIfNoKeysRemoved() { - - actual.add(connection.unlink("unlink.this")); - - verifyResults(Arrays.asList(new Object[] { 0L })); - } - protected void verifyResults(List expected) { assertEquals(expected, getResults()); } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommandsTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommandsTests.java index b4e650d36..d5f80033c 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommandsTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommandsTests.java @@ -28,6 +28,8 @@ import java.nio.ByteBuffer; import java.time.Duration; import java.time.Instant; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import org.junit.Rule; import org.junit.Test; @@ -53,12 +55,12 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest nativeCommands.set(KEY_1, VALUE_1); - assertThat(connection.keyCommands().exists(KEY_1_BBUFFER).block(), is(true)); + StepVerifier.create(connection.keyCommands().exists(KEY_1_BBUFFER)).expectNext(true).verifyComplete(); } @Test // DATAREDIS-525 public void existsShouldReturnFalseForNonExistingKeys() { - assertThat(connection.keyCommands().exists(KEY_1_BBUFFER).block(), is(false)); + StepVerifier.create(connection.keyCommands().exists(KEY_1_BBUFFER)).expectNext(false).verifyComplete(); } @Test // DATAREDIS-525 @@ -68,9 +70,9 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest nativeCommands.sadd(KEY_2, VALUE_2); nativeCommands.hset(KEY_3, KEY_1, VALUE_1); - assertThat(connection.keyCommands().type(KEY_1_BBUFFER).block(), is(DataType.STRING)); - assertThat(connection.keyCommands().type(KEY_2_BBUFFER).block(), is(DataType.SET)); - assertThat(connection.keyCommands().type(KEY_3_BBUFFER).block(), is(DataType.HASH)); + StepVerifier.create(connection.keyCommands().type(KEY_1_BBUFFER)).expectNext(DataType.STRING).verifyComplete(); + StepVerifier.create(connection.keyCommands().type(KEY_2_BBUFFER)).expectNext(DataType.SET).verifyComplete(); + StepVerifier.create(connection.keyCommands().type(KEY_3_BBUFFER)).expectNext(DataType.HASH).verifyComplete(); } @Test // DATAREDIS-525 @@ -84,8 +86,12 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest nativeCommands.set(VALUE_2, KEY_2); nativeCommands.set(VALUE_3, KEY_3); - assertThat(connection.keyCommands().keys(ByteBuffer.wrap("*".getBytes())).block(), hasSize(6)); - assertThat(connection.keyCommands().keys(ByteBuffer.wrap("key*".getBytes())).block(), hasSize(3)); + StepVerifier.create(connection.keyCommands().keys(ByteBuffer.wrap("*".getBytes())).flatMapIterable(it -> it)) // + .expectNextCount(6) // + .verifyComplete(); + + StepVerifier.create(connection.keyCommands().keys(ByteBuffer.wrap("key*".getBytes())).flatMapIterable(it -> it)) // + .expectNextCount(3).verifyComplete(); } @Test // DATAREDIS-525 @@ -95,12 +101,12 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest nativeCommands.set(KEY_2, VALUE_2); nativeCommands.set(KEY_3, VALUE_3); - assertThat(connection.keyCommands().randomKey().block(), is(notNullValue())); + StepVerifier.create(connection.keyCommands().randomKey()).expectNextCount(1).verifyComplete(); } @Test // DATAREDIS-525 public void randomKeyShouldReturnNullWhenNoKeyExists() { - assertThat(connection.keyCommands().randomKey().block(), is(nullValue())); + StepVerifier.create(connection.keyCommands().randomKey()).verifyComplete(); } @Test // DATAREDIS-525 @@ -108,14 +114,17 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest nativeCommands.set(KEY_1, VALUE_2); - assertThat(connection.keyCommands().rename(KEY_1_BBUFFER, KEY_2_BBUFFER).block(), is(true)); + StepVerifier.create(connection.keyCommands().rename(KEY_1_BBUFFER, KEY_2_BBUFFER)).expectNext(true) + .verifyComplete(); assertThat(nativeCommands.exists(KEY_2), is(1L)); assertThat(nativeCommands.exists(KEY_1), is(0L)); } - @Test(expected = RedisSystemException.class) // DATAREDIS-525 + @Test // DATAREDIS-525 public void renameShouldThrowErrorWhenKeyDoesNotExist() { - assertThat(connection.keyCommands().rename(KEY_1_BBUFFER, KEY_2_BBUFFER).block(), is(true)); + + StepVerifier.create(connection.keyCommands().rename(KEY_1_BBUFFER, KEY_2_BBUFFER)) + .expectError(RedisSystemException.class).verify(); } @Test // DATAREDIS-525 @@ -123,7 +132,8 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest nativeCommands.set(KEY_1, VALUE_2); - assertThat(connection.keyCommands().rename(KEY_1_BBUFFER, KEY_2_BBUFFER).block(), is(true)); + StepVerifier.create(connection.keyCommands().renameNX(KEY_1_BBUFFER, KEY_2_BBUFFER)).expectNext(true) + .verifyComplete(); assertThat(nativeCommands.exists(KEY_2), is(1L)); assertThat(nativeCommands.exists(KEY_1), is(0L)); @@ -135,7 +145,8 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest nativeCommands.set(KEY_1, VALUE_2); nativeCommands.set(KEY_2, VALUE_2); - assertThat(connection.keyCommands().renameNX(KEY_1_BBUFFER, KEY_2_BBUFFER).block(), is(false)); + StepVerifier.create(connection.keyCommands().renameNX(KEY_1_BBUFFER, KEY_2_BBUFFER)).expectNext(false) + .verifyComplete(); } @Test // DATAREDIS-525 @@ -143,8 +154,7 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest nativeCommands.set(KEY_1, VALUE_1); - Mono result = connection.keyCommands().del(KEY_1_BBUFFER); - assertThat(result.block(), is(1L)); + StepVerifier.create(connection.keyCommands().del(KEY_1_BBUFFER)).expectNext(1L).verifyComplete(); } @Test // DATAREDIS-525 @@ -167,7 +177,7 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest Mono result = connection.keyCommands().mDel(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER)); - assertThat(result.block(), is(2L)); + StepVerifier.create(result).expectNext(2L).verifyComplete(); } @Test // DATAREDIS-525 @@ -176,9 +186,62 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest nativeCommands.set(KEY_1, VALUE_1); nativeCommands.set(KEY_2, VALUE_2); + Flux> input = Flux.just(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER), + Collections.singletonList(KEY_1_BBUFFER)); + Flux result = connection.keyCommands() - .mDel( - Flux.fromIterable(Arrays.asList(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER), Arrays.asList(KEY_1_BBUFFER)))) + .mDel(input) + .map(NumericResponse::getOutput); + + StepVerifier.create(result).expectNextCount(2).verifyComplete(); + } + + @Test // DATAREDIS-693 + @IfProfileValue(name = "redisVersion", value = "4.0.0+") + public void shouldUnlinkKeyCorrectly() { + + nativeCommands.set(KEY_1, VALUE_1); + + StepVerifier.create(connection.keyCommands().unlink(KEY_1_BBUFFER)).expectNext(1L).verifyComplete(); + } + + @Test // DATAREDIS-693 + @IfProfileValue(name = "redisVersion", value = "4.0.0+") + public void shouldUnlinkKeysCorrectly() { + + nativeCommands.set(KEY_1, VALUE_1); + nativeCommands.set(KEY_2, VALUE_2); + + Flux> result = connection.keyCommands() + .unlink(Flux.fromIterable(Arrays.asList(new KeyCommand(KEY_1_BBUFFER), new KeyCommand(KEY_2_BBUFFER)))); + + StepVerifier.create(result).expectNextCount(2).verifyComplete(); + } + + @Test // DATAREDIS-693 + @IfProfileValue(name = "redisVersion", value = "4.0.0+") + public void shouldUnlinkKeysInBatchCorrectly() { + + nativeCommands.set(KEY_1, VALUE_1); + nativeCommands.set(KEY_2, VALUE_2); + + Mono result = connection.keyCommands().mUnlink(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER)); + + StepVerifier.create(result).expectNext(2L).verifyComplete(); + } + + @Test // DATAREDIS-693 + @IfProfileValue(name = "redisVersion", value = "4.0.0+") + public void shouldUnlinkKeysInMultipleBatchesCorrectly() { + + nativeCommands.set(KEY_1, VALUE_1); + nativeCommands.set(KEY_2, VALUE_2); + + Flux> input = Flux.just(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER), + Collections.singletonList(KEY_1_BBUFFER)); + + Flux result = connection.keyCommands() + .mUnlink(input) .map(NumericResponse::getOutput); StepVerifier.create(result).expectNextCount(2).verifyComplete(); @@ -307,29 +370,7 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest @Test // DATAREDIS-694 public void touchReturnsZeroIfNoKeysTouched() { - StepVerifier.create(connection.keyCommands().touch(Arrays.asList(KEY_1_BBUFFER))) // - .expectNext(0L) // - .verifyComplete(); - } - - @Test // DATAREDIS-693 - @IfProfileValue(name = "redisVersion", value = "4.0.0+") - public void unlinkReturnsNrOfKeysRemoved() { - - nativeCommands.set(KEY_1, VALUE_1); - nativeCommands.set(KEY_2, VALUE_2); - - StepVerifier.create(connection.keyCommands().unlink(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER, KEY_3_BBUFFER))) - .expectNext(2L) // - .verifyComplete(); - - } - - @Test // DATAREDIS-693 - @IfProfileValue(name = "redisVersion", value = "4.0.0+") - public void unlinkReturnsZeroIfNoKeysRemoved() { - - StepVerifier.create(connection.keyCommands().unlink(Arrays.asList(KEY_1_BBUFFER))) // + StepVerifier.create(connection.keyCommands().touch(Collections.singletonList(KEY_1_BBUFFER))) // .expectNext(0L) // .verifyComplete(); } 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 72e02c130..3b0b0639e 100644 --- a/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java @@ -166,6 +166,36 @@ public class ReactiveRedisTemplateIntegrationTests { .verify(); } + @Test // DATAREDIS-693 + public void unlink() { + + K single = keyFactory.instance(); + + StepVerifier.create(redisTemplate.opsForValue().set(single, valueFactory.instance())).expectNext(true) + .verifyComplete(); + + StepVerifier.create(redisTemplate.unlink(single)).expectNext(1L).verifyComplete(); + + StepVerifier.create(redisTemplate.hasKey(single)).expectNext(false).verifyComplete(); + } + + @Test // DATAREDIS-693 + public void unlinkMany() { + + K key1 = keyFactory.instance(); + K key2 = keyFactory.instance(); + + StepVerifier.create(redisTemplate.opsForValue().set(key1, valueFactory.instance())).expectNext(true) + .verifyComplete(); + StepVerifier.create(redisTemplate.opsForValue().set(key2, valueFactory.instance())).expectNext(true) + .verifyComplete(); + + StepVerifier.create(redisTemplate.unlink(key1, key2)).expectNext(2L).verifyComplete(); + + StepVerifier.create(redisTemplate.hasKey(key1)).expectNext(false).verifyComplete(); + StepVerifier.create(redisTemplate.hasKey(key2)).expectNext(false).verifyComplete(); + } + @Test // DATAREDIS-683 @SuppressWarnings("unchecked") public void executeScript() {