diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index c256b64e70..e81303a644 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -188,6 +188,7 @@ enum RequestType { ZInter = 146; BitPos = 147; BitOp = 148; + HStrlen = 149; FunctionLoad = 150; LMPop = 155; } diff --git a/glide-core/src/request_type.rs b/glide-core/src/request_type.rs index 803eb42e4c..36156c202a 100644 --- a/glide-core/src/request_type.rs +++ b/glide-core/src/request_type.rs @@ -158,6 +158,7 @@ pub enum RequestType { ZInter = 146, BitPos = 147, BitOp = 148, + HStrlen = 149, FunctionLoad = 150, LMPop = 155, } @@ -323,6 +324,7 @@ impl From<::protobuf::EnumOrUnknown> for RequestType { ProtobufRequestType::FunctionLoad => RequestType::FunctionLoad, ProtobufRequestType::BitPos => RequestType::BitPos, ProtobufRequestType::BitOp => RequestType::BitOp, + ProtobufRequestType::HStrlen => RequestType::HStrlen, } } } @@ -482,6 +484,7 @@ impl RequestType { RequestType::FunctionLoad => Some(get_two_word_command("FUNCTION", "LOAD")), RequestType::BitPos => Some(cmd("BITPOS")), RequestType::BitOp => Some(cmd("BITOP")), + RequestType::HStrlen => Some(cmd("HSTRLEN")), } } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 9c6c5ae5fd..bfc97aa6d2 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -43,6 +43,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HRandField; import static redis_request.RedisRequestOuterClass.RequestType.HSet; import static redis_request.RedisRequestOuterClass.RequestType.HSetNX; +import static redis_request.RedisRequestOuterClass.RequestType.HStrlen; import static redis_request.RedisRequestOuterClass.RequestType.HVals; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; @@ -555,6 +556,12 @@ public CompletableFuture hkeys(@NonNull String key) { response -> castArray(handleArrayResponse(response), String.class)); } + @Override + public CompletableFuture hstrlen(@NonNull String key, @NonNull String field) { + return commandManager.submitNewCommand( + HStrlen, new String[] {key, field}, this::handleLongResponse); + } + @Override public CompletableFuture hrandfield(@NonNull String key) { return commandManager.submitNewCommand( 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 cae1c4fae8..51fe72dc03 100644 --- a/java/client/src/main/java/glide/api/commands/HashBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashBaseCommands.java @@ -225,7 +225,7 @@ public interface HashBaseCommands { /** * Returns all field names in the hash stored at key. * - * @see redis.io for details + * @see valkey.io for details. * @param key The key of the hash. * @return An array of field names in the hash, or an empty array when * the key does not exist. @@ -237,6 +237,23 @@ public interface HashBaseCommands { */ CompletableFuture hkeys(String key); + /** + * Returns the string length of the value associated with field in the hash stored at + * key. + * + * @see valkey.io for details. + * @param key The key of the hash. + * @param field The field in the hash. + * @return The string length or 0 if field or key does not + * exist. + * @example + *
{@code
+     * Long strlen = client.hstrlen("my_hash", "my_field").get();
+     * assert strlen >= 0L;
+     * }
+ */ + CompletableFuture hstrlen(String key, String field); + /** * Returns a random field name from the hash value 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 1aa32c8d08..86a70fc2f1 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.HRandField; import static redis_request.RedisRequestOuterClass.RequestType.HSet; import static redis_request.RedisRequestOuterClass.RequestType.HSetNX; +import static redis_request.RedisRequestOuterClass.RequestType.HStrlen; import static redis_request.RedisRequestOuterClass.RequestType.HVals; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; @@ -715,6 +716,21 @@ public T hkeys(@NonNull String key) { return getThis(); } + /** + * Returns the string length of the value associated with field in the hash stored at + * key. + * + * @see valkey.io for details. + * @param key The key of the hash. + * @param field The field in the hash. + * @return Command Response - The string length or 0 if field or + * key does not exist. + */ + public T hstrlen(@NonNull String key, @NonNull String field) { + protobufTransaction.addCommands(buildCommand(HStrlen, buildArgs(key, field))); + return getThis(); + } + /** * Returns a random field name from the hash value 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 e5b8d1ee0b..b6dff5a6bb 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -78,6 +78,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HRandField; import static redis_request.RedisRequestOuterClass.RequestType.HSet; import static redis_request.RedisRequestOuterClass.RequestType.HSetNX; +import static redis_request.RedisRequestOuterClass.RequestType.HStrlen; import static redis_request.RedisRequestOuterClass.RequestType.HVals; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; @@ -1429,6 +1430,31 @@ public void hkeys_returns_success() { assertEquals(values, payload); } + @SneakyThrows + @Test + public void hstrlen_returns_success() { + // setup + String key = "testKey"; + String field = "field"; + String[] args = {key, field}; + Long value = 42L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(HStrlen), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.hstrlen(key, field); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void hrandfield_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 42598f8b15..adb50af73b 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -67,6 +67,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HRandField; import static redis_request.RedisRequestOuterClass.RequestType.HSet; import static redis_request.RedisRequestOuterClass.RequestType.HSetNX; +import static redis_request.RedisRequestOuterClass.RequestType.HStrlen; import static redis_request.RedisRequestOuterClass.RequestType.HVals; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; @@ -295,6 +296,9 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.hkeys("key"); results.add(Pair.of(HKeys, buildArgs("key"))); + transaction.hstrlen("key", "field"); + results.add(Pair.of(HStrlen, buildArgs("key", "field"))); + transaction .hrandfield("key") .hrandfieldWithCount("key", 2) diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 9c36396bff..a5a101e08c 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -775,13 +775,36 @@ public void hkeys(BaseClient client) { assertEquals(0, client.hkeys(key2).get().length); - // Key exists, but it is not a List + // Key exists, but it is not a hash assertEquals(OK, client.set(key2, "value").get()); Exception executionException = assertThrows(ExecutionException.class, () -> client.hkeys(key2).get()); assertTrue(executionException.getCause() instanceof RequestException); } + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void hstrlen(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + + assertEquals(1, client.hset(key1, Map.of("field", "value")).get()); + assertEquals(5L, client.hstrlen(key1, "field").get()); + + // missing value + assertEquals(0, client.hstrlen(key1, "field 2").get()); + + // missing key + assertEquals(0, client.hstrlen(key2, "field").get()); + + // Key exists, but it is not a hash + assertEquals(OK, client.set(key2, "value").get()); + Exception executionException = + assertThrows(ExecutionException.class, () -> client.hstrlen(key2, "field").get()); + assertInstanceOf(RequestException.class, executionException.getCause()); + } + @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 cde8a3a229..c6ba5f7f15 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -214,7 +214,8 @@ private static Object[] hashCommands(BaseTransaction transaction) { .hrandfieldWithCountWithValues(hashKey1, -2) .hincrBy(hashKey1, field3, 5) .hincrByFloat(hashKey1, field3, 5.5) - .hkeys(hashKey1); + .hkeys(hashKey1) + .hstrlen(hashKey1, field2); return new Object[] { 2L, // hset(hashKey1, Map.of(field1, value1, field2, value2)) @@ -236,6 +237,7 @@ private static Object[] hashCommands(BaseTransaction transaction) { 5L, // hincrBy(hashKey1, field3, 5) 10.5, // hincrByFloat(hashKey1, field3, 5.5) new String[] {field2, field3}, // hkeys(hashKey1) + (long) value2.length(), // hstrlen(hashKey1, field2) }; }