diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index f0a30e30d2..6094fe30bc 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -11,6 +11,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; +import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; @@ -297,6 +298,13 @@ public CompletableFuture hdel(@NonNull String key, @NonNull String[] field return commandManager.submitNewCommand(HashDel, args, this::handleLongResponse); } + @Override + public CompletableFuture hmget(@NonNull String key, @NonNull String[] fields) { + String[] arguments = ArrayUtils.addFirst(fields, key); + return commandManager.submitNewCommand( + HashMGet, arguments, response -> castArray(handleArrayResponse(response), String.class)); + } + @Override public CompletableFuture sadd(String key, String[] members) { String[] arguments = ArrayUtils.addFirst(members, key); diff --git a/java/client/src/main/java/glide/api/commands/HashCommands.java b/java/client/src/main/java/glide/api/commands/HashCommands.java index 22837b5531..dd9b0085c9 100644 --- a/java/client/src/main/java/glide/api/commands/HashCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashCommands.java @@ -45,4 +45,23 @@ public interface HashCommands { * If key does not exist, it is treated as an empty hash and it returns 0.
*/ CompletableFuture hdel(String key, String[] fields); + + /** + * Returns the values associated with the specified fields in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param fields The fields in the hash stored at key to retrieve from the database. + * @return An array of values associated with the given fields, in the same order as they are + * requested.
+ * For every field that does not exist in the hash, a null value is returned.
+ * If key does not exist, it is treated as an empty hash, and it returns an array + * of null values.
+ * @example + *
+     * String[] values = client.hmget("my_hash", new String[] {"field1", "field2"}).get()
+     * assert values == new String[] {"value1", "value2"}
+     * 
+ */ + CompletableFuture hmget(String key, String[] fields); } 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 734456e25b..e7151ea3ac 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -10,6 +10,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; +import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; @@ -362,6 +363,25 @@ public T hdel(@NonNull String key, @NonNull String[] fields) { return getThis(); } + /** + * Returns the values associated with the specified fields in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param fields The fields in the hash stored at key to retrieve from the database. + * @return Command Response - An array of values associated with the given fields, in the same + * order as they are requested.
+ * For every field that does not exist in the hash, a null value is returned.
+ * If key does not exist, it is treated as an empty hash, and it returns an array + * of null values.
+ */ + public T hmget(@NonNull String key, @NonNull String[] fields) { + ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(fields, key)); + + protobufTransaction.addCommands(buildCommand(HashMGet, commandArgs)); + return getThis(); + } + /** * Add specified members to the set stored at key. Specified members that are already * a member of this set are ignored. diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 64e2a75f78..8a1086dc39 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -20,6 +20,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; +import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; @@ -568,6 +569,31 @@ public void hdel_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void hmget_success() { + // setup + String key = "testKey"; + String[] fields = {"testField1", "testField2"}; + String[] args = {"testKey", "testField1", "testField2"}; + String[] value = {"testValue1", "testValue2"}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(HashMGet), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.hmget(key, fields); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void sadd_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 c8e193ce59..58d7fc7a96 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -10,6 +10,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; +import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; @@ -91,6 +92,9 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.mget(new String[] {"key"}); results.add(Pair.of(MGet, ArgsArray.newBuilder().addArgs("key").build())); + transaction.hmget("key", new String[] {"field"}); + results.add(Pair.of(HashMGet, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + transaction.incr("key"); results.add(Pair.of(Incr, 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 1ac025ebe3..bc8eceb8cd 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -424,6 +424,26 @@ 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 hmget_multiple_existing_fields_non_existing_field_non_existing_key( + BaseClient client) { + String key = UUID.randomUUID().toString(); + String field1 = UUID.randomUUID().toString(); + String field2 = UUID.randomUUID().toString(); + String value = UUID.randomUUID().toString(); + Map fieldValueMap = Map.of(field1, value, field2, value); + + assertEquals(2, client.hset(key, fieldValueMap).get()); + assertArrayEquals( + new String[] {value, null, value}, + client.hmget(key, new String[] {field1, "non_existing_field", field2}).get()); + assertArrayEquals( + new String[] {null, null}, + client.hmget("non_existing_key", new String[] {field1, field2}).get()); + } + @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 d8e1dd8d1a..e194c9b364 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -44,6 +44,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.hset(key4, Map.of(field1, value1, field2, value2)); baseTransaction.hget(key4, field1); + baseTransaction.hmget(key4, new String[] {field1, "non_existing_field", field2}); baseTransaction.hdel(key4, new String[] {field1}); baseTransaction.sadd(key5, new String[] {"baz", "foo"}); @@ -72,6 +73,7 @@ public static Object[] transactionTestResult() { 0.5, 2L, value1, + new String[] {value1, null, value2}, 1L, 2L, 1L,