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 3790f3202..ac04472ea 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -252,6 +252,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.del(keys), identityConverter); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#unlink(byte[][]) + */ + @Override + public Long unlink(byte[]... keys) { + return convertAndReturn(delegate.unlink(keys), identityConverter); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisTxCommands#discard() @@ -1785,6 +1794,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return del(serializeMulti(keys)); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#unlink(java.lang.String[]) + */ + @Override + public Long unlink(String... keys) { + return unlink(serializeMulti(keys)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.StringRedisConnection#echo(java.lang.String) 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 4e150bbe0..d09d88e33 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -68,6 +68,13 @@ public interface DefaultedRedisConnection extends RedisConnection { return keyCommands().del(keys); } + /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */ + @Override + @Deprecated + default Long unlink(byte[]... keys) { + return keyCommands().unlink(keys); + } + /** @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 24852a275..088bb528f 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java @@ -264,6 +264,31 @@ 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. * 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 557e1edc8..a60f1e13f 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java @@ -70,6 +70,18 @@ public interface RedisKeyCommands { @Nullable Long del(byte[]... keys); + /** + * Unlinks the {@code keys} from the keyspace. Unlike with {@link #del(byte[]...)} the actual removal here happens + * asynchronously. + * + * @param keys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: UNLINK + * @since 2.1 + */ + @Nullable + Long unlink(byte[]... keys); + /** * Determine the type stored at {@code key}. * 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 eb88c5b8a..c752214e7 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -113,6 +113,18 @@ 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. + * + * @param keys must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: UNLINK + * @since 2.1 + */ + @Nullable + Long unlink(String... keys); + /** * Determine the type stored at {@code key}. * 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 28c2be397..fc8be5df9 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 @@ -81,6 +81,20 @@ class JedisClusterKeyCommands implements RedisKeyCommands { .resultsAsList().size(); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#unlink(byte[][]) + */ + @Nullable + @Override + public Long unlink(byte[]... keys) { + + Assert.notNull(keys, "Keys must not be null!"); + + return connection. execute("UNLINK", Arrays.asList(keys), Collections.emptyList()).stream() + .mapToLong(val -> val).sum(); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#type(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 d03bf16d1..3912e7196 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 @@ -120,6 +120,19 @@ class JedisKeyCommands implements RedisKeyCommands { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#unlink(byte[][]) + */ + @Nullable + @Override + public Long unlink(byte[]... keys) { + + Assert.notNull(keys, "Keys must not be null!"); + + return Long.class.cast(connection.execute("UNLINK", keys)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#type(byte[]) 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 e89450e92..f9faf1504 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 @@ -126,6 +126,30 @@ class LettuceKeyCommands implements RedisKeyCommands { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#unlink(byte[][]) + */ + @Override + public Long unlink(byte[]... keys) { + + Assert.notNull(keys, "Keys must not be null!"); + + try { + if (isPipelined()) { + pipeline(connection.newLettuceResult(getAsyncConnection().unlink(keys))); + return null; + } + if (isQueueing()) { + transaction(connection.newLettuceResult(getAsyncConnection().unlink(keys))); + return null; + } + return getConnection().unlink(keys); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#type(byte[]) 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 3696d5c87..9b60972ea 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 @@ -175,6 +175,22 @@ 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) 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 91f1f147f..6b7c2902b 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -2790,6 +2790,26 @@ 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/jedis/JedisClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java index 717872860..f051b7db6 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java @@ -2219,4 +2219,18 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests { assertThat(clusterConnection.keyCommands().touch(KEY_1_BYTES), is(0L)); } + @Test // DATAREDIS-693 + public void unlinkReturnsNrOfKeysTouched() { + + nativeConnection.set(KEY_1, VALUE_1); + nativeConnection.set(KEY_2, VALUE_1); + + assertThat(clusterConnection.keyCommands().unlink(KEY_1_BYTES, KEY_2_BYTES, KEY_3_BYTES), is(2L)); + } + + @Test // DATAREDIS-693 + public void unlinkReturnsZeroIfNoKeysTouched() { + assertThat(clusterConnection.keyCommands().unlink(KEY_1_BYTES), is(0L)); + } + } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java index f99f5209d..1301fc3da 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java @@ -2182,4 +2182,32 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests { assertThat(nativeConnection.zrange(SAME_SLOT_KEY_3, 0, -1), hasItems(VALUE_1, VALUE_2, VALUE_3)); } + + @Test // DATAREDIS-694 + public void touchReturnsNrOfKeysTouched() { + + nativeConnection.set(KEY_1, VALUE_1); + nativeConnection.set(KEY_2, VALUE_1); + + assertThat(clusterConnection.keyCommands().touch(KEY_1_BYTES, KEY_2_BYTES, KEY_3_BYTES), is(2L)); + } + + @Test // DATAREDIS-694 + public void touchReturnsZeroIfNoKeysTouched() { + assertThat(clusterConnection.keyCommands().touch(KEY_1_BYTES), is(0L)); + } + + @Test // DATAREDIS-693 + public void unlinkReturnsNrOfKeysTouched() { + + nativeConnection.set(KEY_1, VALUE_1); + nativeConnection.set(KEY_2, VALUE_1); + + assertThat(clusterConnection.keyCommands().unlink(KEY_1_BYTES, KEY_2_BYTES, KEY_3_BYTES), is(2L)); + } + + @Test // DATAREDIS-693 + public void unlinkReturnsZeroIfNoKeysTouched() { + assertThat(clusterConnection.keyCommands().unlink(KEY_1_BYTES), is(0L)); + } } 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 f2bd47131..b4e650d36 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 @@ -29,11 +29,14 @@ import java.time.Duration; import java.time.Instant; import java.util.Arrays; +import org.junit.Rule; import org.junit.Test; import org.springframework.data.redis.RedisSystemException; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand; import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse; +import org.springframework.data.redis.test.util.MinimumRedisVersionRule; +import org.springframework.test.annotation.IfProfileValue; /** * Integration tests for {@link LettuceReactiveKeyCommands}. @@ -43,6 +46,8 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.Numeric */ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTestsBase { + public @Rule MinimumRedisVersionRule versionRule = new MinimumRedisVersionRule(); + @Test // DATAREDIS-525 public void existsShouldReturnTrueForExistingKeys() { @@ -306,4 +311,26 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest .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))) // + .expectNext(0L) // + .verifyComplete(); + } }