diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 5587bf150b..f569c6ad3c 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -21,6 +21,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashIncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; +import static redis_request.RedisRequestOuterClass.RequestType.Hvals; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; @@ -347,6 +348,14 @@ public CompletableFuture hdel(@NonNull String key, @NonNull String[] field return commandManager.submitNewCommand(HashDel, args, this::handleLongResponse); } + @Override + public CompletableFuture hvals(@NonNull String key) { + return commandManager.submitNewCommand( + Hvals, + new String[] {key}, + response -> castArray(handleArrayResponse(response), String.class)); + } + @Override public CompletableFuture hmget(@NonNull String key, @NonNull String[] fields) { String[] arguments = ArrayUtils.addFirst(fields, key); diff --git a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java index 1ffc19a5fe..f267d7902c 100644 --- a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java @@ -65,6 +65,21 @@ public interface HashBaseCommands { */ CompletableFuture hdel(String key, String[] fields); + /** + * Returns all values in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @return An array of values in the hash, or an empty array when the + * key does not exist. + * @example + *
{@code
+     * String[] values = client.hvals("myHash").get();
+     * assert values.equals(new String[] {"value1", "value2", "value3"}); // Returns all the values stored in the hash "myHash".
+     * }
+ */ + CompletableFuture hvals(String key); + /** * Returns the values associated with the specified fields in the hash stored at 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 1decc9f3b5..7e505f80f0 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -29,6 +29,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashIncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; +import static redis_request.RedisRequestOuterClass.RequestType.Hvals; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; @@ -438,6 +439,21 @@ public T hdel(@NonNull String key, @NonNull String[] fields) { return getThis(); } + /** + * Returns all values in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @return Command Response - An array of values in the hash, or an empty array + * when the key does not exist. + */ + public T hvals(@NonNull String key) { + ArgsArray commandArgs = buildArgs(key); + + protobufTransaction.addCommands(buildCommand(Hvals, commandArgs)); + return getThis(); + } + /** * Returns the values associated with the specified fields in the hash stored at key. * diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 8901c73c1a..8f5bf6f5e6 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -40,6 +40,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashIncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; +import static redis_request.RedisRequestOuterClass.RequestType.Hvals; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; @@ -1021,6 +1022,30 @@ public void hdel_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void hvals_success() { + // setup + String key = "testKey"; + String[] args = {key}; + String[] values = new String[] {"value1", "value2"}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(values); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Hvals), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.hvals(key); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(values, payload); + } + @SneakyThrows @Test public void hmget_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 9d4e36eb4c..bab5d5fa6f 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -27,6 +27,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashIncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; +import static redis_request.RedisRequestOuterClass.RequestType.Hvals; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; @@ -169,6 +170,9 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.hdel("key", new String[] {"field"}); results.add(Pair.of(HashDel, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + transaction.hvals("key"); + results.add(Pair.of(Hvals, ArgsArray.newBuilder().addArgs("key").build())); + transaction.hmget("key", new String[] {"field"}); results.add(Pair.of(HashMGet, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 49c32b28da..2005d4138b 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -37,6 +37,7 @@ import glide.api.models.configuration.RedisClusterClientConfiguration; import glide.api.models.exceptions.RequestException; import java.time.Instant; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -471,6 +472,32 @@ public void hdel_multiple_existing_fields_non_existing_field_non_existing_key(Ba assertEquals(0, client.hdel("non_existing_key", new String[] {field3}).get()); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void hvals(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String field1 = UUID.randomUUID().toString(); + String field2 = UUID.randomUUID().toString(); + Map fieldValueMap = Map.of(field1, "value1", field2, "value2"); + + assertEquals(2, client.hset(key1, fieldValueMap).get()); + + String[] hvalsPayload = client.hvals(key1).get(); + Arrays.sort(hvalsPayload); // ordering for values by hvals is not guaranteed + assertArrayEquals(new String[] {"value1", "value2"}, hvalsPayload); + + assertEquals(1, client.hdel(key1, new String[] {field1}).get()); + assertArrayEquals(new String[] {"value2"}, client.hvals(key1).get()); + assertArrayEquals(new String[] {}, client.hvals("nonExistingKey").get()); + + assertEquals(OK, client.set(key2, "value2").get()); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.hvals(key2).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 95cf3caa06..1ddd89e910 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -64,6 +64,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.hmget(key4, new String[] {field1, "non_existing_field", field2}); baseTransaction.hgetall(key4); baseTransaction.hdel(key4, new String[] {field1}); + baseTransaction.hvals(key4); baseTransaction.hincrBy(key4, field3, 5); baseTransaction.hincrByFloat(key4, field3, 5.5); @@ -134,6 +135,7 @@ public static Object[] transactionTestResult() { new String[] {value1, null, value2}, Map.of(field1, value1, field2, value2), 1L, + new String[] {value2}, // hvals(key4) 5L, 10.5, 5L,