Skip to content

Commit a589022

Browse files
committed
Add Range and Limit support to ZSetOperations.rangeByScoreWithScores
This commit adds support for the Range and Limit parameters to the rangeByScoreWithScores and reverseRangeByScoreWithScores methods in ZSetOperations interface. The implementation follows the same pattern already used for other methods like rangeByLex, providing a more flexible and consistent API for working with Redis sorted sets. New methods: - rangeByScoreWithScores(K key, Range<Double> range) - rangeByScoreWithScores(K key, Range<Double> range, Limit limit) - reverseRangeByScoreWithScores(K key, Range<Double> range) - reverseRangeByScoreWithScores(K key, Range<Double> range, Limit limit) Unit and integration tests added to verify functionality. Fixes #3139 Related to #796 and #938 Signed-off-by: kssumin <[email protected]>
1 parent 5029094 commit a589022

File tree

4 files changed

+240
-0
lines changed

4 files changed

+240
-0
lines changed

src/main/java/org/springframework/data/redis/core/DefaultZSetOperations.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* @author Andrey Shlykov
4444
* @author Shyngys Sapraliyev
4545
* @author John Blum
46+
* @author Kim Sumin
4647
*/
4748
class DefaultZSetOperations<K, V> extends AbstractOperations<K, V> implements ZSetOperations<K, V> {
4849

@@ -638,6 +639,48 @@ public Cursor<TypedTuple<V>> scan(K key, ScanOptions options) {
638639
return new ConvertingCursor<>(cursor, this::deserializeTuple);
639640
}
640641

642+
@Override
643+
public Set<TypedTuple<V>> rangeByScoreWithScores(K key, Range<Double> range, Limit limit) {
644+
645+
Assert.notNull(key, "Key must not be null!");
646+
Assert.notNull(range, "Range must not be null!");
647+
648+
byte[] rawKey = rawKey(key);
649+
650+
return execute(connection -> {
651+
Set<Tuple> result;
652+
653+
if (limit.isUnlimited()) {
654+
result = connection.zRangeByScoreWithScores(rawKey, range);
655+
} else {
656+
result = connection.zRangeByScoreWithScores(rawKey, range, limit);
657+
}
658+
659+
return deserializeTupleValues(result);
660+
});
661+
}
662+
663+
@Override
664+
public Set<TypedTuple<V>> reverseRangeByScoreWithScores(K key, Range<Double> range, Limit limit) {
665+
666+
Assert.notNull(key, "Key must not be null!");
667+
Assert.notNull(range, "Range must not be null!");
668+
669+
byte[] rawKey = rawKey(key);
670+
671+
return execute(connection -> {
672+
Set<Tuple> result;
673+
674+
if (limit.isUnlimited()) {
675+
result = connection.zRevRangeByScoreWithScores(rawKey, range);
676+
} else {
677+
result = connection.zRevRangeByScoreWithScores(rawKey, range, limit);
678+
}
679+
680+
return deserializeTupleValues(result);
681+
});
682+
}
683+
641684
public Set<byte[]> rangeByScore(K key, String min, String max) {
642685

643686
byte[] rawKey = rawKey(key);

src/main/java/org/springframework/data/redis/core/ZSetOperations.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
* @author Wongoo (望哥)
4141
* @author Andrey Shlykov
4242
* @author Shyngys Sapraliyev
43+
* @author Kim Sumin
4344
*/
4445
public interface ZSetOperations<K, V> {
4546

@@ -1271,4 +1272,63 @@ default Long reverseRangeAndStoreByScore(K srcKey, K dstKey, Range<? extends Num
12711272
* @return never {@literal null}.
12721273
*/
12731274
RedisOperations<K, V> getOperations();
1275+
1276+
1277+
/**
1278+
* Get set of {@link TypedTuple}s where score is between the values defined by the
1279+
* {@link Range} from sorted set.
1280+
*
1281+
* @param key must not be {@literal null}.
1282+
* @param range must not be {@literal null}.
1283+
* @return {@literal null} when used in pipeline / transaction.
1284+
* @see <a href="https://redis.io/commands/zrangebyscore">Redis Documentation: ZRANGEBYSCORE</a>
1285+
* @since 3.5 (or next version number)
1286+
*/
1287+
@Nullable
1288+
default Set<TypedTuple<V>> rangeByScoreWithScores(K key, Range<Double> range) {
1289+
return rangeByScoreWithScores(key, range, Limit.unlimited());
1290+
}
1291+
1292+
/**
1293+
* Get set of {@link TypedTuple}s where score is between the values defined by the
1294+
* {@link Range} and limited by the {@link Limit} from sorted set.
1295+
*
1296+
* @param key must not be {@literal null}.
1297+
* @param range must not be {@literal null}.
1298+
* @param limit can be {@literal null}.
1299+
* @return {@literal null} when used in pipeline / transaction.
1300+
* @see <a href="https://redis.io/commands/zrangebyscore">Redis Documentation: ZRANGEBYSCORE</a>
1301+
* @since 3.5 (or next version number)
1302+
*/
1303+
@Nullable
1304+
Set<TypedTuple<V>> rangeByScoreWithScores(K key, Range<Double> range, Limit limit);
1305+
1306+
/**
1307+
* Get set of {@link TypedTuple}s where score is between the values defined by the
1308+
* {@link Range} from sorted set ordered from high to low.
1309+
*
1310+
* @param key must not be {@literal null}.
1311+
* @param range must not be {@literal null}.
1312+
* @return {@literal null} when used in pipeline / transaction.
1313+
* @see <a href="https://redis.io/commands/zrevrangebyscore">Redis Documentation: ZREVRANGEBYSCORE</a>
1314+
* @since 3.5 (or next version number)
1315+
*/
1316+
@Nullable
1317+
default Set<TypedTuple<V>> reverseRangeByScoreWithScores(K key, Range<Double> range) {
1318+
return reverseRangeByScoreWithScores(key, range, Limit.unlimited());
1319+
}
1320+
1321+
/**
1322+
* Get set of {@link TypedTuple}s where score is between the values defined by the
1323+
* {@link Range} and limited by the {@link Limit} from sorted set ordered from high to low.
1324+
*
1325+
* @param key must not be {@literal null}.
1326+
* @param range must not be {@literal null}.
1327+
* @param limit can be {@literal null}.
1328+
* @return {@literal null} when used in pipeline / transaction.
1329+
* @see <a href="https://redis.io/commands/zrevrangebyscore">Redis Documentation: ZREVRANGEBYSCORE</a>
1330+
* @since 3.5 (or next version number)
1331+
*/
1332+
@Nullable
1333+
Set<TypedTuple<V>> reverseRangeByScoreWithScores(K key, Range<Double> range, Limit limit);
12741334
}

src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsIntegrationTests.java

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import java.io.IOException;
2323
import java.time.Duration;
24+
import java.util.ArrayList;
2425
import java.util.Collection;
2526
import java.util.Collections;
2627
import java.util.HashSet;
@@ -53,6 +54,7 @@
5354
* @author Mark Paluch
5455
* @author Wongoo (望哥)
5556
* @author Andrey Shlykov
57+
* @author Kim Sumin
5658
* @param <K> Key type
5759
* @param <V> Value type
5860
*/
@@ -661,4 +663,100 @@ void testZsetUnionWithAggregateWeights() {
661663
assertThat(zSetOps.score(key1, value1)).isCloseTo(6.0, offset(0.1));
662664
}
663665

666+
@ParameterizedRedisTest // GH-3139
667+
void testRangeByScoreWithScoresWithRange() {
668+
669+
K key = keyFactory.instance();
670+
V value1 = valueFactory.instance();
671+
V value2 = valueFactory.instance();
672+
V value3 = valueFactory.instance();
673+
674+
zSetOps.add(key, value1, 1.9);
675+
zSetOps.add(key, value2, 3.7);
676+
zSetOps.add(key, value3, 5.8);
677+
678+
Range<Double> range = Range.of(Range.Bound.inclusive(1.5), Range.Bound.exclusive(5.0));
679+
680+
Set<TypedTuple<V>> results = zSetOps.rangeByScoreWithScores(key, range);
681+
682+
assertThat(results).hasSize(2)
683+
.contains(new DefaultTypedTuple<>(value1, 1.9))
684+
.contains(new DefaultTypedTuple<>(value2, 3.7));
685+
}
686+
687+
@ParameterizedRedisTest // GH-3139
688+
void testRangeByScoreWithScoresWithRangeAndLimit() {
689+
690+
K key = keyFactory.instance();
691+
V value1 = valueFactory.instance();
692+
V value2 = valueFactory.instance();
693+
V value3 = valueFactory.instance();
694+
V value4 = valueFactory.instance();
695+
696+
zSetOps.add(key, value1, 1.0);
697+
zSetOps.add(key, value2, 2.0);
698+
zSetOps.add(key, value3, 3.0);
699+
zSetOps.add(key, value4, 4.0);
700+
701+
Range<Double> range = Range.of(Range.Bound.unbounded(), Range.Bound.inclusive(4.0));
702+
Limit limit = Limit.limit().offset(1).count(2);
703+
704+
Set<TypedTuple<V>> results = zSetOps.rangeByScoreWithScores(key, range, limit);
705+
706+
assertThat(results).hasSize(2)
707+
.contains(new DefaultTypedTuple<>(value2, 2.0))
708+
.contains(new DefaultTypedTuple<>(value3, 3.0));
709+
}
710+
711+
@ParameterizedRedisTest // GH-3139
712+
void testReverseRangeByScoreWithScoresWithRange() {
713+
714+
K key = keyFactory.instance();
715+
V value1 = valueFactory.instance();
716+
V value2 = valueFactory.instance();
717+
V value3 = valueFactory.instance();
718+
719+
zSetOps.add(key, value1, 1.9);
720+
zSetOps.add(key, value2, 3.7);
721+
zSetOps.add(key, value3, 5.8);
722+
723+
Range<Double> range = Range.of(Range.Bound.inclusive(1.5), Range.Bound.exclusive(5.0));
724+
725+
Set<TypedTuple<V>> results = zSetOps.reverseRangeByScoreWithScores(key, range);
726+
727+
assertThat(results).hasSize(2)
728+
.contains(new DefaultTypedTuple<>(value1, 1.9))
729+
.contains(new DefaultTypedTuple<>(value2, 3.7));
730+
731+
assertThat(new ArrayList<>(results).get(0).getValue()).isEqualTo(value2);
732+
assertThat(new ArrayList<>(results).get(1).getValue()).isEqualTo(value1);
733+
}
734+
735+
@ParameterizedRedisTest // GH-3139
736+
void testReverseRangeByScoreWithScoresWithRangeAndLimit() {
737+
738+
K key = keyFactory.instance();
739+
V value1 = valueFactory.instance();
740+
V value2 = valueFactory.instance();
741+
V value3 = valueFactory.instance();
742+
V value4 = valueFactory.instance();
743+
744+
zSetOps.add(key, value1, 1.0);
745+
zSetOps.add(key, value2, 2.0);
746+
zSetOps.add(key, value3, 3.0);
747+
zSetOps.add(key, value4, 4.0);
748+
749+
Range<Double> range = Range.of(Range.Bound.inclusive(1.0), Range.Bound.inclusive(4.0));
750+
Limit limit = Limit.limit().offset(1).count(2);
751+
752+
Set<TypedTuple<V>> results = zSetOps.reverseRangeByScoreWithScores(key, range, limit);
753+
754+
assertThat(results).hasSize(2)
755+
.contains(new DefaultTypedTuple<>(value2, 2.0))
756+
.contains(new DefaultTypedTuple<>(value3, 3.0));
757+
758+
assertThat(new ArrayList<>(results).get(0).getValue()).isEqualTo(value3);
759+
assertThat(new ArrayList<>(results).get(1).getValue()).isEqualTo(value2);
760+
}
761+
664762
}

src/test/java/org/springframework/data/redis/core/DefaultZSetOperationsUnitTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
* Unit tests for {@link DefaultZSetOperations}.
3232
*
3333
* @author Christoph Strobl
34+
* @author Kim Sumin
3435
*/
3536
class DefaultZSetOperationsUnitTests {
3637

@@ -69,4 +70,42 @@ void delegatesAddIfAbsentForTuples() {
6970

7071
template.verify().zAdd(eq(template.serializeKey("key")), any(Set.class), eq(ZAddArgs.ifNotExists()));
7172
}
73+
74+
@Test // GH-3139
75+
void delegatesRangeByScoreWithScoresWithRange() {
76+
77+
Range<Double> range = Range.closed(1.0, 3.0);
78+
zSetOperations.rangeByScoreWithScores("key", range);
79+
80+
template.verify().zRangeByScoreWithScores(eq(template.serializeKey("key")), eq(range));
81+
}
82+
83+
@Test // GH-3139
84+
void delegatesRangeByScoreWithScoresWithRangeAndLimit() {
85+
86+
Range<Double> range = Range.closed(1.0, 3.0);
87+
org.springframework.data.redis.connection.Limit limit = org.springframework.data.redis.connection.Limit.limit().offset(1).count(2);
88+
zSetOperations.rangeByScoreWithScores("key", range, limit);
89+
90+
template.verify().zRangeByScoreWithScores(eq(template.serializeKey("key")), eq(range), eq(limit));
91+
}
92+
93+
@Test // GH-3139
94+
void delegatesReverseRangeByScoreWithScoresWithRange() {
95+
96+
Range<Double> range = Range.closed(1.0, 3.0);
97+
zSetOperations.reverseRangeByScoreWithScores("key", range);
98+
99+
template.verify().zRevRangeByScoreWithScores(eq(template.serializeKey("key")), eq(range));
100+
}
101+
102+
@Test // GH-3139
103+
void delegatesReverseRangeByScoreWithScoresWithRangeAndLimit() {
104+
105+
Range<Double> range = Range.closed(1.0, 3.0);
106+
org.springframework.data.redis.connection.Limit limit = org.springframework.data.redis.connection.Limit.limit().offset(1).count(2);
107+
zSetOperations.reverseRangeByScoreWithScores("key", range, limit);
108+
109+
template.verify().zRevRangeByScoreWithScores(eq(template.serializeKey("key")), eq(range), eq(limit));
110+
}
72111
}

0 commit comments

Comments
 (0)