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 030974bf8c..d73867359b 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java @@ -43,6 +43,7 @@ * @author Andrey Shlykov * @author Shyngys Sapraliyev * @author John Blum + * @author Kim Sumin */ class DefaultZSetOperations extends AbstractOperations implements ZSetOperations { @@ -638,6 +639,48 @@ public Cursor> scan(K key, ScanOptions options) { return new ConvertingCursor<>(cursor, this::deserializeTuple); } + @Override + public Set> rangeByScoreWithScores(K key, Range range, Limit limit) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(range, "Range must not be null!"); + + byte[] rawKey = rawKey(key); + + return execute(connection -> { + Set result; + + if (limit.isUnlimited()) { + result = connection.zRangeByScoreWithScores(rawKey, range); + } else { + result = connection.zRangeByScoreWithScores(rawKey, range, limit); + } + + return deserializeTupleValues(result); + }); + } + + @Override + public Set> reverseRangeByScoreWithScores(K key, Range range, Limit limit) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(range, "Range must not be null!"); + + byte[] rawKey = rawKey(key); + + return execute(connection -> { + Set result; + + if (limit.isUnlimited()) { + result = connection.zRevRangeByScoreWithScores(rawKey, range); + } else { + result = connection.zRevRangeByScoreWithScores(rawKey, range, limit); + } + + return deserializeTupleValues(result); + }); + } + public Set rangeByScore(K key, String min, String max) { byte[] rawKey = rawKey(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 263346fe5f..f3df6f6044 100644 --- a/src/main/java/org/springframework/data/redis/core/ZSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ZSetOperations.java @@ -40,6 +40,7 @@ * @author Wongoo (望哥) * @author Andrey Shlykov * @author Shyngys Sapraliyev + * @author Kim Sumin */ public interface ZSetOperations { @@ -1271,4 +1272,63 @@ default Long reverseRangeAndStoreByScore(K srcKey, K dstKey, Range getOperations(); + + + /** + * Get set of {@link TypedTuple}s where score is between the values defined by the + * {@link Range} from sorted set. + * + * @param key must not be {@literal null}. + * @param range must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZRANGEBYSCORE + * @since 3.5 (or next version number) + */ + @Nullable + default Set> rangeByScoreWithScores(K key, Range range) { + return rangeByScoreWithScores(key, range, Limit.unlimited()); + } + + /** + * Get set of {@link TypedTuple}s where score is between the values defined by the + * {@link Range} and limited by the {@link Limit} from sorted set. + * + * @param key must not be {@literal null}. + * @param range must not be {@literal null}. + * @param limit can be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZRANGEBYSCORE + * @since 3.5 (or next version number) + */ + @Nullable + Set> rangeByScoreWithScores(K key, Range range, Limit limit); + + /** + * Get set of {@link TypedTuple}s where score is between the values defined by the + * {@link Range} from sorted set ordered from high to low. + * + * @param key must not be {@literal null}. + * @param range must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZREVRANGEBYSCORE + * @since 3.5 (or next version number) + */ + @Nullable + default Set> reverseRangeByScoreWithScores(K key, Range range) { + return reverseRangeByScoreWithScores(key, range, Limit.unlimited()); + } + + /** + * Get set of {@link TypedTuple}s where score is between the values defined by the + * {@link Range} and limited by the {@link Limit} from sorted set ordered from high to low. + * + * @param key must not be {@literal null}. + * @param range must not be {@literal null}. + * @param limit can be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: ZREVRANGEBYSCORE + * @since 3.5 (or next version number) + */ + @Nullable + Set> reverseRangeByScoreWithScores(K key, Range range, Limit limit); } diff --git a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java index dcfd41feb4..e0813a30ef 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.time.Duration; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -53,6 +54,7 @@ * @author Mark Paluch * @author Wongoo (望哥) * @author Andrey Shlykov + * @author Kim Sumin * @param Key type * @param Value type */ @@ -661,4 +663,100 @@ void testZsetUnionWithAggregateWeights() { assertThat(zSetOps.score(key1, value1)).isCloseTo(6.0, offset(0.1)); } + @ParameterizedRedisTest // GH-3139 + void testRangeByScoreWithScoresWithRange() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + V value3 = valueFactory.instance(); + + zSetOps.add(key, value1, 1.9); + zSetOps.add(key, value2, 3.7); + zSetOps.add(key, value3, 5.8); + + Range range = Range.of(Range.Bound.inclusive(1.5), Range.Bound.exclusive(5.0)); + + Set> results = zSetOps.rangeByScoreWithScores(key, range); + + assertThat(results).hasSize(2) + .contains(new DefaultTypedTuple<>(value1, 1.9)) + .contains(new DefaultTypedTuple<>(value2, 3.7)); + } + + @ParameterizedRedisTest // GH-3139 + void testRangeByScoreWithScoresWithRangeAndLimit() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + V value3 = valueFactory.instance(); + V value4 = valueFactory.instance(); + + zSetOps.add(key, value1, 1.0); + zSetOps.add(key, value2, 2.0); + zSetOps.add(key, value3, 3.0); + zSetOps.add(key, value4, 4.0); + + Range range = Range.of(Range.Bound.unbounded(), Range.Bound.inclusive(4.0)); + Limit limit = Limit.limit().offset(1).count(2); + + Set> results = zSetOps.rangeByScoreWithScores(key, range, limit); + + assertThat(results).hasSize(2) + .contains(new DefaultTypedTuple<>(value2, 2.0)) + .contains(new DefaultTypedTuple<>(value3, 3.0)); + } + + @ParameterizedRedisTest // GH-3139 + void testReverseRangeByScoreWithScoresWithRange() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + V value3 = valueFactory.instance(); + + zSetOps.add(key, value1, 1.9); + zSetOps.add(key, value2, 3.7); + zSetOps.add(key, value3, 5.8); + + Range range = Range.of(Range.Bound.inclusive(1.5), Range.Bound.exclusive(5.0)); + + Set> results = zSetOps.reverseRangeByScoreWithScores(key, range); + + assertThat(results).hasSize(2) + .contains(new DefaultTypedTuple<>(value1, 1.9)) + .contains(new DefaultTypedTuple<>(value2, 3.7)); + + assertThat(new ArrayList<>(results).get(0).getValue()).isEqualTo(value2); + assertThat(new ArrayList<>(results).get(1).getValue()).isEqualTo(value1); + } + + @ParameterizedRedisTest // GH-3139 + void testReverseRangeByScoreWithScoresWithRangeAndLimit() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + V value3 = valueFactory.instance(); + V value4 = valueFactory.instance(); + + zSetOps.add(key, value1, 1.0); + zSetOps.add(key, value2, 2.0); + zSetOps.add(key, value3, 3.0); + zSetOps.add(key, value4, 4.0); + + Range range = Range.of(Range.Bound.inclusive(1.0), Range.Bound.inclusive(4.0)); + Limit limit = Limit.limit().offset(1).count(2); + + Set> results = zSetOps.reverseRangeByScoreWithScores(key, range, limit); + + assertThat(results).hasSize(2) + .contains(new DefaultTypedTuple<>(value2, 2.0)) + .contains(new DefaultTypedTuple<>(value3, 3.0)); + + assertThat(new ArrayList<>(results).get(0).getValue()).isEqualTo(value3); + assertThat(new ArrayList<>(results).get(1).getValue()).isEqualTo(value2); + } + } diff --git a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsUnitTests.java b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsUnitTests.java index fdbb55b91e..e08e72ab09 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsUnitTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsUnitTests.java @@ -31,6 +31,7 @@ * Unit tests for {@link DefaultZSetOperations}. * * @author Christoph Strobl + * @author Kim Sumin */ class DefaultZSetOperationsUnitTests { @@ -69,4 +70,42 @@ void delegatesAddIfAbsentForTuples() { template.verify().zAdd(eq(template.serializeKey("key")), any(Set.class), eq(ZAddArgs.ifNotExists())); } + + @Test // GH-3139 + void delegatesRangeByScoreWithScoresWithRange() { + + Range range = Range.closed(1.0, 3.0); + zSetOperations.rangeByScoreWithScores("key", range); + + template.verify().zRangeByScoreWithScores(eq(template.serializeKey("key")), eq(range)); + } + + @Test // GH-3139 + void delegatesRangeByScoreWithScoresWithRangeAndLimit() { + + Range range = Range.closed(1.0, 3.0); + org.springframework.data.redis.connection.Limit limit = org.springframework.data.redis.connection.Limit.limit().offset(1).count(2); + zSetOperations.rangeByScoreWithScores("key", range, limit); + + template.verify().zRangeByScoreWithScores(eq(template.serializeKey("key")), eq(range), eq(limit)); + } + + @Test // GH-3139 + void delegatesReverseRangeByScoreWithScoresWithRange() { + + Range range = Range.closed(1.0, 3.0); + zSetOperations.reverseRangeByScoreWithScores("key", range); + + template.verify().zRevRangeByScoreWithScores(eq(template.serializeKey("key")), eq(range)); + } + + @Test // GH-3139 + void delegatesReverseRangeByScoreWithScoresWithRangeAndLimit() { + + Range range = Range.closed(1.0, 3.0); + org.springframework.data.redis.connection.Limit limit = org.springframework.data.redis.connection.Limit.limit().offset(1).count(2); + zSetOperations.reverseRangeByScoreWithScores("key", range, limit); + + template.verify().zRevRangeByScoreWithScores(eq(template.serializeKey("key")), eq(range), eq(limit)); + } }