From 507a882e93e952311f8b5a3939e97a33584693d3 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 15 Feb 2021 09:52:27 +0100 Subject: [PATCH] Add support for ZREMRANGEBYLEX. We now support ZREMRANGEBYLEX for Synchronous, Reactive, ZSetOperations and RedisZSet. Also provide a Kotlin coroutines variant via the extension for ReactiveZSetOperations. Closes #1816. Original pull request: #1968. --- .../DefaultStringRedisConnection.java | 18 +++++ .../connection/DefaultedRedisConnection.java | 7 ++ .../connection/ReactiveZSetCommands.java | 76 +++++++++++++++++++ .../redis/connection/RedisZSetCommands.java | 11 +++ .../connection/StringRedisConnection.java | 12 +++ .../jedis/JedisClusterZSetCommands.java | 20 +++++ .../connection/jedis/JedisZSetCommands.java | 16 ++++ .../lettuce/LettuceReactiveZSetCommands.java | 18 +++++ .../lettuce/LettuceZSetCommands.java | 14 ++++ .../data/redis/core/BoundZSetOperations.java | 11 +++ .../core/DefaultBoundZSetOperations.java | 9 +++ .../core/DefaultReactiveZSetOperations.java | 13 ++++ .../redis/core/DefaultZSetOperations.java | 11 +++ .../redis/core/ReactiveZSetOperations.java | 11 +++ .../data/redis/core/ZSetOperations.java | 12 +++ .../support/collections/DefaultRedisZSet.java | 10 +++ .../redis/support/collections/RedisZSet.java | 9 +++ .../core/ReactiveZSetOperationsExtensions.kt | 9 +++ .../AbstractConnectionIntegrationTests.java | 19 +++++ .../connection/RedisConnectionUnitTests.java | 5 ++ ...eReactiveZSetCommandsIntegrationTests.java | 20 +++++ .../core/ConnectionMockingRedisTemplate.java | 76 +++++++++++++++++++ .../DefaultBoundZSetOperationsUnitTests.java | 47 ++++++++++++ .../core/DefaultZSetOperationsUnitTests.java | 47 ++++++++++++ ...activeZSetOperationsExtensionsUnitTests.kt | 15 ++++ 25 files changed, 516 insertions(+) create mode 100644 src/test/java/org/springframework/data/redis/core/ConnectionMockingRedisTemplate.java create mode 100644 src/test/java/org/springframework/data/redis/core/DefaultBoundZSetOperationsUnitTests.java create mode 100644 src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsUnitTests.java 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 00023ea61..90976449b 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -1581,6 +1581,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.zRemRange(key, start, end), Converters.identityConverter()); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRemRangeByLex(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Range) + */ + @Override + public Long zRemRangeByLex(byte[] key, Range range) { + return convertAndReturn(delegate.zRemRangeByLex(key, range), Converters.identityConverter()); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zRemRangeByScore(byte[], double, double) @@ -2829,6 +2838,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return zRemRange(serialize(key), start, end); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#zRemRange(java.lang.String, org.springframework.data.redis.connection.RedisZSetCommands.Range) + */ + @Override + public Long zRemRangeByLex(String key, Range range) { + return zRemRangeByLex(serialize(key), range); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.StringRedisConnection#zRemRangeByScore(java.lang.String, double, double) 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 8594d8f50..41c50d7f1 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -1024,6 +1024,13 @@ public interface DefaultedRedisConnection extends RedisConnection { return zSetCommands().zRemRange(key, start, end); } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ + @Override + @Deprecated + default Long zRemRangeByLex(byte[] key, Range range) { + return zSetCommands().zRemRangeByLex(key, range); + } + /** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */ @Override @Deprecated diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java index e9a7aeadd..27e24253c 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveZSetCommands.java @@ -1444,6 +1444,82 @@ public interface ReactiveZSetCommands { */ Flux> zRemRangeByScore(Publisher commands); + /** + * {@code ZREMRANGEBYLEX} command parameters. + * + * @author Christoph Strobl + * @since 2.5 + * @see Redis Documentation: ZREMRANGEBYLEX + */ + class ZRemRangeByLexCommand extends KeyCommand { + + private final Range range; + + private ZRemRangeByLexCommand(@Nullable ByteBuffer key, Range range) { + + super(key); + this.range = range; + } + + /** + * Creates a new {@link ZRemRangeByLexCommand} given a {@link Range}. + * + * @param range must not be {@literal null}. + * @return a new {@link ZRemRangeByScoreCommand} for {@link Range}. + */ + public static ZRemRangeByLexCommand lexWithin(Range range) { + return new ZRemRangeByLexCommand(null, range); + } + + /** + * Applies the {@literal key}. Constructs a new command instance with all previously configured properties. + * + * @param key must not be {@literal null}. + * @return a new {@link ZRemRangeByLexCommand} with {@literal key} applied. + */ + public ZRemRangeByLexCommand from(ByteBuffer key) { + + Assert.notNull(key, "Key must not be null!"); + + return new ZRemRangeByLexCommand(key, range); + } + + /** + * @return + */ + public Range getRange() { + return range; + } + } + + /** + * Remove elements in {@link Range} from sorted set with {@literal key}. + * + * @param key must not be {@literal null}. + * @param range must not be {@literal null}. + * @return a {@link Mono} emitting the number of removed elements. + * @since 2.5 + * @see Redis Documentation: ZREMRANGEBYLEX + */ + default Mono zRemRangeByLex(ByteBuffer key, Range range) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(range, "Range must not be null!"); + + return zRemRangeByLex(Mono.just(ZRemRangeByLexCommand.lexWithin(range).from(key))).next() + .map(NumericResponse::getOutput); + } + + /** + * Remove elements in {@link Range} from sorted set with {@link ZRemRangeByLexCommand#getKey()}. + * + * @param commands must not be {@literal null}. + * @return + * @since 2.5 + * @see Redis Documentation: ZREMRANGEBYLEX + */ + Flux> zRemRangeByLex(Publisher commands); + /** * {@code ZUNIONSTORE} command parameters. * diff --git a/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java index a9ced6240..d26e5af2f 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisZSetCommands.java @@ -814,6 +814,17 @@ public interface RedisZSetCommands { @Nullable Long zRemRange(byte[] key, long start, long end); + /** + * Remove all elements between the lexicographical {@link Range}. + * + * @param key must not be {@literal null}. + * @param range must not be {@literal null}. + * @return the number of elements removed, or {@literal null} when used in pipeline / transaction. + * @since 2.5 + * @see Redis Documentation: ZREMRANGEBYLEX + */ + Long zRemRangeByLex(byte[] key, Range range); + /** * Remove elements with scores between {@code min} and {@code max} from sorted set with {@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 7b353ba3b..8633ea3a5 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -1376,6 +1376,18 @@ public interface StringRedisConnection extends RedisConnection { */ Long zRemRange(String key, long start, long end); + + /** + * Remove all elements between the lexicographical {@link Range}. + * + * @param key must not be {@literal null}. + * @param range must not be {@literal null}. + * @return the number of elements removed, or {@literal null} when used in pipeline / transaction. + * @since 2.5 + * @see Redis Documentation: ZREMRANGEBYLEX + */ + Long zRemRangeByLex(String key, Range range); + /** * Remove elements with scores between {@code min} and {@code max} from sorted set with {@code key}. * diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterZSetCommands.java index e64492dbf..a37359fc7 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterZSetCommands.java @@ -348,6 +348,26 @@ class JedisClusterZSetCommands implements RedisZSetCommands { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRemRangeByLex(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Range) + */ + @Override + public Long zRemRangeByLex(byte[] key, Range range) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(range, "Range must not be null for ZREMRANGEBYLEX!"); + + byte[] min = JedisConverters.boundaryToBytesForZRangeByLex(range.getMin(), JedisConverters.MINUS_BYTES); + byte[] max = JedisConverters.boundaryToBytesForZRangeByLex(range.getMax(), JedisConverters.PLUS_BYTES); + + try { + return connection.getCluster().zremrangeByLex(key, min, max); + } catch (Exception ex) { + throw convertJedisAccessException(ex); + } + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zRevRangeByLex(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Range, org.springframework.data.redis.connection.RedisZSetCommands.Limit) diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisZSetCommands.java index 48c70d506..d9c396a8e 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisZSetCommands.java @@ -336,6 +336,22 @@ class JedisZSetCommands implements RedisZSetCommands { end); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRemRangeByLex(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Range) + */ + @Override + public Long zRemRangeByLex(byte[] key, Range range) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(range, "Range must not be null for ZREMRANGEBYLEX!"); + + byte[] min = JedisConverters.boundaryToBytesForZRangeByLex(range.getMin(), JedisConverters.MINUS_BYTES); + byte[] max = JedisConverters.boundaryToBytesForZRangeByLex(range.getMax(), JedisConverters.PLUS_BYTES); + + return connection.invoke().just(BinaryJedis::zremrangeByLex, MultiKeyPipelineBase::zremrangeByLex, key, min, max); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zRemRangeByScore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Range) diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java index 254dba78d..1cc6ae1c3 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java @@ -418,6 +418,24 @@ class LettuceReactiveZSetCommands implements ReactiveZSetCommands { })); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zRemRangeByLex(org.reactivestreams.Publisher) + */ + @Override + public Flux> zRemRangeByLex(Publisher commands) { + + return connection.execute(cmd -> Flux.from(commands).concatMap(command -> { + + Assert.notNull(command.getKey(), "Key must not be null!"); + Assert.notNull(command.getRange(), "Range must not be null!"); + + Mono result = cmd.zremrangebylex(command.getKey(), RangeConverter.toRange(command.getRange())); + + return result.map(value -> new NumericResponse<>(command, value)); + })); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.ReactiveZSetCommands#zUnionStore(org.reactivestreams.Publisher) diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceZSetCommands.java index da17bd195..9d011364c 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceZSetCommands.java @@ -312,6 +312,20 @@ class LettuceZSetCommands implements RedisZSetCommands { return connection.invoke().just(RedisSortedSetAsyncCommands::zremrangebyrank, key, start, end); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisZSetCommands#zRemRangeByLex(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Range) + */ + @Override + public Long zRemRangeByLex(byte[] key, Range range) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(range, "Range must not be null for ZREMRANGEBYLEX!"); + + return connection.invoke().just(RedisSortedSetAsyncCommands::zremrangebylex, key, + LettuceConverters. toRange(range, true)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisZSetCommands#zRemRangeByScore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Range) diff --git a/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java b/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java index 69cb3facf..5baa3572e 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundZSetOperations.java @@ -252,6 +252,17 @@ public interface BoundZSetOperations extends BoundKeyOperations { @Nullable Long removeRange(long start, long end); + /** + * Remove elements in {@link Range} from sorted set with the bound key. + * + * @param range must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.5 + * @see Redis Documentation: ZREMRANGEBYLEX + */ + @Nullable + Long removeRangeByLex(Range range); + /** * Remove elements with scores between {@code min} and {@code max} from sorted set with the bound key. * diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java index 7b2efd106..fc3b5f9b7 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultBoundZSetOperations.java @@ -249,6 +249,15 @@ class DefaultBoundZSetOperations extends DefaultBoundKeyOperations impl return ops.removeRange(getKey(), start, end); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundZSetOperations#removeRangeByLex(org.springframework.data.redis.connection.RedisZSetCommands.Range) + */ + @Override + public Long removeRangeByLex(Range range) { + return ops.removeRangeByLex(getKey(), range); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.BoundZSetOperations#removeRangeByScore(double, double) diff --git a/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java index 327a9cd3a..0995bc96d 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultReactiveZSetOperations.java @@ -383,6 +383,19 @@ class DefaultReactiveZSetOperations implements ReactiveZSetOperations connection.zRemRangeByRank(rawKey(key), range)); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveZSetOperations#removeRangebyLex(java.lang.Object, org.springframework.data.domain.Range) + */ + @Override + public Mono removeRangeByLex(K key, Range range) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(range, "Range must not be null!"); + + return createMono(connection -> connection.zRemRangeByLex(rawKey(key), range)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveZSetOperations#removeRangeByScore(java.lang.Object, org.springframework.data.domain.Range) diff --git a/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java index 3c2dac203..1c1499132 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java @@ -353,6 +353,17 @@ class DefaultZSetOperations extends AbstractOperations implements ZS return execute(connection -> connection.zRemRange(rawKey, start, end), true); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ZSetOperations#removeRangeByLex(java.lang.Object, Range) + */ + @Override + public Long removeRangeByLex(K key, Range range) { + + byte[] rawKey = rawKey(key); + return execute(connection -> connection.zRemRangeByLex(rawKey, range), true); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.ZSetOperations#removeRangeByScore(java.lang.Object, double, double) diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java b/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java index 0db8491a2..1914da966 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveZSetOperations.java @@ -310,6 +310,17 @@ public interface ReactiveZSetOperations { */ Mono removeRange(K key, Range range); + /** + * Remove elements in range from sorted set with {@code key}. + * + * @param key must not be {@literal null}. + * @param range must not be {@literal null}. + * @return a {@link Mono} emitting the number or removed elements. + * @since 2.5 + * @see Redis Documentation: ZREMRANGEBYRANK + */ + Mono removeRangeByLex(K key, Range range); + /** * Remove elements with scores between {@code min} and {@code max} from sorted set with {@code key}. * diff --git a/src/main/java/org/springframework/data/redis/core/ZSetOperations.java b/src/main/java/org/springframework/data/redis/core/ZSetOperations.java index 61b7cc55a..4aa5c8263 100644 --- a/src/main/java/org/springframework/data/redis/core/ZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ZSetOperations.java @@ -344,6 +344,18 @@ public interface ZSetOperations { @Nullable Long removeRange(K key, long start, long end); + /** + * Remove elements in {@link Range} from sorted set with {@literal key}. + * + * @param key must not be {@literal null}. + * @param range must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.5 + * @see Redis Documentation: ZREMRANGEBYLEX + */ + @Nullable + Long removeRangeByLex(K key, Range range); + /** * Remove elements with scores between {@code min} and {@code max} from sorted set with {@code key}. * diff --git a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisZSet.java b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisZSet.java index 1b3d29d6a..4fda95ea6 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisZSet.java +++ b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisZSet.java @@ -226,6 +226,16 @@ public class DefaultRedisZSet extends AbstractRedisCollection implements R return this; } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.support.collections.RedisZSet#removeRangeByLex(org.springframework.data.redis.connection.RedisZSetCommands.Range) + */ + @Override + public Set removeByLex(Range range) { + boundZSetOps.removeRangeByLex(range); + return this; + } + /* * (non-Javadoc) * @see org.springframework.data.redis.support.collections.RedisZSet#removeByScore(double, double) diff --git a/src/main/java/org/springframework/data/redis/support/collections/RedisZSet.java b/src/main/java/org/springframework/data/redis/support/collections/RedisZSet.java index 53777114c..e6af859e5 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/RedisZSet.java +++ b/src/main/java/org/springframework/data/redis/support/collections/RedisZSet.java @@ -118,6 +118,15 @@ public interface RedisZSet extends RedisCollection, Set { RedisZSet remove(long start, long end); + /** + * Remove all elements in range. + * + * @param range must not be {@literal null}. + * @return never {@literal null}. + * @since 2.5 + */ + Set removeByLex(Range range); + RedisZSet removeByScore(double min, double max); /** diff --git a/src/main/kotlin/org/springframework/data/redis/core/ReactiveZSetOperationsExtensions.kt b/src/main/kotlin/org/springframework/data/redis/core/ReactiveZSetOperationsExtensions.kt index fc28bca96..494da393b 100644 --- a/src/main/kotlin/org/springframework/data/redis/core/ReactiveZSetOperationsExtensions.kt +++ b/src/main/kotlin/org/springframework/data/redis/core/ReactiveZSetOperationsExtensions.kt @@ -186,6 +186,15 @@ suspend fun ReactiveZSetOperations.scoreAndAwait(key: K suspend fun ReactiveZSetOperations.removeRangeAndAwait(key: K, range: Range): Long = removeRange(key, range).awaitSingle() +/** + * Coroutines variant of [ReactiveZSetOperations.removeRangeByLex]. + * + * @author Christoph Strobl + * @since 2.5 + */ +suspend fun ReactiveZSetOperations.removeRangeByLexAndAwait(key: K, range: Range): Long = + removeRangeByLex(key, range).awaitSingle() + /** * Coroutines variant of [ReactiveZSetOperations.removeRangeByScore]. * 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 b305a44d8..d9698ae66 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -1909,6 +1909,25 @@ public abstract class AbstractConnectionIntegrationTests { verifyResults(Arrays.asList(new Object[] { true, true, 2L, new LinkedHashSet(0) })); } + @Test // GH-1816 + void testZRemRangeByLex() { + + actual.add(connection.zAdd("myset", 0, "aaaa")); + actual.add(connection.zAdd("myset", 0, "b")); + actual.add(connection.zAdd("myset", 0, "c")); + actual.add(connection.zAdd("myset", 0, "d")); + actual.add(connection.zAdd("myset", 0, "e")); + actual.add(connection.zAdd("myset", 0, "foo")); + actual.add(connection.zAdd("myset", 0, "zap")); + actual.add(connection.zAdd("myset", 0, "zip")); + actual.add(connection.zAdd("myset", 0, "ALPHA")); + actual.add(connection.zAdd("myset", 0, "alpha")); + actual.add(connection.zRemRangeByLex("myset", Range.range().gte("alpha").lte("omega"))); + + actual.add(connection.zRange("myset", 0L, -1L)); + verifyResults(Arrays.asList(new Object[] { true, true, true,true, true, true,true, true, true,true, 6L, new LinkedHashSet(Arrays.asList("ALPHA", "aaaa", "zap", "zip")) })); + } + @Test void testZRemRangeByScore() { actual.add(connection.zAdd("myset", 2, "Bob")); 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 1b0d88e56..b5a995fea 100644 --- a/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java @@ -948,6 +948,11 @@ class RedisConnectionUnitTests { return delegate.zRemRangeByScore(key, range); } + @Override + public Long zRemRangeByLex(byte[] key, Range range) { + return delegate.zRemRangeByLex(key, range); + } + @Override public Set zRangeByScore(byte[] key, Range range) { return delegate.zRangeByScore(key, range); diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsIntegrationTests.java index 3fcf219e7..0e4cb2de4 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsIntegrationTests.java @@ -412,6 +412,26 @@ public class LettuceReactiveZSetCommandsIntegrationTests extends LettuceReactive assertThat(connection.zSetCommands().zRemRangeByRank(KEY_1_BBUFFER, ONE_TO_TWO).block()).isEqualTo(2L); } + @ParameterizedRedisTest // GH-1816 + void zRemRangeByLexRemovesValuesCorrectly() { + + nativeCommands.zadd(KEY_1, 0D, "aaaa"); + nativeCommands.zadd(KEY_1, 0D, "b"); + nativeCommands.zadd(KEY_1, 0D, "c"); + nativeCommands.zadd(KEY_1, 0D, "d"); + nativeCommands.zadd(KEY_1, 0D, "e"); + nativeCommands.zadd(KEY_1, 0D, "foo"); + nativeCommands.zadd(KEY_1, 0D, "zap"); + nativeCommands.zadd(KEY_1, 0D, "zip"); + nativeCommands.zadd(KEY_1, 0D, "ALPHA"); + nativeCommands.zadd(KEY_1, 0D, "alpha"); + + connection.zSetCommands().zRemRangeByLex(KEY_1_BBUFFER, Range.closed("alpha", "omega")) // + .as(StepVerifier::create) // + .expectNext(6L) // + .verifyComplete(); + } + @ParameterizedRedisTest // DATAREDIS-525 void zRemRangeByScoreShouldRemoveValuesCorrectly() { diff --git a/src/test/java/org/springframework/data/redis/core/ConnectionMockingRedisTemplate.java b/src/test/java/org/springframework/data/redis/core/ConnectionMockingRedisTemplate.java new file mode 100644 index 000000000..a59efb08f --- /dev/null +++ b/src/test/java/org/springframework/data/redis/core/ConnectionMockingRedisTemplate.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.core; + +import org.mockito.Mockito; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.RedisSerializer; + +/** + * @author Christoph Strobl + */ +public class ConnectionMockingRedisTemplate extends RedisTemplate { + + private RedisConnection connectionMock; + + private ConnectionMockingRedisTemplate() { + + connectionMock = Mockito.mock(RedisConnection.class); + + RedisConnectionFactory connectionFactory = Mockito.mock(RedisConnectionFactory.class); + Mockito.when(connectionFactory.getConnection()).thenReturn(connectionMock); + + setConnectionFactory(connectionFactory); + } + + static ConnectionMockingRedisTemplate template() { + return builder().build(); + } + + static MockTemplateBuilder builder() { + return new MockTemplateBuilder(); + } + + public RedisConnection verify() { + return Mockito.verify(connectionMock); + } + + public byte[] serializeKey(K key) { + return ((RedisSerializer) getKeySerializer()).serialize(key); + } + + public RedisConnection never() { + return Mockito.verify(connectionMock, Mockito.never()); + } + + public RedisConnection doReturn(Object o) { + return Mockito.doReturn(o).when(connectionMock); + } + + public static class MockTemplateBuilder { + + private ConnectionMockingRedisTemplate template = new ConnectionMockingRedisTemplate(); + + public ConnectionMockingRedisTemplate build() { + + template.afterPropertiesSet(); + return template; + } + + } + +} diff --git a/src/test/java/org/springframework/data/redis/core/DefaultBoundZSetOperationsUnitTests.java b/src/test/java/org/springframework/data/redis/core/DefaultBoundZSetOperationsUnitTests.java new file mode 100644 index 000000000..a94dca556 --- /dev/null +++ b/src/test/java/org/springframework/data/redis/core/DefaultBoundZSetOperationsUnitTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.core; + +import static org.mockito.ArgumentMatchers.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.redis.connection.RedisZSetCommands.Range; + +/** + * @author Christoph Strobl + */ +class DefaultBoundZSetOperationsUnitTests { + + DefaultBoundZSetOperations zSetOperations; + ConnectionMockingRedisTemplate template; + + @BeforeEach + void beforeEach() { + + template = ConnectionMockingRedisTemplate.template(); + zSetOperations = new DefaultBoundZSetOperations<>("key", template); + } + + @Test // GH-1816 + void delegatesRemoveRangeByLex() { + + Range range = Range.range().gte("alpha").lte("omega"); + zSetOperations.removeRangeByLex(range); + + template.verify().zRemRangeByLex(eq(template.serializeKey("key")), eq(range)); + } +} diff --git a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsUnitTests.java b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsUnitTests.java new file mode 100644 index 000000000..47ef7564f --- /dev/null +++ b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsUnitTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.core; + +import static org.mockito.ArgumentMatchers.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.redis.connection.RedisZSetCommands.Range; + +/** + * @author Christoph Strobl + */ +class DefaultZSetOperationsUnitTests { + + DefaultZSetOperations zSetOperations; + ConnectionMockingRedisTemplate template; + + @BeforeEach + void beforeEach() { + + template = ConnectionMockingRedisTemplate.template(); + zSetOperations = new DefaultZSetOperations<>(template); + } + + @Test // GH-1816 + void delegatesRemoveRangeByLex() { + + Range range = Range.range().gte("alpha").lte("omega"); + zSetOperations.removeRangeByLex("key", range); + + template.verify().zRemRangeByLex(eq(template.serializeKey("key")), eq(range)); + } +} diff --git a/src/test/kotlin/org/springframework/data/redis/core/ReactiveZSetOperationsExtensionsUnitTests.kt b/src/test/kotlin/org/springframework/data/redis/core/ReactiveZSetOperationsExtensionsUnitTests.kt index 64e138d37..ead3ea627 100644 --- a/src/test/kotlin/org/springframework/data/redis/core/ReactiveZSetOperationsExtensionsUnitTests.kt +++ b/src/test/kotlin/org/springframework/data/redis/core/ReactiveZSetOperationsExtensionsUnitTests.kt @@ -366,6 +366,21 @@ class ReactiveZSetOperationsExtensionsUnitTests { } } + @Test // GH-1816 + fun removeRangeByLex() { + + val operations = mockk>() + every { operations.removeRangeByLex(any(), any()) } returns Mono.just(1) + + runBlocking { + assertThat(operations.removeRangeByLexAndAwait("foo", Range.unbounded())).isEqualTo(1) + } + + verify { + operations.removeRangeByLex("foo", Range.unbounded()) + } + } + @Test // DATAREDIS-937 fun removeRangeByScore() {