diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index d5c6ff8f41..b092eb363e 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -152,6 +152,7 @@ enum RequestType { ZLexCount = 109; Append = 110; SInterStore = 114; + ZRangeStore = 115; } message Command { diff --git a/glide-core/src/request_type.rs b/glide-core/src/request_type.rs index f0ea78a145..c6f24547de 100644 --- a/glide-core/src/request_type.rs +++ b/glide-core/src/request_type.rs @@ -120,6 +120,7 @@ pub enum RequestType { ZLexCount = 109, Append = 110, SInterStore = 114, + ZRangeStore = 115, } fn get_two_word_command(first: &str, second: &str) -> Cmd { @@ -243,6 +244,7 @@ impl From<::protobuf::EnumOrUnknown> for RequestType { ProtobufRequestType::ZLexCount => RequestType::ZLexCount, ProtobufRequestType::Append => RequestType::Append, ProtobufRequestType::SInterStore => RequestType::SInterStore, + ProtobufRequestType::ZRangeStore => RequestType::ZRangeStore, } } } @@ -362,6 +364,7 @@ impl RequestType { RequestType::ZLexCount => Some(cmd("ZLEXCOUNT")), RequestType::Append => Some(cmd("APPEND")), RequestType::SInterStore => Some(cmd("SINTERSTORE")), + RequestType::ZRangeStore => Some(cmd("ZRANGESTORE")), } } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 9e47cdb5e6..8fba1b4bb1 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -69,6 +69,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.ZMScore; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; +import static redis_request.RedisRequestOuterClass.RequestType.ZRangeStore; import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByLex; import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByRank; import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByScore; @@ -810,6 +811,24 @@ public CompletableFuture zlexcount( ZLexCount, new String[] {key, minLex.toArgs(), maxLex.toArgs()}, this::handleLongResponse); } + @Override + public CompletableFuture zrangestore( + @NonNull String destination, + @NonNull String source, + @NonNull RangeQuery rangeQuery, + boolean reverse) { + String[] arguments = + RangeOptions.createZRangeStoreArgs(destination, source, rangeQuery, reverse); + + return commandManager.submitNewCommand(ZRangeStore, arguments, this::handleLongResponse); + } + + @Override + public CompletableFuture zrangestore( + @NonNull String destination, @NonNull String source, @NonNull RangeQuery rangeQuery) { + return this.zrangestore(destination, source, rangeQuery, false); + } + @Override public CompletableFuture xadd(@NonNull String key, @NonNull Map values) { return xadd(key, values, StreamAddOptions.builder().build()); @@ -879,7 +898,7 @@ public CompletableFuture lpushx(@NonNull String key, @NonNull String[] ele @Override public CompletableFuture zrange( @NonNull String key, @NonNull RangeQuery rangeQuery, boolean reverse) { - String[] arguments = RangeOptions.createZrangeArgs(key, rangeQuery, reverse, false); + String[] arguments = RangeOptions.createZRangeArgs(key, rangeQuery, reverse, false); return commandManager.submitNewCommand( Zrange, @@ -895,7 +914,7 @@ public CompletableFuture zrange(@NonNull String key, @NonNull RangeQue @Override public CompletableFuture> zrangeWithScores( @NonNull String key, @NonNull ScoredRangeQuery rangeQuery, boolean reverse) { - String[] arguments = RangeOptions.createZrangeArgs(key, rangeQuery, reverse, true); + String[] arguments = RangeOptions.createZRangeArgs(key, rangeQuery, reverse, true); return commandManager.submitNewCommand(Zrange, arguments, this::handleMapResponse); } diff --git a/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java b/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java index 9bf77b856b..dcd2fce4d5 100644 --- a/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java @@ -419,6 +419,67 @@ CompletableFuture> zrangeWithScores( */ CompletableFuture> zrangeWithScores(String key, ScoredRangeQuery rangeQuery); + /** + * Stores a specified range of elements from the sorted set at source, into a new + * sorted set at destination. If destination doesn't exist, a new sorted + * set is created; if it exists, it's overwritten.
+ * + * @see redis.io for more details. + * @param destination The key for the destination sorted set. + * @param source The key of the source sorted set. + * @param rangeQuery The range query object representing the type of range query to perform.
+ *
    + *
  • For range queries by index (rank), use {@link RangeByIndex}. + *
  • For range queries by lexicographical order, use {@link RangeByLex}. + *
  • For range queries by score, use {@link RangeByScore}. + *
+ * + * @param reverse If true, reverses the sorted set, with index 0 as the + * element with the highest score. + * @return The number of elements in the resulting sorted set. + * @example + *
{@code
+     * RangeByIndex query1 = new RangeByIndex(0, -1); // Query for all members.
+     * Long payload1 = client.zrangestore("destinationKey", "mySortedSet", query1, true).get();
+     * assert payload1 == 7L;
+     *
+     * RangeByScore query2 = new RangeByScore(InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3)); // Query for members with scores within the range of negative infinity to 3.
+     * Long payload2 = client.zrangestore("destinationKey", "mySortedSet", query2, false).get();
+     * assert payload2 == 5L;
+     * }
+ */ + CompletableFuture zrangestore( + String destination, String source, RangeQuery rangeQuery, boolean reverse); + + /** + * Stores a specified range of elements from the sorted set at source, into a new + * sorted set at destination. If destination doesn't exist, a new sorted + * set is created; if it exists, it's overwritten.
+ * + * @see redis.io for more details. + * @param destination The key for the destination sorted set. + * @param source The key of the source sorted set. + * @param rangeQuery The range query object representing the type of range query to perform.
+ *
    + *
  • For range queries by index (rank), use {@link RangeByIndex}. + *
  • For range queries by lexicographical order, use {@link RangeByLex}. + *
  • For range queries by score, use {@link RangeByScore}. + *
+ * + * @return The number of elements in the resulting sorted set. + * @example + *
{@code
+     * RangeByIndex query1 = new RangeByIndex(0, -1); // Query for all members.
+     * Long payload1 = client.zrangestore("destinationKey", "mySortedSet", query1).get();
+     * assert payload1 == 7L;
+     *
+     * RangeByScore query2 = new RangeByScore(InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3)); // Query for members with scores within the range of negative infinity to 3.
+     * Long payload2 = client.zrangestore("destinationKey", "mySortedSet", query2).get();
+     * assert payload2 == 5L;
+     * }
+ */ + CompletableFuture zrangestore(String destination, String source, RangeQuery rangeQuery); + /** * Returns the rank of member in the sorted set stored at key, with * scores ordered from low to high.
diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index 42584dc6ef..8292c70038 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -3,7 +3,7 @@ import static glide.api.commands.SortedSetBaseCommands.WITH_SCORES_REDIS_API; import static glide.api.commands.SortedSetBaseCommands.WITH_SCORE_REDIS_API; -import static glide.api.models.commands.RangeOptions.createZrangeArgs; +import static glide.api.models.commands.RangeOptions.createZRangeArgs; import static glide.utils.ArrayTransformUtils.concatenateArrays; import static glide.utils.ArrayTransformUtils.convertMapToKeyValueStringArray; import static glide.utils.ArrayTransformUtils.convertMapToValueKeyStringArray; @@ -81,6 +81,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.ZMScore; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; +import static redis_request.RedisRequestOuterClass.RequestType.ZRangeStore; import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByLex; import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByRank; import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByScore; @@ -96,6 +97,7 @@ import glide.api.models.commands.InfoOptions; import glide.api.models.commands.InfoOptions.Section; import glide.api.models.commands.LInsertOptions.InsertPosition; +import glide.api.models.commands.RangeOptions; import glide.api.models.commands.RangeOptions.InfLexBound; import glide.api.models.commands.RangeOptions.InfScoreBound; import glide.api.models.commands.RangeOptions.LexBoundary; @@ -1642,6 +1644,58 @@ public T zremrangebyrank(@NonNull String key, long start, long end) { return getThis(); } + /** + * Stores a specified range of elements from the sorted set at source, into a new + * sorted set at destination. If destination doesn't exist, a new sorted + * set is created; if it exists, it's overwritten.
+ * + * @see redis.io for more details. + * @param destination The key for the destination sorted set. + * @param source The key of the source sorted set. + * @param rangeQuery The range query object representing the type of range query to perform.
+ *
    + *
  • For range queries by index (rank), use {@link RangeByIndex}. + *
  • For range queries by lexicographical order, use {@link RangeByLex}. + *
  • For range queries by score, use {@link RangeByScore}. + *
+ * + * @param reverse If true, reverses the sorted set, with index 0 as the + * element with the highest score. + * @return Command Response - The number of elements in the resulting sorted set. + */ + public T zrangestore( + @NonNull String destination, + @NonNull String source, + @NonNull RangeQuery rangeQuery, + boolean reverse) { + ArgsArray commandArgs = + buildArgs(RangeOptions.createZRangeStoreArgs(destination, source, rangeQuery, reverse)); + protobufTransaction.addCommands(buildCommand(ZRangeStore, commandArgs)); + return getThis(); + } + + /** + * Stores a specified range of elements from the sorted set at source, into a new + * sorted set at destination. If destination doesn't exist, a new sorted + * set is created; if it exists, it's overwritten.
+ * + * @see redis.io for more details. + * @param destination The key for the destination sorted set. + * @param source The key of the source sorted set. + * @param rangeQuery The range query object representing the type of range query to perform.
+ *
    + *
  • For range queries by index (rank), use {@link RangeByIndex}. + *
  • For range queries by lexicographical order, use {@link RangeByLex}. + *
  • For range queries by score, use {@link RangeByScore}. + *
+ * + * @return Command Response - The number of elements in the resulting sorted set. + */ + public T zrangestore( + @NonNull String destination, @NonNull String source, @NonNull RangeQuery rangeQuery) { + return getThis().zrangestore(destination, source, rangeQuery, false); + } + /** * Removes all elements in the sorted set stored at key with a lexicographical order * between minLex and maxLex. @@ -1927,7 +1981,7 @@ public T blpop(@NonNull String[] keys, double timeout) { * array. */ public T zrange(@NonNull String key, @NonNull RangeQuery rangeQuery, boolean reverse) { - ArgsArray commandArgs = buildArgs(createZrangeArgs(key, rangeQuery, reverse, false)); + ArgsArray commandArgs = buildArgs(createZRangeArgs(key, rangeQuery, reverse, false)); protobufTransaction.addCommands(buildCommand(Zrange, commandArgs)); return getThis(); } @@ -1975,7 +2029,7 @@ public T zrange(@NonNull String key, @NonNull RangeQuery rangeQuery) { */ public T zrangeWithScores( @NonNull String key, @NonNull ScoredRangeQuery rangeQuery, boolean reverse) { - ArgsArray commandArgs = buildArgs(createZrangeArgs(key, rangeQuery, reverse, true)); + ArgsArray commandArgs = buildArgs(createZRangeArgs(key, rangeQuery, reverse, true)); protobufTransaction.addCommands(buildCommand(Zrange, commandArgs)); return getThis(); } diff --git a/java/client/src/main/java/glide/api/models/commands/RangeOptions.java b/java/client/src/main/java/glide/api/models/commands/RangeOptions.java index afe6478e82..179145eaaf 100644 --- a/java/client/src/main/java/glide/api/models/commands/RangeOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/RangeOptions.java @@ -13,14 +13,15 @@ * Arguments for {@link SortedSetBaseCommands#zcount}, {@link * SortedSetBaseCommands#zremrangebyrank}, {@link SortedSetBaseCommands#zremrangebylex(String, * LexRange, LexRange)}, {@link SortedSetBaseCommands#zremrangebyscore}, {@link - * SortedSetBaseCommands#zrange}, {@link SortedSetBaseCommands#zrangeWithScores}, and {@link - * SortedSetBaseCommands#zlexcount} + * SortedSetBaseCommands#zrange}, {@link SortedSetBaseCommands#zrangestore}, {@link + * SortedSetBaseCommands#zrangeWithScores}, and {@link SortedSetBaseCommands#zlexcount} * * @see redis.io * @see redis.io * @see redis.io * @see redis.io * @see redis.io + * @see redis.io * @see redis.io */ public class RangeOptions { @@ -298,9 +299,25 @@ public RangeByScore( } } - public static String[] createZrangeArgs( + public static String[] createZRangeArgs( String key, RangeQuery rangeQuery, boolean reverse, boolean withScores) { - String[] arguments = new String[] {key, rangeQuery.getStart(), rangeQuery.getEnd()}; + String[] arguments = + concatenateArrays(new String[] {key}, createZRangeBaseArgs(rangeQuery, reverse)); + if (withScores) { + arguments = concatenateArrays(arguments, new String[] {WITH_SCORES_REDIS_API}); + } + + return arguments; + } + + public static String[] createZRangeStoreArgs( + String destination, String source, RangeQuery rangeQuery, boolean reverse) { + return concatenateArrays( + new String[] {destination, source}, createZRangeBaseArgs(rangeQuery, reverse)); + } + + private static String[] createZRangeBaseArgs(RangeQuery rangeQuery, boolean reverse) { + String[] arguments = new String[] {rangeQuery.getStart(), rangeQuery.getEnd()}; if (rangeQuery instanceof RangeByScore) { arguments = concatenateArrays(arguments, new String[] {"BYSCORE"}); @@ -323,10 +340,6 @@ public static String[] createZrangeArgs( }); } - if (withScores) { - arguments = concatenateArrays(arguments, new String[] {WITH_SCORES_REDIS_API}); - } - return arguments; } } diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 862f76f21b..ab0eba3bee 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -102,6 +102,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.ZMScore; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; +import static redis_request.RedisRequestOuterClass.RequestType.ZRangeStore; import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByLex; import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByRank; import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByScore; @@ -2584,6 +2585,93 @@ public void zlexcount_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void zrangestore_by_lex_returns_success() { + // setup + String source = "testSourceKey"; + String destination = "testDestinationKey"; + RangeByLex rangeByLex = + new RangeByLex(InfLexBound.NEGATIVE_INFINITY, new LexBoundary("c", false)); + String[] arguments = + new String[] {source, destination, rangeByLex.getStart(), rangeByLex.getEnd(), "BYLEX"}; + Long value = 2L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ZRangeStore), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.zrangestore(source, destination, rangeByLex); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void zrangestore_by_index_returns_success() { + // setup + String source = "testSourceKey"; + String destination = "testDestinationKey"; + RangeByIndex rangeByIndex = new RangeByIndex(0, 1); + String[] arguments = + new String[] {source, destination, rangeByIndex.getStart(), rangeByIndex.getEnd()}; + Long value = 2L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ZRangeStore), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.zrangestore(source, destination, rangeByIndex); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void zrangestore_by_score_with_reverse_returns_success() { + // setup + String source = "testSourceKey"; + String destination = "testDestinationKey"; + RangeByScore rangeByScore = + new RangeByScore(new ScoreBoundary(3, false), InfScoreBound.NEGATIVE_INFINITY); + boolean reversed = true; + String[] arguments = + new String[] { + source, destination, rangeByScore.getStart(), rangeByScore.getEnd(), "BYSCORE", "REV" + }; + Long value = 2L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ZRangeStore), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = + service.zrangestore(source, destination, rangeByScore, reversed); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void xadd_returns_success() { diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index f104d6ea10..113506b5ac 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -86,6 +86,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.ZMScore; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; +import static redis_request.RedisRequestOuterClass.RequestType.ZRangeStore; import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByLex; import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByRank; import static redis_request.RedisRequestOuterClass.RequestType.ZRemRangeByScore; @@ -103,6 +104,7 @@ import glide.api.models.commands.RangeOptions.InfScoreBound; import glide.api.models.commands.RangeOptions.LexBoundary; import glide.api.models.commands.RangeOptions.Limit; +import glide.api.models.commands.RangeOptions.RangeByIndex; import glide.api.models.commands.RangeOptions.RangeByScore; import glide.api.models.commands.RangeOptions.ScoreBoundary; import glide.api.models.commands.SetOptions; @@ -401,6 +403,21 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.zlexcount("key", new LexBoundary("c", false), InfLexBound.POSITIVE_INFINITY); results.add(Pair.of(ZLexCount, buildArgs("key", "(c", "+"))); + transaction.zrangestore( + "destination", + "source", + new RangeByScore( + InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), + true); + results.add( + Pair.of( + ZRangeStore, + buildArgs( + "destination", "source", "-inf", "(3.0", "BYSCORE", "REV", "LIMIT", "1", "2"))); + + transaction.zrangestore("destination", "source", new RangeByIndex(2, 3)); + results.add(Pair.of(ZRangeStore, buildArgs("destination", "source", "2", "3"))); + transaction.xadd("key", Map.of("field1", "foo1")); results.add(Pair.of(XAdd, buildArgs("key", "*", "field1", "foo1"))); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 99acee473a..0ba74697c9 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -1703,6 +1703,175 @@ public void zlexcount(BaseClient client) { assertTrue(executionException.getCause() instanceof RequestException); } + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void zrangestore_by_index(BaseClient client) { + String key = "{testKey}:" + UUID.randomUUID(); + String destination = "{testKey}:" + UUID.randomUUID(); + String source = "{testKey}:" + UUID.randomUUID(); + Map membersScores = Map.of("one", 1.0, "two", 2.0, "three", 3.0); + assertEquals(3, client.zadd(source, membersScores).get()); + + // Full range. + assertEquals(3, client.zrangestore(destination, source, new RangeByIndex(0, -1)).get()); + assertEquals( + Map.of("one", 1.0, "two", 2.0, "three", 3.0), + client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Range from rank 0 to 1. In descending order of scores. + assertEquals(2, client.zrangestore(destination, source, new RangeByIndex(0, 1), true).get()); + assertEquals( + Map.of("three", 3.0, "two", 2.0), + client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Incorrect range as start > stop. + assertEquals(0, client.zrangestore(destination, source, new RangeByIndex(3, 1)).get()); + assertEquals(Map.of(), client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Non-existing source. + assertEquals(0, client.zrangestore(destination, key, new RangeByIndex(0, -1)).get()); + assertEquals(Map.of(), client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Key exists, but it is not a set + assertEquals(OK, client.set(key, "value").get()); + ExecutionException executionException = + assertThrows( + ExecutionException.class, + () -> client.zrangestore(destination, key, new RangeByIndex(3, 1)).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void zrangestore_by_score(BaseClient client) { + String key = "{testKey}:" + UUID.randomUUID(); + String destination = "{testKey}:" + UUID.randomUUID(); + String source = "{testKey}:" + UUID.randomUUID(); + Map membersScores = Map.of("one", 1.0, "two", 2.0, "three", 3.0); + assertEquals(3, client.zadd(source, membersScores).get()); + + // Range from negative infinity to 3 (exclusive). + RangeByScore query = + new RangeByScore(InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false)); + assertEquals(2, client.zrangestore(destination, source, query).get()); + assertEquals( + Map.of("one", 1.0, "two", 2.0), + client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Range from 1 (inclusive) to positive infinity. + query = new RangeByScore(new ScoreBoundary(1), InfScoreBound.POSITIVE_INFINITY); + assertEquals(3, client.zrangestore(destination, source, query).get()); + assertEquals( + Map.of("one", 1.0, "two", 2.0, "three", 3.0), + client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Range from negative to positive infinity. Limited to ranks 1 to 2. + query = + new RangeByScore( + InfScoreBound.NEGATIVE_INFINITY, InfScoreBound.POSITIVE_INFINITY, new Limit(1, 2)); + assertEquals(2, client.zrangestore(destination, source, query).get()); + assertEquals( + Map.of("two", 2.0, "three", 3.0), + client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Range from positive to negative infinity with rev set to true. Limited to ranks 1 to 2. + query = + new RangeByScore( + InfScoreBound.POSITIVE_INFINITY, InfScoreBound.NEGATIVE_INFINITY, new Limit(1, 2)); + assertEquals(2, client.zrangestore(destination, source, query, true).get()); + assertEquals( + Map.of("two", 2.0, "one", 1.0), + client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Incorrect range as start > stop. + query = new RangeByScore(new ScoreBoundary(3, false), InfScoreBound.NEGATIVE_INFINITY); + assertEquals(0, client.zrangestore(destination, source, query).get()); + assertEquals(Map.of(), client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Non-existent source. + query = new RangeByScore(InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false)); + assertEquals(0, client.zrangestore(destination, key, query).get()); + assertEquals(Map.of(), client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Key exists, but it is not a set + assertEquals(OK, client.set(key, "value").get()); + ExecutionException executionException = + assertThrows( + ExecutionException.class, + () -> + client + .zrangestore( + destination, + key, + new RangeByScore(new ScoreBoundary(0), new ScoreBoundary(3))) + .get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void zrangestore_by_lex(BaseClient client) { + String key = "{testKey}:" + UUID.randomUUID(); + String destination = "{testKey}:" + UUID.randomUUID(); + String source = "{testKey}:" + UUID.randomUUID(); + Map membersScores = Map.of("a", 1.0, "b", 2.0, "c", 3.0); + assertEquals(3, client.zadd(source, membersScores).get()); + + // Range from negative infinity to "c" (exclusive). + RangeByLex query = new RangeByLex(InfLexBound.NEGATIVE_INFINITY, new LexBoundary("c", false)); + assertEquals(2, client.zrangestore(destination, source, query).get()); + assertEquals( + Map.of("a", 1.0, "b", 2.0), + client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Range from "a" (inclusive) to positive infinity. + query = new RangeByLex(new LexBoundary("a"), InfLexBound.POSITIVE_INFINITY); + assertEquals(3, client.zrangestore(destination, source, query).get()); + assertEquals( + Map.of("a", 1.0, "b", 2.0, "c", 3.0), + client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Range from negative to positive infinity. Limited to ranks 1 to 2. + query = + new RangeByLex( + InfLexBound.NEGATIVE_INFINITY, InfLexBound.POSITIVE_INFINITY, new Limit(1, 2)); + assertEquals(2, client.zrangestore(destination, source, query).get()); + assertEquals( + Map.of("b", 2.0, "c", 3.0), + client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Range from positive to negative infinity with rev set to true. Limited to ranks 1 to 2. + query = + new RangeByLex( + InfLexBound.POSITIVE_INFINITY, InfLexBound.NEGATIVE_INFINITY, new Limit(1, 2)); + assertEquals(2, client.zrangestore(destination, source, query, true).get()); + assertEquals( + Map.of("b", 2.0, "a", 1.0), + client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Non-existent source. + query = new RangeByLex(InfLexBound.NEGATIVE_INFINITY, new LexBoundary("c", false)); + assertEquals(0, client.zrangestore(destination, key, query).get()); + assertEquals(Map.of(), client.zrangeWithScores(destination, new RangeByIndex(0, -1)).get()); + + // Key exists, but it is not a set + assertEquals(OK, client.set(key, "value").get()); + ExecutionException executionException = + assertThrows( + ExecutionException.class, + () -> + client + .zrangestore( + destination, + key, + new RangeByLex(new LexBoundary("a"), new LexBoundary("c"))) + .get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + @SneakyThrows @ParameterizedTest(autoCloseArguments = false) @MethodSource("getClients") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 149a3455df..5bdee80f8b 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -115,6 +115,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.zmscore(key8, new String[] {"two", "three"}); baseTransaction.zrange(key8, new RangeByIndex(0, 1)); baseTransaction.zrangeWithScores(key8, new RangeByIndex(0, 1)); + baseTransaction.zrangestore(key8, key8, new RangeByIndex(0, -1)); baseTransaction.zscore(key8, "two"); baseTransaction.zcount(key8, new ScoreBoundary(2, true), InfScoreBound.POSITIVE_INFINITY); baseTransaction.zlexcount(key8, new LexBoundary("a", true), InfLexBound.POSITIVE_INFINITY); @@ -219,6 +220,7 @@ public static Object[] transactionTestResult() { new Double[] {2.0, 3.0}, // zmscore(key8, new String[] {"two", "three"}) new String[] {"two", "three"}, // zrange Map.of("two", 2.0, "three", 3.0), // zrangeWithScores + 2L, // zrangestore(key8, key8, new RangeByIndex(0, -1)) 2.0, // zscore(key8, "two") 2L, // zcount(key8, new ScoreBoundary(2, true), InfScoreBound.POSITIVE_INFINITY) 2L, // zlexcount(key8, new LexBoundary("a", true), InfLexBound.POSITIVE_INFINITY)