diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 085b44c175..fdc99cc981 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -48,6 +48,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; +import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; @@ -614,6 +615,17 @@ public CompletableFuture zcard(@NonNull String key) { return commandManager.submitNewCommand(Zcard, new String[] {key}, this::handleLongResponse); } + @Override + public CompletableFuture> zpopmin(@NonNull String key, long count) { + return commandManager.submitNewCommand( + ZPopMin, new String[] {key, Long.toString(count)}, this::handleMapResponse); + } + + @Override + public CompletableFuture> zpopmin(@NonNull String key) { + return commandManager.submitNewCommand(ZPopMin, new String[] {key}, this::handleMapResponse); + } + @Override public CompletableFuture> zpopmax(@NonNull String key, long count) { return commandManager.submitNewCommand( 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 33a58ce849..726c0ce1b9 100644 --- a/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java @@ -184,6 +184,44 @@ CompletableFuture zaddIncr( */ CompletableFuture zcard(String key); + /** + * Removes and returns up to count members with the lowest scores from the sorted set + * stored at the specified key. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param count Specifies the quantity of members to pop.
+ * If count is higher than the sorted set's cardinality, returns all members and + * their scores, ordered from lowest to highest. + * @return A map of the removed members and their scores, ordered from the one with the lowest + * score to the one with the highest.
+ * If key doesn't exist, it will be treated as an empty sorted set and the + * command returns an empty Map. + * @example + *
{@code
+     * Map payload = client.zpopmax("mySortedSet", 2).get();
+     * assert payload.equals(Map.of('member3', 7.5 , 'member2', 8.0)); // Indicates that 'member3' with a score of 7.5 and 'member2' with a score of 8.0 have been removed from the sorted set.
+     * }
+ */ + CompletableFuture> zpopmin(String key, long count); + + /** + * Removes and returns the member with the lowest score from the sorted set stored at the + * specified key. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @return A map containing the removed member and its corresponding score.
+ * If key doesn't exist, it will be treated as an empty sorted set and the + * command returns an empty Map. + * @example + *
{@code
+     * Map payload = client.zpopmin("mySortedSet").get();
+     * assert payload.equals(Map.of('member1', 5.0)); // Indicates that 'member1' with a score of 5.0 has been removed from the sorted set.
+     * }
+ */ + CompletableFuture> zpopmin(String key); + /** * Removes and returns up to count members with the highest scores from the sorted * set stored at the specified key. 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 7993877399..d50210fcb4 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -57,6 +57,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; +import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; @@ -1277,6 +1278,42 @@ public T zcard(@NonNull String key) { return getThis(); } + /** + * Removes and returns up to count members with the lowest scores from the sorted set + * stored at the specified key. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param count Specifies the quantity of members to pop.
+ * If count is higher than the sorted set's cardinality, returns all members and + * their scores, ordered from lowest to highest. + * @return Command Response - A map of the removed members and their scores, ordered from the one + * with the lowest score to the one with the highest.
+ * If key doesn't exist, it will be treated as an empty sorted set and the + * command returns an empty Map. + */ + public T zpopmin(@NonNull String key, long count) { + ArgsArray commandArgs = buildArgs(new String[] {key, Long.toString(count)}); + protobufTransaction.addCommands(buildCommand(ZPopMin, commandArgs)); + return getThis(); + } + + /** + * Removes and returns the member with the lowest score from the sorted set stored at the + * specified key. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @return Command Response - A map containing the removed member and its corresponding score.
+ * If key doesn't exist, it will be treated as an empty sorted set and the + * command returns an empty Map. + */ + public T zpopmin(@NonNull String key) { + ArgsArray commandArgs = buildArgs(new String[] {key}); + protobufTransaction.addCommands(buildCommand(ZPopMin, commandArgs)); + return getThis(); + } + /** * Removes and returns up to count members with the highest scores from the sorted * set stored at the specified key. diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 6bb6bbc75e..a17615d1ac 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -69,6 +69,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; +import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; @@ -1790,6 +1791,55 @@ public void zcard_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void zpopmin_returns_success() { + // setup + String key = "testKey"; + String[] arguments = new String[] {key}; + Map value = Map.of("member1", 2.5); + + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.>submitNewCommand(eq(ZPopMin), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture> response = service.zpopmin(key); + Map payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void zpopmin_with_count_returns_success() { + // setup + String key = "testKey"; + long count = 2L; + String[] arguments = new String[] {key, Long.toString(count)}; + Map value = Map.of("member1", 2.0, "member2", 3.0); + + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.>submitNewCommand(eq(ZPopMin), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture> response = service.zpopmin(key, count); + Map payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void zpopmax_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 65df81f957..99c5e065d8 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -55,6 +55,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; +import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; import static redis_request.RedisRequestOuterClass.RequestType.Zadd; import static redis_request.RedisRequestOuterClass.RequestType.Zcard; @@ -389,6 +390,12 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.zcard("key"); results.add(Pair.of(Zcard, ArgsArray.newBuilder().addArgs("key").build())); + transaction.zpopmin("key"); + results.add(Pair.of(ZPopMin, ArgsArray.newBuilder().addArgs("key").build())); + + transaction.zpopmin("key", 2); + results.add(Pair.of(ZPopMin, ArgsArray.newBuilder().addArgs("key").addArgs("2").build())); + transaction.zpopmax("key"); results.add(Pair.of(ZPopMax, ArgsArray.newBuilder().addArgs("key").build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index d648080f1f..a7ddbe0dc9 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -1113,6 +1113,25 @@ public void zcard(BaseClient client) { assertTrue(executionException.getCause() instanceof RequestException); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void zpopmin(BaseClient client) { + String key = UUID.randomUUID().toString(); + Map membersScores = Map.of("a", 1.0, "b", 2.0, "c", 3.0); + assertEquals(3, client.zadd(key, membersScores).get()); + assertEquals(Map.of("a", 1.0), client.zpopmin(key).get()); + assertEquals(Map.of("b", 2.0, "c", 3.0), client.zpopmin(key, 3).get()); + assertTrue(client.zpopmin(key).get().isEmpty()); + assertTrue(client.zpopmin("non_existing_key").get().isEmpty()); + + // Key exists, but it is not a set + assertEquals(OK, client.set(key, "value").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.zpopmin(key).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + @SneakyThrows @ParameterizedTest @MethodSource("getClients") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index ef84438630..5af67f20b5 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -89,6 +89,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.zrem(key8, new String[] {"one"}); baseTransaction.zcard(key8); baseTransaction.zscore(key8, "two"); + baseTransaction.zpopmin(key8); baseTransaction.zpopmax(key8); baseTransaction.configSet(Map.of("timeout", "1000")); @@ -150,6 +151,7 @@ public static Object[] transactionTestResult() { 1L, 2L, 2.0, // zscore(key8, "two") + Map.of("two", 2.0), // zpopmin(key8) Map.of("three", 3.0), // zpopmax(key8) OK, Map.of("timeout", "1000"),