diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 6094fe30bc..537ba0e662 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -10,6 +10,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Exists; import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; +import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; @@ -174,6 +175,10 @@ protected String handleStringOrNullResponse(Response response) throws RedisExcep return handleRedisResponse(String.class, true, response); } + protected Boolean handleBooleanResponse(Response response) throws RedisException { + return handleRedisResponse(Boolean.class, false, response); + } + protected Long handleLongResponse(Response response) throws RedisException { return handleRedisResponse(Long.class, false, response); } @@ -305,6 +310,12 @@ public CompletableFuture hmget(@NonNull String key, @NonNull String[] HashMGet, arguments, response -> castArray(handleArrayResponse(response), String.class)); } + @Override + public CompletableFuture hexists(@NonNull String key, @NonNull String field) { + return commandManager.submitNewCommand( + HashExists, new String[] {key, field}, this::handleBooleanResponse); + } + @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 dd9b0085c9..f430e85b28 100644 --- a/java/client/src/main/java/glide/api/commands/HashCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashCommands.java @@ -64,4 +64,22 @@ public interface HashCommands { * */ CompletableFuture hmget(String key, String[] fields); + + /** + * Returns if field is an existing field in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param field The field to check in the hash stored at key. + * @return True if the hash contains the specified field. If the hash does not + * contain the field, or if the key does not exist, it returns False. + * @example + *
+     * Boolean exists = client.hexists("my_hash", "field1").get()
+     * assert exists
+     * Boolean exists = client.hexists("my_hash", "non_existent_field").get()
+     * assert !exists
+     * 
+ */ + CompletableFuture hexists(String key, String field); } 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 e7151ea3ac..6e5c79510f 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -9,6 +9,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Exists; import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; +import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; @@ -382,6 +383,23 @@ public T hmget(@NonNull String key, @NonNull String[] fields) { return getThis(); } + /** + * Returns if field is an existing field in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param field The field to check in the hash stored at key. + * @return Command Response - True if the hash contains the specified field. If the + * hash does not contain the field, or if the key does not exist, it returns False + * . + */ + public T hexists(@NonNull String key, @NonNull String field) { + ArgsArray commandArgs = buildArgs(key, field); + + protobufTransaction.addCommands(buildCommand(HashExists, 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 8a1086dc39..55947bb0a4 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -19,6 +19,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Exists; import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; +import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; @@ -594,6 +595,31 @@ public void hmget_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void hexists_success() { + // setup + String key = "testKey"; + String field = "testField"; + String[] args = new String[] {key, field}; + Boolean value = true; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(HashExists), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.hexists(key, field); + Boolean 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 58d7fc7a96..d4172c37d1 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -9,6 +9,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Exists; import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; +import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; @@ -122,6 +123,10 @@ 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.hexists("key", "field"); + results.add( + Pair.of(HashExists, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + transaction.sadd("key", new String[] {"value"}); results.add(Pair.of(SAdd, ArgsArray.newBuilder().addArgs("key").addArgs("value").build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index bc8eceb8cd..e688b78696 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -10,6 +10,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -444,6 +445,21 @@ public void hmget_multiple_existing_fields_non_existing_field_non_existing_key( client.hmget("non_existing_key", new String[] {field1, field2}).get()); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void hexists_existing_field_non_existing_field_non_existing_key(BaseClient client) { + String key = UUID.randomUUID().toString(); + String field1 = UUID.randomUUID().toString(); + String field2 = UUID.randomUUID().toString(); + Map fieldValueMap = Map.of(field1, "value1", field2, "value1"); + + assertEquals(2, client.hset(key, fieldValueMap).get()); + assertTrue(client.hexists(key, field1).get()); + assertFalse(client.hexists(key, "non_existing_field").get()); + assertFalse(client.hexists("non_existing_key", 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 e194c9b364..fc0ac386d1 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.hexists(key4, field2); baseTransaction.hmget(key4, new String[] {field1, "non_existing_field", field2}); baseTransaction.hdel(key4, new String[] {field1}); @@ -73,6 +74,7 @@ public static Object[] transactionTestResult() { 0.5, 2L, value1, + true, new String[] {value1, null, value2}, 1L, 2L,