diff --git a/CHANGELOG.md b/CHANGELOG.md index a609b1dd79..75aa8272ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * Python: Added HKEYS command ([#1228](https://github.com/aws/glide-for-redis/pull/1228)) * Python: Added ZREMRANGEBYSCORE command ([#1151](https://github.com/aws/glide-for-redis/pull/1151)) * Node: Added SPOP, SPOPCOUNT commands. ([#1117](https://github.com/aws/glide-for-redis/pull/1117)) +* Node: Added ZRANGE command ([#1115](https://github.com/aws/glide-for-redis/pull/1115)) +* Python: Added RENAME command ([#1252](https://github.com/aws/glide-for-redis/pull/1252)) #### Fixes * Python: Fix typing error "‘type’ object is not subscriptable" ([#1203](https://github.com/aws/glide-for-redis/pull/1203)) diff --git a/glide-core/THIRD_PARTY_LICENSES_RUST b/glide-core/THIRD_PARTY_LICENSES_RUST index 0733f7230f..0c33ce8c72 100644 --- a/glide-core/THIRD_PARTY_LICENSES_RUST +++ b/glide-core/THIRD_PARTY_LICENSES_RUST @@ -3451,7 +3451,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bumpalo:3.15.4 +Package: bumpalo:3.16.0 The following copyrights and licenses were found in the source code of this package: @@ -9734,7 +9734,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: getrandom:0.2.13 +Package: getrandom:0.2.14 The following copyrights and licenses were found in the source code of this package: diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 9ce5fddc60..a3d02a945d 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -144,6 +144,7 @@ enum RequestType { LInsert = 101; RPushX = 102; LPushX = 103; + ZMScore = 104; } message Command { @@ -170,7 +171,7 @@ message Transaction { message RedisRequest { uint32 callback_idx = 1; - + oneof command { Command single_command = 2; Transaction transaction = 3; diff --git a/glide-core/src/request_type.rs b/glide-core/src/request_type.rs index 2d8c688a23..e75322c653 100644 --- a/glide-core/src/request_type.rs +++ b/glide-core/src/request_type.rs @@ -112,6 +112,7 @@ pub enum RequestType { LInsert = 101, RPushX = 102, LPushX = 103, + ZMScore = 104, } fn get_two_word_command(first: &str, second: &str) -> Cmd { @@ -227,6 +228,7 @@ impl From<::protobuf::EnumOrUnknown> for RequestType { ProtobufRequestType::Blpop => RequestType::Blpop, ProtobufRequestType::LInsert => RequestType::LInsert, ProtobufRequestType::Spop => RequestType::Spop, + ProtobufRequestType::ZMScore => RequestType::ZMScore, } } } @@ -338,6 +340,7 @@ impl RequestType { RequestType::Blpop => Some(cmd("BLPOP")), RequestType::LInsert => Some(cmd("LINSERT")), RequestType::Spop => Some(cmd("SPOP")), + RequestType::ZMScore => Some(cmd("ZMSCORE")), } } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 01717aae02..c46cbcc354 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -37,6 +37,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.LRange; import static redis_request.RedisRequestOuterClass.RequestType.LRem; import static redis_request.RedisRequestOuterClass.RequestType.LTrim; +import static redis_request.RedisRequestOuterClass.RequestType.Lindex; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; @@ -60,6 +61,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; +import static redis_request.RedisRequestOuterClass.RequestType.ZMScore; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; @@ -447,6 +449,12 @@ public CompletableFuture lrange(@NonNull String key, long start, long response -> castArray(handleArrayOrNullResponse(response), String.class)); } + @Override + public CompletableFuture lindex(@NonNull String key, long index) { + return commandManager.submitNewCommand( + Lindex, new String[] {key, Long.toString(index)}, this::handleStringOrNullResponse); + } + @Override public CompletableFuture ltrim(@NonNull String key, long start, long end) { return commandManager.submitNewCommand( @@ -709,6 +717,15 @@ public CompletableFuture zrankWithScore(@NonNull String key, @NonNull Zrank, new String[] {key, member, WITH_SCORE_REDIS_API}, this::handleArrayOrNullResponse); } + @Override + public CompletableFuture zmscore(@NonNull String key, @NonNull String[] members) { + String[] arguments = ArrayUtils.addFirst(members, key); + return commandManager.submitNewCommand( + ZMScore, + arguments, + response -> castArray(handleArrayOrNullResponse(response), Double.class)); + } + @Override public CompletableFuture xadd(@NonNull String key, @NonNull Map values) { return xadd(key, values, StreamAddOptions.builder().build()); diff --git a/java/client/src/main/java/glide/api/commands/ListBaseCommands.java b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java index f9e50a1553..c85b85fb68 100644 --- a/java/client/src/main/java/glide/api/commands/ListBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java @@ -104,6 +104,30 @@ public interface ListBaseCommands { */ CompletableFuture lrange(String key, long start, long end); + /** + * Returns the element at index from the list stored at key.
+ * The index is zero-based, so 0 means the first element, 1 the second + * element and so on. Negative indices can be used to designate elements starting at the tail of + * the list. Here, -1 means the last element, -2 means the penultimate + * and so forth. + * + * @see redis.io for details. + * @param key The key of the list. + * @param index The index of the element in the list to retrieve. + * @return The element at index in the list stored at key.
+ * If index is out of range or if key does not exist, null + * is returned. + * @example + *
{@code
+     * String payload1 = client.lindex("myList", 0).get();
+     * assert payload1.equals('value1'); // Returns the first element in the list stored at 'myList'.
+     *
+     * String payload2 = client.lindex("myList", -1).get();
+     * assert payload2.equals('value3'); // Returns the last element in the list stored at 'myList'.
+     * }
+ */ + CompletableFuture lindex(String key, long index); + /** * Trims an existing list so that it will contain only the specified range of elements specified. *
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 d4a9e5d4ea..0e048a3bfe 100644 --- a/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/SortedSetBaseCommands.java @@ -456,4 +456,22 @@ CompletableFuture> zrangeWithScores( * } */ CompletableFuture zrankWithScore(String key, String member); + + /** + * Returns the scores associated with the specified members in the sorted set stored + * at key. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param members An array of members in the sorted set. + * @return An Array of scores of the members.
+ * If a member does not exist, the corresponding value in the Array + * will be null. + * @example + *
{@code
+     * Double[] payload = client.zmscore(key1, new String[] {"one", "nonExistentMember", "three"}).get();
+     * assert payload.equals(new Double[] {1.0, null, 3.0});
+     * }
+ */ + CompletableFuture zmscore(String key, String[] members); } 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 b8574e6520..c77d19fb0f 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -46,6 +46,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.LRange; import static redis_request.RedisRequestOuterClass.RequestType.LRem; import static redis_request.RedisRequestOuterClass.RequestType.LTrim; +import static redis_request.RedisRequestOuterClass.RequestType.Lindex; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; @@ -71,6 +72,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; +import static redis_request.RedisRequestOuterClass.RequestType.ZMScore; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; @@ -679,6 +681,28 @@ public T lrange(@NonNull String key, long start, long end) { return getThis(); } + /** + * Returns the element at index from the list stored at key.
+ * The index is zero-based, so 0 means the first element, 1 the second + * element and so on. Negative indices can be used to designate elements starting at the tail of + * the list. Here, -1 means the last element, -2 means the penultimate + * and so forth. + * + * @see redis.io for details. + * @param key The key of the list. + * @param index The index of the element in the list to retrieve. + * @return Command Response - The element at index in the list stored at key + * .
+ * If index is out of range or if key does not exist, null + * is returned. + */ + public T lindex(@NonNull String key, long index) { + ArgsArray commandArgs = buildArgs(key, Long.toString(index)); + + protobufTransaction.addCommands(buildCommand(Lindex, commandArgs)); + return getThis(); + } + /** * Trims an existing list so that it will contain only the specified range of elements specified.
* The offsets start and end are zero-based indexes, with 0 being the @@ -1493,6 +1517,23 @@ public T zrankWithScore(@NonNull String key, @NonNull String member) { return getThis(); } + /** + * Returns the scores associated with the specified members in the sorted set stored + * at key. + * + * @see redis.io for more details. + * @param key The key of the sorted set. + * @param members An array of members in the sorted set. + * @return Command Response - An Array of scores of the members.
+ * If a member does not exist, the corresponding value in the Array + * will be null. + */ + public T zmscore(@NonNull String key, @NonNull String[] members) { + ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(members, key)); + protobufTransaction.addCommands(buildCommand(ZMScore, commandArgs)); + return getThis(); + } + /** * Adds an entry to the specified stream. * diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 8467a3526f..4913afc291 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -66,6 +66,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.LRange; import static redis_request.RedisRequestOuterClass.RequestType.LRem; import static redis_request.RedisRequestOuterClass.RequestType.LTrim; +import static redis_request.RedisRequestOuterClass.RequestType.Lindex; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; @@ -92,6 +93,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; +import static redis_request.RedisRequestOuterClass.RequestType.ZMScore; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; @@ -157,8 +159,8 @@ public void customCommand_returns_success() { Object value = "testValue"; String cmd = "GETSTRING"; String[] arguments = new String[] {cmd, key}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(CustomCommand), eq(arguments), any())) @@ -199,8 +201,8 @@ public void echo_returns_success() { @Test public void ping_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn("PONG"); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete("PONG"); // match on protobuf request when(commandManager.submitNewCommand(eq(Ping), eq(new String[0]), any())) @@ -241,9 +243,9 @@ public void ping_with_message_returns_success() { @Test public void select_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - Long index = 5L; - when(testResponse.get()).thenReturn(OK); + CompletableFuture testResponse = new CompletableFuture<>(); + long index = 5L; + testResponse.complete(OK); // match on protobuf request when(commandManager.submitNewCommand( @@ -265,8 +267,8 @@ public void del_returns_long_success() { // setup String[] keys = new String[] {"testKey1", "testKey2"}; Long numberDeleted = 1L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(numberDeleted); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(numberDeleted); when(commandManager.submitNewCommand(eq(Del), eq(keys), any())).thenReturn(testResponse); // exercise @@ -284,8 +286,8 @@ public void unlink_returns_long_success() { // setup String[] keys = new String[] {"testKey1", "testKey2"}; Long numberUnlinked = 1L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(numberUnlinked); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(numberUnlinked); // match on protobuf request when(commandManager.submitNewCommand(eq(Unlink), eq(keys), any())) @@ -306,8 +308,8 @@ public void get_returns_success() { // setup String key = "testKey"; String value = "testValue"; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); when(commandManager.submitNewCommand(eq(GetString), eq(new String[] {key}), any())) .thenReturn(testResponse); @@ -326,9 +328,10 @@ public void set_returns_success() { // setup String key = "testKey"; String value = "testValue"; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(null); - when(commandManager.submitNewCommand(eq(SetString), eq(new String[] {key, value}), any())) + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(null); + when(commandManager.submitNewCommand( + eq(SetString), eq(new String[] {key, value}), any())) .thenReturn(testResponse); // exercise @@ -354,8 +357,8 @@ public void set_with_SetOptions_OnlyIfExists_returns_success() { .build(); String[] arguments = new String[] {key, value, ONLY_IF_EXISTS.getRedisApi(), "KEEPTTL"}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(null); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(null); when(commandManager.submitNewCommand(eq(SetString), eq(arguments), any())) .thenReturn(testResponse); @@ -383,8 +386,8 @@ public void set_with_SetOptions_OnlyIfDoesNotExist_returns_success() { new String[] { key, value, ONLY_IF_DOES_NOT_EXIST.getRedisApi(), RETURN_OLD_VALUE, "EXAT", "60" }; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); when(commandManager.submitNewCommand(eq(SetString), eq(arguments), any())) .thenReturn(testResponse); @@ -402,8 +405,8 @@ public void exists_returns_long_success() { // setup String[] keys = new String[] {"testKey1", "testKey2"}; Long numberExisting = 1L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(numberExisting); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(numberExisting); when(commandManager.submitNewCommand(eq(Exists), eq(keys), any())) .thenReturn(testResponse); @@ -424,8 +427,8 @@ public void expire_returns_success() { long seconds = 10L; String[] arguments = new String[] {key, Long.toString(seconds)}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(true); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(Boolean.TRUE); // match on protobuf request when(commandManager.submitNewCommand(eq(Expire), eq(arguments), any())) @@ -447,8 +450,8 @@ public void expire_with_expireOptions_returns_success() { long seconds = 10L; String[] arguments = new String[] {key, Long.toString(seconds), "NX"}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(false); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(Boolean.FALSE); // match on protobuf request when(commandManager.submitNewCommand(eq(Expire), eq(arguments), any())) @@ -470,8 +473,8 @@ public void expireAt_returns_success() { long unixSeconds = 100000L; String[] arguments = new String[] {key, Long.toString(unixSeconds)}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(true); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(Boolean.TRUE); // match on protobuf request when(commandManager.submitNewCommand(eq(ExpireAt), eq(arguments), any())) @@ -493,8 +496,8 @@ public void expireAt_with_expireOptions_returns_success() { long unixSeconds = 100000L; String[] arguments = new String[] {key, Long.toString(unixSeconds), "XX"}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(false); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(Boolean.FALSE); // match on protobuf request when(commandManager.submitNewCommand(eq(ExpireAt), eq(arguments), any())) @@ -517,8 +520,8 @@ public void pexpire_returns_success() { long milliseconds = 50000L; String[] arguments = new String[] {key, Long.toString(milliseconds)}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(true); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(Boolean.TRUE); // match on protobuf request when(commandManager.submitNewCommand(eq(PExpire), eq(arguments), any())) @@ -540,8 +543,8 @@ public void pexpire_with_expireOptions_returns_success() { long milliseconds = 50000L; String[] arguments = new String[] {key, Long.toString(milliseconds), "LT"}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(false); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(Boolean.FALSE); // match on protobuf request when(commandManager.submitNewCommand(eq(PExpire), eq(arguments), any())) @@ -564,8 +567,8 @@ public void pexpireAt_returns_success() { long unixMilliseconds = 999999L; String[] arguments = new String[] {key, Long.toString(unixMilliseconds)}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(true); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(Boolean.TRUE); // match on protobuf request when(commandManager.submitNewCommand(eq(PExpireAt), eq(arguments), any())) @@ -587,8 +590,8 @@ public void pexpireAt_with_expireOptions_returns_success() { long unixMilliseconds = 999999L; String[] arguments = new String[] {key, Long.toString(unixMilliseconds), "GT"}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(false); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(Boolean.FALSE); // match on protobuf request when(commandManager.submitNewCommand(eq(PExpireAt), eq(arguments), any())) @@ -609,9 +612,8 @@ public void ttl_returns_success() { // setup String key = "testKey"; long ttl = 999L; - - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(ttl); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(ttl); // match on protobuf request when(commandManager.submitNewCommand(eq(TTL), eq(new String[] {key}), any())) @@ -638,7 +640,7 @@ public void invokeScript_returns_success() { testResponse.complete(payload); // match on protobuf request - when(commandManager.submitScript(eq(script), eq(List.of()), eq(List.of()), any())) + when(commandManager.submitScript(eq(script), eq(List.of()), eq(List.of()), any())) .thenReturn(testResponse); // exercise @@ -665,7 +667,7 @@ public void invokeScript_with_ScriptOptions_returns_success() { testResponse.complete(payload); // match on protobuf request - when(commandManager.submitScript( + when(commandManager.submitScript( eq(script), eq(List.of("key1", "key2")), eq(List.of("arg1", "arg2")), any())) .thenReturn(testResponse); @@ -684,8 +686,8 @@ public void pttl_returns_success() { String key = "testKey"; long pttl = 999000L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(pttl); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(pttl); // match on protobuf request when(commandManager.submitNewCommand(eq(PTTL), eq(new String[] {key}), any())) @@ -725,9 +727,9 @@ public void persist_returns_success() { @Test public void info_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); String testPayload = "Key: Value"; - when(testResponse.get()).thenReturn(testPayload); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(testPayload); when(commandManager.submitNewCommand(eq(Info), eq(new String[0]), any())) .thenReturn(testResponse); @@ -746,9 +748,9 @@ public void info_with_multiple_InfoOptions_returns_success() { // setup String[] arguments = new String[] {InfoOptions.Section.ALL.toString(), InfoOptions.Section.DEFAULT.toString()}; - CompletableFuture testResponse = mock(CompletableFuture.class); String testPayload = "Key: Value"; - when(testResponse.get()).thenReturn(testPayload); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(testPayload); when(commandManager.submitNewCommand(eq(Info), eq(arguments), any())) .thenReturn(testResponse); @@ -770,9 +772,9 @@ public void info_with_multiple_InfoOptions_returns_success() { @Test public void info_with_empty_InfoOptions_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); String testPayload = "Key: Value"; - when(testResponse.get()).thenReturn(testPayload); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(testPayload); when(commandManager.submitNewCommand(eq(Info), eq(new String[0]), any())) .thenReturn(testResponse); @@ -792,11 +794,11 @@ public void mget_returns_success() { String[] keys = {"key1", null, "key2"}; String[] values = {"value1", null, "value2"}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(values); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(values); // match on protobuf request - when(commandManager.submitNewCommand(eq(MGet), eq(keys), any())) + when(commandManager.submitNewCommand(eq(MGet), eq(keys), any())) .thenReturn(testResponse); // exercise @@ -817,8 +819,8 @@ public void mset_returns_success() { keyValueMap.put("key2", "value2"); String[] args = {"key1", "value1", "key2", "value2"}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(OK); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); // match on protobuf request when(commandManager.submitNewCommand(eq(MSet), eq(args), any())) @@ -840,11 +842,11 @@ public void incr_returns_success() { String key = "testKey"; Long value = 10L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request - when(commandManager.submitNewCommand(eq(Incr), eq(new String[] {key}), any())) + when(commandManager.submitNewCommand(eq(Incr), eq(new String[] {key}), any())) .thenReturn(testResponse); // exercise @@ -864,11 +866,11 @@ public void incrBy_returns_success() { long amount = 1L; Long value = 10L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request - when(commandManager.submitNewCommand( + when(commandManager.submitNewCommand( eq(IncrBy), eq(new String[] {key, Long.toString(amount)}), any())) .thenReturn(testResponse); @@ -889,11 +891,11 @@ public void incrByFloat_returns_success() { double amount = 1.1; Double value = 10.1; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request - when(commandManager.submitNewCommand( + when(commandManager.submitNewCommand( eq(IncrByFloat), eq(new String[] {key, Double.toString(amount)}), any())) .thenReturn(testResponse); @@ -913,8 +915,8 @@ public void decr_returns_success() { String key = "testKey"; Long value = 10L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(Decr), eq(new String[] {key}), any())) @@ -937,8 +939,8 @@ public void decrBy_returns_success() { long amount = 1L; Long value = 10L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand( @@ -961,8 +963,8 @@ public void strlen_returns_success() { String key = "testKey"; Long value = 10L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(Strlen), eq(new String[] {key}), any())) @@ -986,8 +988,8 @@ public void hget_success() { String[] args = new String[] {key, field}; String value = "value"; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); when(commandManager.submitNewCommand(eq(HashGet), eq(args), any())) .thenReturn(testResponse); @@ -1011,8 +1013,8 @@ public void hset_success() { String[] args = new String[] {key, "field1", "value1", "field2", "value2"}; Long value = 2L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); when(commandManager.submitNewCommand(eq(HashSet), eq(args), any())) .thenReturn(testResponse); @@ -1059,8 +1061,8 @@ public void hdel_success() { String[] args = {key, "testField1", "testField2"}; Long value = 2L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(HashDel), eq(args), any())) @@ -1131,8 +1133,8 @@ public void hmget_success() { String[] args = {"testKey", "testField1", "testField2"}; String[] value = {"testValue1", "testValue2"}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(HashMGet), eq(args), any())) @@ -1156,8 +1158,8 @@ public void hexists_success() { String[] args = new String[] {key, field}; Boolean value = true; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(HashExists), eq(args), any())) @@ -1182,8 +1184,8 @@ public void hgetall_success() { value.put("key1", "field1"); value.put("key2", "field2"); - CompletableFuture> testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.>submitNewCommand(eq(HashGetAll), eq(args), any())) @@ -1207,8 +1209,8 @@ public void hincrBy_returns_success() { long amount = 1L; Long value = 10L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand( @@ -1233,8 +1235,8 @@ public void hincrByFloat_returns_success() { double amount = 1.0; Double value = 10.0; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand( @@ -1259,8 +1261,8 @@ public void lpush_returns_success() { String[] args = new String[] {key, "value1", "value2"}; Long value = 2L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(LPush), eq(args), any())) @@ -1283,8 +1285,8 @@ public void lpop_returns_success() { String[] args = new String[] {key}; String value = "value"; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(LPop), eq(args), any())) @@ -1308,8 +1310,8 @@ public void lpopCount_returns_success() { String[] args = new String[] {key, Long.toString(count)}; String[] value = new String[] {"value1", "value2"}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(LPop), eq(args), any())) @@ -1334,8 +1336,8 @@ public void lrange_returns_success() { String[] args = new String[] {key, Long.toString(start), Long.toString(end)}; String[] value = new String[] {"value1", "value2"}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(LRange), eq(args), any())) @@ -1350,6 +1352,31 @@ public void lrange_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void lindex_returns_success() { + // setup + String key = "testKey"; + long index = 2; + String[] args = new String[] {key, Long.toString(index)}; + String value = "value"; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Lindex), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.lindex(key, index); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void ltrim_returns_success() { @@ -1359,8 +1386,8 @@ public void ltrim_returns_success() { long end = 2L; String[] args = new String[] {key, Long.toString(end), Long.toString(start)}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(OK); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); // match on protobuf request when(commandManager.submitNewCommand(eq(LTrim), eq(args), any())) @@ -1383,8 +1410,8 @@ public void llen_returns_success() { String[] args = new String[] {key}; long value = 2L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(LLen), eq(args), any())).thenReturn(testResponse); @@ -1408,8 +1435,8 @@ public void lrem_returns_success() { String[] args = new String[] {key, Long.toString(count), element}; long value = 2L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(LRem), eq(args), any())).thenReturn(testResponse); @@ -1432,8 +1459,8 @@ public void rpush_returns_success() { String[] args = new String[] {key, "value1", "value2"}; Long value = 2L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(RPush), eq(args), any())) @@ -1456,8 +1483,8 @@ public void rpop_returns_success() { String value = "value"; String[] args = new String[] {key}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(RPop), eq(args), any())) @@ -1481,8 +1508,8 @@ public void rpopCount_returns_success() { String[] args = new String[] {key, Long.toString(count)}; String[] value = new String[] {"value1", "value2"}; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(RPop), eq(args), any())) @@ -1506,8 +1533,8 @@ public void sadd_returns_success() { String[] arguments = ArrayUtils.addFirst(members, key); Long value = 2L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(SAdd), eq(arguments), any())) @@ -1555,8 +1582,8 @@ public void srem_returns_success() { String[] arguments = ArrayUtils.addFirst(members, key); Long value = 2L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(SRem), eq(arguments), any())) @@ -1578,8 +1605,8 @@ public void smembers_returns_success() { String key = "testKey"; Set value = Set.of("testMember"); - CompletableFuture> testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.>submitNewCommand(eq(SMembers), eq(new String[] {key}), any())) @@ -1601,8 +1628,8 @@ public void scard_returns_success() { String key = "testKey"; Long value = 2L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(SCard), eq(new String[] {key}), any())) @@ -1629,8 +1656,8 @@ public void zadd_noOptions_returns_success() { String[] arguments = ArrayUtils.addFirst(membersScoresArgs, key); Long value = 2L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(Zadd), eq(arguments), any())) @@ -1664,8 +1691,8 @@ public void zadd_withOptions_returns_success() { arguments = ArrayUtils.addAll(arguments, membersScoresArgs); Long value = 2L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(Zadd), eq(arguments), any())) @@ -1708,8 +1735,8 @@ public void zaddIncr_noOptions_returns_success() { String[] arguments = new String[] {key, "INCR", Double.toString(increment), member}; Double value = 3.0; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(Zadd), eq(arguments), any())) @@ -1743,8 +1770,8 @@ public void zaddIncr_withOptions_returns_success() { new String[] {"INCR", Double.toString(increment), member}); Double value = 3.0; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(Zadd), eq(arguments), any())) @@ -1763,8 +1790,8 @@ public void zaddIncr_withOptions_returns_success() { @Test public void clientId_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(42L); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(42L); // match on protobuf request when(commandManager.submitNewCommand(eq(ClientId), eq(new String[0]), any())) @@ -1782,8 +1809,8 @@ public void clientId_returns_success() { @Test public void clientGetName_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn("TEST"); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete("TEST"); // match on protobuf request when(commandManager.submitNewCommand(eq(ClientGetName), eq(new String[0]), any())) @@ -1801,8 +1828,8 @@ public void clientGetName_returns_success() { @Test public void configRewrite_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(OK); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); // match on protobuf request when(commandManager.submitNewCommand(eq(ConfigRewrite), eq(new String[0]), any())) @@ -1821,8 +1848,8 @@ public void configRewrite_returns_success() { @Test public void configResetStat_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(OK); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); // match on protobuf request when(commandManager.submitNewCommand(eq(ConfigResetStat), eq(new String[0]), any())) @@ -1841,9 +1868,9 @@ public void configResetStat_returns_success() { @Test public void configGet_returns_success() { // setup - CompletableFuture> testResponse = mock(CompletableFuture.class); Map testPayload = Map.of("timeout", "1000"); - when(testResponse.get()).thenReturn(testPayload); + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(testPayload); // match on protobuf request when(commandManager.>submitNewCommand( @@ -1863,8 +1890,8 @@ public void configGet_returns_success() { @Test public void configSet_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(OK); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); // match on protobuf request when(commandManager.submitNewCommand( @@ -1888,8 +1915,8 @@ public void zrem_returns_success() { String[] arguments = ArrayUtils.addFirst(members, key); Long value = 2L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(Zrem), eq(arguments), any())) @@ -1912,8 +1939,8 @@ public void zcard_returns_success() { String[] arguments = new String[] {key}; Long value = 3L; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(Zcard), eq(arguments), any())) @@ -2083,7 +2110,6 @@ public void zrange_by_score_with_reverse_returns_success() { String key = "testKey"; RangeByScore rangeByScore = new RangeByScore(new ScoreBoundary(3, false), InfScoreBound.NEGATIVE_INFINITY); - boolean reversed = true; String[] arguments = new String[] {key, rangeByScore.getStart(), rangeByScore.getEnd(), "BYSCORE", "REV"}; String[] value = new String[] {"two", "one"}; @@ -2246,6 +2272,31 @@ public void zrankWithScore_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void zmscore_returns_success() { + // setup + String key = "testKey"; + String[] members = new String[] {"member1", "member2"}; + String[] arguments = new String[] {key, "member1", "member2"}; + Double[] value = new Double[] {2.5, 8.2}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ZMScore), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.zmscore(key, members); + Double[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void xadd_returns_success() { @@ -2424,8 +2475,8 @@ public void type_returns_success() { String[] arguments = new String[] {key}; String value = "none"; - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(value); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); // match on protobuf request when(commandManager.submitNewCommand(eq(Type), eq(arguments), any())) @@ -2444,9 +2495,9 @@ public void type_returns_success() { @Test public void time_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); + CompletableFuture testResponse = new CompletableFuture<>(); String[] payload = new String[] {"UnixTime", "ms"}; - when(testResponse.get()).thenReturn(payload); + testResponse.complete(payload); // match on protobuf request when(commandManager.submitNewCommand(eq(Time), eq(new String[0]), any())) diff --git a/java/client/src/test/java/glide/api/RedisClusterClientTest.java b/java/client/src/test/java/glide/api/RedisClusterClientTest.java index fde9adc3ac..0a92568d6a 100644 --- a/java/client/src/test/java/glide/api/RedisClusterClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClusterClientTest.java @@ -39,7 +39,6 @@ import response.ResponseOuterClass.ConstantResponse; import response.ResponseOuterClass.Response; -@SuppressWarnings("unchecked,resource") public class RedisClusterClientTest { RedisClusterClient service; @@ -62,10 +61,10 @@ public void setUp() { public void custom_command_returns_single_value() { var commandManager = new TestCommandManager(null); - var client = new TestClient(commandManager, "TEST"); - - var value = client.customCommand(TEST_ARGS).get(); - assertEquals("TEST", value.getSingleValue()); + try (var client = new TestClient(commandManager, "TEST")) { + var value = client.customCommand(TEST_ARGS).get(); + assertEquals("TEST", value.getSingleValue()); + } } @Test @@ -74,10 +73,10 @@ public void custom_command_returns_multi_value() { var commandManager = new TestCommandManager(null); var data = Map.of("key1", "value1", "key2", "value2"); - var client = new TestClient(commandManager, data); - - var value = client.customCommand(TEST_ARGS).get(); - assertEquals(data, value.getMultiValue()); + try (var client = new TestClient(commandManager, data)) { + var value = client.customCommand(TEST_ARGS).get(); + assertEquals(data, value.getMultiValue()); + } } @Test @@ -87,10 +86,10 @@ public void custom_command_with_single_node_route_returns_single_value() { var commandManager = new TestCommandManager(null); var data = Map.of("key1", "value1", "key2", "value2"); - var client = new TestClient(commandManager, data); - - var value = client.customCommand(TEST_ARGS, RANDOM).get(); - assertEquals(data, value.getSingleValue()); + try (var client = new TestClient(commandManager, data)) { + var value = client.customCommand(TEST_ARGS, RANDOM).get(); + assertEquals(data, value.getSingleValue()); + } } @Test @@ -99,10 +98,10 @@ public void custom_command_with_multi_node_route_returns_multi_value() { var commandManager = new TestCommandManager(null); var data = Map.of("key1", "value1", "key2", "value2"); - var client = new TestClient(commandManager, data); - - var value = client.customCommand(TEST_ARGS, ALL_NODES).get(); - assertEquals(data, value.getMultiValue()); + try (var client = new TestClient(commandManager, data)) { + var value = client.customCommand(TEST_ARGS, ALL_NODES).get(); + assertEquals(data, value.getMultiValue()); + } } @Test @@ -112,10 +111,10 @@ public void custom_command_returns_single_value_on_constant_response() { new TestCommandManager( Response.newBuilder().setConstantResponse(ConstantResponse.OK).build()); - var client = new TestClient(commandManager, "OK"); - - var value = client.customCommand(TEST_ARGS, ALL_NODES).get(); - assertEquals("OK", value.getSingleValue()); + try (var client = new TestClient(commandManager, "OK")) { + var value = client.customCommand(TEST_ARGS, ALL_NODES).get(); + assertEquals("OK", value.getSingleValue()); + } } private static class TestClient extends RedisClusterClient { @@ -129,8 +128,13 @@ public TestClient(CommandManager commandManager, Object objectToReturn) { @Override protected T handleRedisResponse(Class classType, boolean isNullable, Response response) { - return (T) object; + @SuppressWarnings("unchecked") + T returnValue = (T) object; + return returnValue; } + + @Override + public void close() {} } private static class TestCommandManager extends CommandManager { @@ -153,8 +157,8 @@ public CompletableFuture submitCommandToChannel( @Test public void ping_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn("PONG"); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete("PONG"); // match on protobuf request when(commandManager.submitNewCommand(eq(Ping), eq(new String[0]), any())) @@ -175,7 +179,7 @@ public void ping_with_message_returns_success() { // setup String message = "RETURN OF THE PONG"; String[] arguments = new String[] {message}; - CompletableFuture testResponse = new CompletableFuture(); + CompletableFuture testResponse = new CompletableFuture<>(); testResponse.complete(message); // match on protobuf request @@ -195,8 +199,8 @@ public void ping_with_message_returns_success() { @Test public void ping_with_route_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn("PONG"); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete("PONG"); Route route = ALL_NODES; @@ -286,12 +290,12 @@ public void echo_with_route_returns_success() { @Test public void info_returns_string() { // setup - CompletableFuture> testResponse = mock(CompletableFuture.class); - Map testPayload = new HashMap(); + Map testPayload = new HashMap<>(); testPayload.put("addr1", "value1"); testPayload.put("addr2", "value2"); testPayload.put("addr3", "value3"); - when(testResponse.get()).thenReturn(ClusterValue.of(testPayload)); + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(ClusterValue.of(testPayload)); when(commandManager.>submitNewCommand(eq(Info), eq(new String[0]), any())) .thenReturn(testResponse); @@ -309,10 +313,10 @@ public void info_returns_string() { @Test public void info_with_route_returns_string() { // setup - CompletableFuture> testResponse = mock(CompletableFuture.class); Map testClusterValue = Map.of("addr1", "addr1 result", "addr2", "addr2 result"); Route route = ALL_NODES; - when(testResponse.get()).thenReturn(ClusterValue.of(testClusterValue)); + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(ClusterValue.of(testClusterValue)); when(commandManager.>submitNewCommand( eq(Info), eq(new String[0]), eq(route), any())) .thenReturn(testResponse); @@ -333,9 +337,10 @@ public void info_with_route_returns_string() { public void info_with_route_with_infoOptions_returns_string() { // setup String[] infoArguments = new String[] {"ALL", "DEFAULT"}; - CompletableFuture> testResponse = mock(CompletableFuture.class); Map testClusterValue = Map.of("addr1", "addr1 result", "addr2", "addr2 result"); - when(testResponse.get()).thenReturn(ClusterValue.of(testClusterValue)); + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(ClusterValue.of(testClusterValue)); + Route route = ALL_PRIMARIES; when(commandManager.>submitNewCommand( eq(Info), eq(infoArguments), eq(route), any())) @@ -364,11 +369,12 @@ public void info_with_single_node_route_returns_single_value() { var commandManager = new TestCommandManager(null); var data = "info string"; - var client = new TestClient(commandManager, data); - - var value = client.info(RANDOM).get(); - assertAll( - () -> assertTrue(value.hasSingleData()), () -> assertEquals(data, value.getSingleValue())); + try (var client = new TestClient(commandManager, data)) { + var value = client.info(RANDOM).get(); + assertAll( + () -> assertTrue(value.hasSingleData()), + () -> assertEquals(data, value.getSingleValue())); + } } @Test @@ -377,11 +383,11 @@ public void info_with_multi_node_route_returns_multi_value() { var commandManager = new TestCommandManager(null); var data = Map.of("key1", "value1", "key2", "value2"); - var client = new TestClient(commandManager, data); - - var value = client.info(ALL_NODES).get(); - assertAll( - () -> assertTrue(value.hasMultiData()), () -> assertEquals(data, value.getMultiValue())); + try (var client = new TestClient(commandManager, data)) { + var value = client.info(ALL_NODES).get(); + assertAll( + () -> assertTrue(value.hasMultiData()), () -> assertEquals(data, value.getMultiValue())); + } } @Test @@ -390,11 +396,12 @@ public void info_with_options_and_single_node_route_returns_single_value() { var commandManager = new TestCommandManager(null); var data = "info string"; - var client = new TestClient(commandManager, data); - - var value = client.info(InfoOptions.builder().build(), RANDOM).get(); - assertAll( - () -> assertTrue(value.hasSingleData()), () -> assertEquals(data, value.getSingleValue())); + try (var client = new TestClient(commandManager, data)) { + var value = client.info(InfoOptions.builder().build(), RANDOM).get(); + assertAll( + () -> assertTrue(value.hasSingleData()), + () -> assertEquals(data, value.getSingleValue())); + } } @Test @@ -403,19 +410,19 @@ public void info_with_options_and_multi_node_route_returns_multi_value() { var commandManager = new TestCommandManager(null); var data = Map.of("key1", "value1", "key2", "value2"); - var client = new TestClient(commandManager, data); - - var value = client.info(InfoOptions.builder().build(), ALL_NODES).get(); - assertAll( - () -> assertTrue(value.hasMultiData()), () -> assertEquals(data, value.getMultiValue())); + try (var client = new TestClient(commandManager, data)) { + var value = client.info(InfoOptions.builder().build(), ALL_NODES).get(); + assertAll( + () -> assertTrue(value.hasMultiData()), () -> assertEquals(data, value.getMultiValue())); + } } @SneakyThrows @Test public void clientId_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(42L); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(42L); // match on protobuf request when(commandManager.submitNewCommand(eq(ClientId), eq(new String[0]), any())) @@ -435,10 +442,11 @@ public void clientId_with_multi_node_route_returns_success() { var commandManager = new TestCommandManager(null); var data = Map.of("n1", 42L); - var client = new TestClient(commandManager, data); + try (var client = new TestClient(commandManager, data)) { + var value = client.clientId(ALL_NODES).get(); - var value = client.clientId(ALL_NODES).get(); - assertEquals(data, value.getMultiValue()); + assertEquals(data, value.getMultiValue()); + } } @Test @@ -446,18 +454,18 @@ public void clientId_with_multi_node_route_returns_success() { public void clientId_with_single_node_route_returns_success() { var commandManager = new TestCommandManager(null); - var client = new TestClient(commandManager, 42L); - - var value = client.clientId(RANDOM).get(); - assertEquals(42, value.getSingleValue()); + try (var client = new TestClient(commandManager, 42L)) { + var value = client.clientId(RANDOM).get(); + assertEquals(42, value.getSingleValue()); + } } @SneakyThrows @Test public void clientGetName_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn("TEST"); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete("TEST"); // match on protobuf request when(commandManager.submitNewCommand(eq(ClientGetName), eq(new String[0]), any())) @@ -476,10 +484,10 @@ public void clientGetName_returns_success() { public void clientGetName_with_single_node_route_returns_success() { var commandManager = new TestCommandManager(null); - var client = new TestClient(commandManager, "TEST"); - - var value = client.clientGetName(RANDOM).get(); - assertEquals("TEST", value.getSingleValue()); + try (var client = new TestClient(commandManager, "TEST")) { + var value = client.clientGetName(RANDOM).get(); + assertEquals("TEST", value.getSingleValue()); + } } @Test @@ -488,18 +496,18 @@ public void clientGetName_with_multi_node_route_returns_success() { var commandManager = new TestCommandManager(null); var data = Map.of("n1", "TEST"); - var client = new TestClient(commandManager, data); - - var value = client.clientGetName(ALL_NODES).get(); - assertEquals(data, value.getMultiValue()); + try (var client = new TestClient(commandManager, data)) { + var value = client.clientGetName(ALL_NODES).get(); + assertEquals(data, value.getMultiValue()); + } } @SneakyThrows @Test public void configRewrite_without_route_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(OK); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); // match on protobuf request when(commandManager.submitNewCommand(eq(ConfigRewrite), eq(new String[0]), any())) @@ -518,8 +526,8 @@ public void configRewrite_without_route_returns_success() { @Test public void configRewrite_with_route_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(OK); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); Route route = ALL_NODES; @@ -541,8 +549,8 @@ public void configRewrite_with_route_returns_success() { @Test public void configResetStat_without_route_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(OK); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); // match on protobuf request when(commandManager.submitNewCommand(eq(ConfigResetStat), eq(new String[0]), any())) @@ -561,8 +569,8 @@ public void configResetStat_without_route_returns_success() { @Test public void configResetStat_with_route_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(OK); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); Route route = ALL_NODES; @@ -585,9 +593,9 @@ public void configResetStat_with_route_returns_success() { @Test public void configGet_returns_success() { // setup - CompletableFuture> testResponse = mock(CompletableFuture.class); var testPayload = Map.of("timeout", "1000"); - when(testResponse.get()).thenReturn(testPayload); + CompletableFuture> testResponse = new CompletableFuture<>(); + testResponse.complete(testPayload); // match on protobuf request when(commandManager.>submitNewCommand( @@ -610,11 +618,12 @@ public void configGet_with_single_node_route_returns_single_value() { var commandManager = new TestCommandManager(null); var data = Map.of("timeout", "1000", "maxmemory", "1GB"); - var client = new TestClient(commandManager, data); - - var value = client.configGet(TEST_ARGS, RANDOM).get(); - assertAll( - () -> assertTrue(value.hasSingleData()), () -> assertEquals(data, value.getSingleValue())); + try (var client = new TestClient(commandManager, data)) { + var value = client.configGet(TEST_ARGS, RANDOM).get(); + assertAll( + () -> assertTrue(value.hasSingleData()), + () -> assertEquals(data, value.getSingleValue())); + } } @Test @@ -623,19 +632,19 @@ public void configGet_with_multi_node_route_returns_multi_value() { var commandManager = new TestCommandManager(null); var data = Map.of("node1", Map.of("timeout", "1000", "maxmemory", "1GB")); - var client = new TestClient(commandManager, data); - - var value = client.configGet(TEST_ARGS, ALL_NODES).get(); - assertAll( - () -> assertTrue(value.hasMultiData()), () -> assertEquals(data, value.getMultiValue())); + try (var client = new TestClient(commandManager, data)) { + var value = client.configGet(TEST_ARGS, ALL_NODES).get(); + assertAll( + () -> assertTrue(value.hasMultiData()), () -> assertEquals(data, value.getMultiValue())); + } } @SneakyThrows @Test public void configSet_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(OK); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); // match on protobuf request when(commandManager.submitNewCommand( @@ -654,8 +663,8 @@ public void configSet_returns_success() { @Test public void configSet_with_route_returns_success() { // setup - CompletableFuture testResponse = mock(CompletableFuture.class); - when(testResponse.get()).thenReturn(OK); + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); // match on protobuf request when(commandManager.submitNewCommand( 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 f7ca041524..43c2d21876 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -3,8 +3,15 @@ import static glide.api.commands.SortedSetBaseCommands.WITH_SCORES_REDIS_API; import static glide.api.commands.SortedSetBaseCommands.WITH_SCORE_REDIS_API; +import static glide.api.models.commands.ExpireOptions.HAS_EXISTING_EXPIRY; +import static glide.api.models.commands.ExpireOptions.HAS_NO_EXPIRY; +import static glide.api.models.commands.ExpireOptions.NEW_EXPIRY_LESS_THAN_CURRENT; +import static glide.api.models.commands.InfoOptions.Section.EVERYTHING; import static glide.api.models.commands.LInsertOptions.InsertPosition.AFTER; +import static glide.api.models.commands.RangeOptions.InfScoreBound.NEGATIVE_INFINITY; +import static glide.api.models.commands.RangeOptions.InfScoreBound.POSITIVE_INFINITY; import static glide.api.models.commands.SetOptions.RETURN_OLD_VALUE; +import static glide.api.models.commands.ZaddOptions.UpdateOptions.SCORE_LESS_THAN_CURRENT; import static org.junit.jupiter.api.Assertions.assertEquals; import static redis_request.RedisRequestOuterClass.RequestType.Blpop; import static redis_request.RedisRequestOuterClass.RequestType.Brpop; @@ -45,6 +52,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.LRange; import static redis_request.RedisRequestOuterClass.RequestType.LRem; import static redis_request.RedisRequestOuterClass.RequestType.LTrim; +import static redis_request.RedisRequestOuterClass.RequestType.Lindex; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; @@ -70,6 +78,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; +import static redis_request.RedisRequestOuterClass.RequestType.ZMScore; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMax; import static redis_request.RedisRequestOuterClass.RequestType.ZPopMin; import static redis_request.RedisRequestOuterClass.RequestType.ZScore; @@ -79,9 +88,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Zrank; import static redis_request.RedisRequestOuterClass.RequestType.Zrem; -import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.InfoOptions; -import glide.api.models.commands.RangeOptions.InfScoreBound; import glide.api.models.commands.RangeOptions.Limit; import glide.api.models.commands.RangeOptions.RangeByScore; import glide.api.models.commands.RangeOptions.ScoreBoundary; @@ -112,272 +119,188 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) List> results = new LinkedList<>(); transaction.get("key"); - results.add(Pair.of(GetString, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(GetString, buildArgs("key"))); transaction.set("key", "value"); - results.add(Pair.of(SetString, ArgsArray.newBuilder().addArgs("key").addArgs("value").build())); + results.add(Pair.of(SetString, buildArgs("key", "value"))); transaction.set("key", "value", SetOptions.builder().returnOldValue(true).build()); - results.add( - Pair.of( - SetString, - ArgsArray.newBuilder() - .addArgs("key") - .addArgs("value") - .addArgs(RETURN_OLD_VALUE) - .build())); + results.add(Pair.of(SetString, buildArgs("key", "value", RETURN_OLD_VALUE))); transaction.del(new String[] {"key1", "key2"}); - results.add(Pair.of(Del, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").build())); + results.add(Pair.of(Del, buildArgs("key1", "key2"))); transaction.echo("GLIDE"); - results.add(Pair.of(Echo, ArgsArray.newBuilder().addArgs("GLIDE").build())); + results.add(Pair.of(Echo, buildArgs("GLIDE"))); transaction.ping(); - results.add(Pair.of(Ping, ArgsArray.newBuilder().build())); + results.add(Pair.of(Ping, buildArgs())); transaction.ping("KING PONG"); - results.add(Pair.of(Ping, ArgsArray.newBuilder().addArgs("KING PONG").build())); + results.add(Pair.of(Ping, buildArgs("KING PONG"))); transaction.info(); - results.add(Pair.of(Info, ArgsArray.newBuilder().build())); + results.add(Pair.of(Info, buildArgs())); - transaction.info(InfoOptions.builder().section(InfoOptions.Section.EVERYTHING).build()); - results.add( - Pair.of( - Info, - ArgsArray.newBuilder().addArgs(InfoOptions.Section.EVERYTHING.toString()).build())); + transaction.info(InfoOptions.builder().section(EVERYTHING).build()); + results.add(Pair.of(Info, buildArgs(EVERYTHING.toString()))); transaction.mset(Map.of("key", "value")); - results.add(Pair.of(MSet, ArgsArray.newBuilder().addArgs("key").addArgs("value").build())); + results.add(Pair.of(MSet, buildArgs("key", "value"))); transaction.mget(new String[] {"key"}); - results.add(Pair.of(MGet, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(MGet, buildArgs("key"))); transaction.incr("key"); - results.add(Pair.of(Incr, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(Incr, buildArgs("key"))); transaction.incrBy("key", 1); - results.add(Pair.of(IncrBy, ArgsArray.newBuilder().addArgs("key").addArgs("1").build())); + results.add(Pair.of(IncrBy, buildArgs("key", "1"))); transaction.incrByFloat("key", 2.5); - results.add(Pair.of(IncrByFloat, ArgsArray.newBuilder().addArgs("key").addArgs("2.5").build())); + results.add(Pair.of(IncrByFloat, buildArgs("key", "2.5"))); transaction.decr("key"); - results.add(Pair.of(Decr, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(Decr, buildArgs("key"))); transaction.decrBy("key", 2); - results.add(Pair.of(DecrBy, ArgsArray.newBuilder().addArgs("key").addArgs("2").build())); + results.add(Pair.of(DecrBy, buildArgs("key", "2"))); transaction.strlen("key"); - results.add(Pair.of(Strlen, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(Strlen, buildArgs("key"))); transaction.hset("key", Map.of("field", "value")); - results.add( - Pair.of( - HashSet, - ArgsArray.newBuilder().addArgs("key").addArgs("field").addArgs("value").build())); + results.add(Pair.of(HashSet, buildArgs("key", "field", "value"))); transaction.hsetnx("key", "field", "value"); - results.add( - Pair.of( - HSetNX, - ArgsArray.newBuilder().addArgs("key").addArgs("field").addArgs("value").build())); + results.add(Pair.of(HSetNX, buildArgs("key", "field", "value"))); transaction.hget("key", "field"); - results.add(Pair.of(HashGet, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + results.add(Pair.of(HashGet, buildArgs("key", "field"))); transaction.hdel("key", new String[] {"field"}); - results.add(Pair.of(HashDel, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + results.add(Pair.of(HashDel, buildArgs("key", "field"))); transaction.hlen("key"); - results.add(Pair.of(HLen, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(HLen, buildArgs("key"))); transaction.hvals("key"); - results.add(Pair.of(Hvals, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(Hvals, buildArgs("key"))); transaction.hmget("key", new String[] {"field"}); - results.add(Pair.of(HashMGet, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + results.add(Pair.of(HashMGet, buildArgs("key", "field"))); transaction.hexists("key", "field"); - results.add( - Pair.of(HashExists, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + results.add(Pair.of(HashExists, buildArgs("key", "field"))); transaction.hgetall("key"); - results.add(Pair.of(HashGetAll, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(HashGetAll, buildArgs("key"))); transaction.hincrBy("key", "field", 1); - results.add( - Pair.of( - HashIncrBy, - ArgsArray.newBuilder().addArgs("key").addArgs("field").addArgs("1").build())); + results.add(Pair.of(HashIncrBy, buildArgs("key", "field", "1"))); transaction.hincrByFloat("key", "field", 1.5); - results.add( - Pair.of( - HashIncrByFloat, - ArgsArray.newBuilder().addArgs("key").addArgs("field").addArgs("1.5").build())); + results.add(Pair.of(HashIncrByFloat, buildArgs("key", "field", "1.5"))); transaction.lpush("key", new String[] {"element1", "element2"}); - results.add( - Pair.of( - LPush, - ArgsArray.newBuilder().addArgs("key").addArgs("element1").addArgs("element2").build())); + results.add(Pair.of(LPush, buildArgs("key", "element1", "element2"))); transaction.lpop("key"); - results.add(Pair.of(LPop, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(LPop, buildArgs("key"))); transaction.lpopCount("key", 2); - results.add(Pair.of(LPop, ArgsArray.newBuilder().addArgs("key").addArgs("2").build())); + results.add(Pair.of(LPop, buildArgs("key", "2"))); transaction.lrange("key", 1, 2); - results.add( - Pair.of(LRange, ArgsArray.newBuilder().addArgs("key").addArgs("1").addArgs("2").build())); + results.add(Pair.of(LRange, buildArgs("key", "1", "2"))); + + transaction.lindex("key", 1); + results.add(Pair.of(Lindex, ArgsArray.newBuilder().addArgs("key").addArgs("1").build())); transaction.ltrim("key", 1, 2); - results.add( - Pair.of(LTrim, ArgsArray.newBuilder().addArgs("key").addArgs("1").addArgs("2").build())); + results.add(Pair.of(LTrim, buildArgs("key", "1", "2"))); transaction.llen("key"); - results.add(Pair.of(LLen, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(LLen, buildArgs("key"))); transaction.lrem("key", 1, "element"); - results.add( - Pair.of( - LRem, ArgsArray.newBuilder().addArgs("key").addArgs("1").addArgs("element").build())); + results.add(Pair.of(LRem, buildArgs("key", "1", "element"))); transaction.rpush("key", new String[] {"element"}); - results.add(Pair.of(RPush, ArgsArray.newBuilder().addArgs("key").addArgs("element").build())); + results.add(Pair.of(RPush, buildArgs("key", "element"))); transaction.rpop("key"); - results.add(Pair.of(RPop, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(RPop, buildArgs("key"))); transaction.rpopCount("key", 2); - results.add(Pair.of(RPop, ArgsArray.newBuilder().addArgs("key").addArgs("2").build())); + results.add(Pair.of(RPop, buildArgs("key", "2"))); transaction.sadd("key", new String[] {"value"}); - results.add(Pair.of(SAdd, ArgsArray.newBuilder().addArgs("key").addArgs("value").build())); + results.add(Pair.of(SAdd, buildArgs("key", "value"))); transaction.sismember("key", "member"); results.add( Pair.of(SIsMember, ArgsArray.newBuilder().addArgs("key").addArgs("member").build())); transaction.srem("key", new String[] {"value"}); - results.add(Pair.of(SRem, ArgsArray.newBuilder().addArgs("key").addArgs("value").build())); + results.add(Pair.of(SRem, buildArgs("key", "value"))); transaction.smembers("key"); - results.add(Pair.of(SMembers, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(SMembers, buildArgs("key"))); transaction.scard("key"); - results.add(Pair.of(SCard, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(SCard, buildArgs("key"))); transaction.exists(new String[] {"key1", "key2"}); - results.add(Pair.of(Exists, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").build())); + results.add(Pair.of(Exists, buildArgs("key1", "key2"))); transaction.unlink(new String[] {"key1", "key2"}); - results.add(Pair.of(Unlink, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").build())); + results.add(Pair.of(Unlink, buildArgs("key1", "key2"))); transaction.expire("key", 9L); - results.add( - Pair.of(Expire, ArgsArray.newBuilder().addArgs("key").addArgs(Long.toString(9L)).build())); - - transaction.expire("key", 99L, ExpireOptions.NEW_EXPIRY_GREATER_THAN_CURRENT); - results.add( - Pair.of( - Expire, - ArgsArray.newBuilder() - .addArgs("key") - .addArgs(Long.toString(99L)) - .addArgs("GT") - .build())); - - transaction.expireAt("key", 999L); - results.add( - Pair.of( - ExpireAt, ArgsArray.newBuilder().addArgs("key").addArgs(Long.toString(999L)).build())); - - transaction.expireAt("key", 9999L, ExpireOptions.NEW_EXPIRY_LESS_THAN_CURRENT); - results.add( - Pair.of( - ExpireAt, - ArgsArray.newBuilder() - .addArgs("key") - .addArgs(Long.toString(9999L)) - .addArgs("LT") - .build())); + results.add(Pair.of(Expire, buildArgs("key", "9"))); + transaction.expireAt("key", 9999L, NEW_EXPIRY_LESS_THAN_CURRENT); + results.add(Pair.of(ExpireAt, buildArgs("key", "9999", "LT"))); transaction.pexpire("key", 99999L); - results.add( - Pair.of( - PExpire, ArgsArray.newBuilder().addArgs("key").addArgs(Long.toString(99999L)).build())); + results.add(Pair.of(PExpire, buildArgs("key", "99999"))); - transaction.pexpire("key", 999999L, ExpireOptions.HAS_EXISTING_EXPIRY); - results.add( - Pair.of( - PExpire, - ArgsArray.newBuilder() - .addArgs("key") - .addArgs(Long.toString(999999L)) - .addArgs("XX") - .build())); + transaction.pexpire("key", 999999L, HAS_EXISTING_EXPIRY); + results.add(Pair.of(PExpire, buildArgs("key", "999999", "XX"))); transaction.pexpireAt("key", 9999999L); - results.add( - Pair.of( - PExpireAt, - ArgsArray.newBuilder().addArgs("key").addArgs(Long.toString(9999999L)).build())); + results.add(Pair.of(PExpireAt, buildArgs("key", "9999999"))); - transaction.pexpireAt("key", 99999999L, ExpireOptions.HAS_NO_EXPIRY); - results.add( - Pair.of( - PExpireAt, - ArgsArray.newBuilder() - .addArgs("key") - .addArgs(Long.toString(99999999L)) - .addArgs("NX") - .build())); + transaction.pexpireAt("key", 99999999L, HAS_NO_EXPIRY); + results.add(Pair.of(PExpireAt, buildArgs("key", "99999999", "NX"))); transaction.ttl("key"); - results.add(Pair.of(TTL, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(TTL, buildArgs("key"))); transaction.pttl("key"); - results.add(Pair.of(PTTL, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(PTTL, buildArgs("key"))); transaction.clientId(); - results.add(Pair.of(ClientId, ArgsArray.newBuilder().build())); + results.add(Pair.of(ClientId, buildArgs())); transaction.clientGetName(); - results.add(Pair.of(ClientGetName, ArgsArray.newBuilder().build())); + results.add(Pair.of(ClientGetName, buildArgs())); transaction.configRewrite(); - results.add(Pair.of(ConfigRewrite, ArgsArray.newBuilder().build())); + results.add(Pair.of(ConfigRewrite, buildArgs())); transaction.configResetStat(); - results.add(Pair.of(ConfigResetStat, ArgsArray.newBuilder().build())); + results.add(Pair.of(ConfigResetStat, buildArgs())); transaction.configGet(new String[] {"maxmemory", "hash-max-listpack-entries"}); - results.add( - Pair.of( - ConfigGet, - ArgsArray.newBuilder() - .addArgs("maxmemory") - .addArgs("hash-max-listpack-entries") - .build())); + results.add(Pair.of(ConfigGet, buildArgs("maxmemory", "hash-max-listpack-entries"))); var configSetMap = new LinkedHashMap(); configSetMap.put("maxmemory", "100mb"); configSetMap.put("save", "60"); transaction.configSet(configSetMap); - results.add( - Pair.of( - ConfigSet, - ArgsArray.newBuilder() - .addArgs("maxmemory") - .addArgs("100mb") - .addArgs("save") - .addArgs("60") - .build())); + results.add(Pair.of(ConfigSet, buildArgs("maxmemory", "100mb", "save", "60"))); Map membersScores = new LinkedHashMap<>(); membersScores.put("member1", 1.0); @@ -385,108 +308,64 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.zadd( "key", membersScores, - ZaddOptions.builder() - .updateOptions(ZaddOptions.UpdateOptions.SCORE_LESS_THAN_CURRENT) - .build(), + ZaddOptions.builder().updateOptions(SCORE_LESS_THAN_CURRENT).build(), true); - results.add( - Pair.of( - Zadd, - ArgsArray.newBuilder() - .addArgs("key") - .addArgs("LT") - .addArgs("CH") - .addArgs("1.0") - .addArgs("member1") - .addArgs("2.0") - .addArgs("member2") - .build())); + results.add(Pair.of(Zadd, buildArgs("key", "LT", "CH", "1.0", "member1", "2.0", "member2"))); transaction.zaddIncr( "key", "member1", 3.0, - ZaddOptions.builder() - .updateOptions(ZaddOptions.UpdateOptions.SCORE_LESS_THAN_CURRENT) - .build()); - results.add( - Pair.of( - Zadd, - ArgsArray.newBuilder() - .addArgs("key") - .addArgs("LT") - .addArgs("INCR") - .addArgs("3.0") - .addArgs("member1") - .build())); + ZaddOptions.builder().updateOptions(SCORE_LESS_THAN_CURRENT).build()); + results.add(Pair.of(Zadd, buildArgs("key", "LT", "INCR", "3.0", "member1"))); transaction.zrem("key", new String[] {"member1", "member2"}); - results.add( - Pair.of( - Zrem, - ArgsArray.newBuilder().addArgs("key").addArgs("member1").addArgs("member2").build())); + results.add(Pair.of(Zrem, buildArgs("key", "member1", "member2"))); transaction.zcard("key"); - results.add(Pair.of(Zcard, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(Zcard, buildArgs("key"))); transaction.zpopmin("key"); - results.add(Pair.of(ZPopMin, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(ZPopMin, buildArgs("key"))); transaction.zpopmin("key", 2); - results.add(Pair.of(ZPopMin, ArgsArray.newBuilder().addArgs("key").addArgs("2").build())); + results.add(Pair.of(ZPopMin, buildArgs("key", "2"))); transaction.zpopmax("key"); - results.add(Pair.of(ZPopMax, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(ZPopMax, buildArgs("key"))); transaction.zpopmax("key", 2); - results.add(Pair.of(ZPopMax, ArgsArray.newBuilder().addArgs("key").addArgs("2").build())); + results.add(Pair.of(ZPopMax, buildArgs("key", "2"))); transaction.zscore("key", "member"); - results.add(Pair.of(ZScore, ArgsArray.newBuilder().addArgs("key").addArgs("member").build())); + results.add(Pair.of(ZScore, buildArgs("key", "member"))); transaction.zrank("key", "member"); - results.add(Pair.of(Zrank, ArgsArray.newBuilder().addArgs("key").addArgs("member").build())); + results.add(Pair.of(Zrank, buildArgs("key", "member"))); transaction.zrankWithScore("key", "member"); + results.add(Pair.of(Zrank, buildArgs("key", "member", WITH_SCORE_REDIS_API))); + + transaction.zmscore("key", new String[] {"member1", "member2"}); results.add( Pair.of( - Zrank, - ArgsArray.newBuilder() - .addArgs("key") - .addArgs("member") - .addArgs(WITH_SCORE_REDIS_API) - .build())); + ZMScore, + ArgsArray.newBuilder().addArgs("key").addArgs("member1").addArgs("member2").build())); transaction.xadd("key", Map.of("field1", "foo1")); - results.add( - Pair.of( - XAdd, - ArgsArray.newBuilder() - .addArgs("key") - .addArgs("*") - .addArgs("field1") - .addArgs("foo1") - .build())); + results.add(Pair.of(XAdd, buildArgs("key", "*", "field1", "foo1"))); transaction.xadd("key", Map.of("field1", "foo1"), StreamAddOptions.builder().id("id").build()); - results.add( - Pair.of( - XAdd, - ArgsArray.newBuilder() - .addArgs("key") - .addArgs("id") - .addArgs("field1") - .addArgs("foo1") - .build())); + results.add(Pair.of(XAdd, buildArgs("key", "id", "field1", "foo1"))); transaction.time(); - results.add(Pair.of(Time, ArgsArray.newBuilder().build())); + results.add(Pair.of(Time, buildArgs())); transaction.persist("key"); - results.add(Pair.of(Persist, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(Persist, buildArgs("key"))); transaction.type("key"); - results.add(Pair.of(Type, ArgsArray.newBuilder().addArgs("key").build())); + results.add(Pair.of(Type, buildArgs("key"))); transaction.linsert("key", AFTER, "pivot", "elem"); results.add( @@ -500,69 +379,34 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) .build())); transaction.brpop(new String[] {"key1", "key2"}, 0.5); - results.add( - Pair.of( - Brpop, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").addArgs("0.5").build())); + results.add(Pair.of(Brpop, buildArgs("key1", "key2", "0.5"))); transaction.blpop(new String[] {"key1", "key2"}, 0.5); - results.add( - Pair.of( - Blpop, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").addArgs("0.5").build())); + results.add(Pair.of(Blpop, buildArgs("key1", "key2", "0.5"))); transaction.rpushx("key", new String[] {"element1", "element2"}); - results.add( - Pair.of( - RPushX, - ArgsArray.newBuilder().addArgs("key").addArgs("element1").addArgs("element2").build())); + results.add(Pair.of(RPushX, buildArgs("key", "element1", "element2"))); transaction.lpushx("key", new String[] {"element1", "element2"}); - results.add( - Pair.of( - LPushX, - ArgsArray.newBuilder().addArgs("key").addArgs("element1").addArgs("element2").build())); + results.add(Pair.of(LPushX, buildArgs("key", "element1", "element2"))); transaction.zrange( "key", - new RangeByScore( - InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), + new RangeByScore(NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), true); results.add( - Pair.of( - Zrange, - ArgsArray.newBuilder() - .addArgs("key") - .addArgs("-inf") - .addArgs("(3.0") - .addArgs("BYSCORE") - .addArgs("REV") - .addArgs("LIMIT") - .addArgs("1") - .addArgs("2") - .build())); + Pair.of(Zrange, buildArgs("key", "-inf", "(3.0", "BYSCORE", "REV", "LIMIT", "1", "2"))); transaction.zrangeWithScores( "key", - new RangeByScore( - new ScoreBoundary(5, true), InfScoreBound.POSITIVE_INFINITY, new Limit(1, 2)), + new RangeByScore(new ScoreBoundary(5, true), POSITIVE_INFINITY, new Limit(1, 2)), false); results.add( Pair.of( Zrange, - ArgsArray.newBuilder() - .addArgs("key") - .addArgs("5.0") - .addArgs("+inf") - .addArgs("BYSCORE") - .addArgs("LIMIT") - .addArgs("1") - .addArgs("2") - .addArgs(WITH_SCORES_REDIS_API) - .build())); + buildArgs("key", "5.0", "+inf", "BYSCORE", "LIMIT", "1", "2", WITH_SCORES_REDIS_API))); transaction.pfadd("hll", new String[] {"a", "b", "c"}); - results.add( - Pair.of( - PfAdd, - ArgsArray.newBuilder().addArgs("hll").addArgs("a").addArgs("b").addArgs("c").build())); + results.add(Pair.of(PfAdd, buildArgs("hll", "a", "b", "c"))); transaction.pfcount(new String[] {"hll1", "hll2"}); results.add(Pair.of(PfCount, ArgsArray.newBuilder().addArgs("hll1").addArgs("hll2").build())); @@ -583,4 +427,12 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), assertEquals(results.get(idx).getRight(), protobuf.getArgsArray()); } } + + private ArgsArray buildArgs(String... args) { + var builder = ArgsArray.newBuilder(); + for (var arg : args) { + builder.addArgs(arg); + } + return builder.build(); + } } diff --git a/java/client/src/test/java/glide/connection/ConnectionWithGlideMockTests.java b/java/client/src/test/java/glide/connection/ConnectionWithGlideMockTests.java index 331af6fa39..08235ac1fc 100644 --- a/java/client/src/test/java/glide/connection/ConnectionWithGlideMockTests.java +++ b/java/client/src/test/java/glide/connection/ConnectionWithGlideMockTests.java @@ -169,15 +169,16 @@ public void rethrow_error_on_read_when_malformed_packet_received() { @Test @SneakyThrows public void rethrow_error_if_UDS_channel_closed() { - var client = new TestClient(channelHandler); - stopRustCoreLibMock(); - try { - var exception = - assertThrows(ExecutionException.class, () -> client.customCommand(new String[0]).get()); - assertTrue(exception.getCause() instanceof ClosingException); - } finally { - // restart mock to let other tests pass if this one failed - startRustCoreLibMock(null); + try (var client = new TestClient(channelHandler)) { + stopRustCoreLibMock(); + try { + var exception = + assertThrows(ExecutionException.class, () -> client.customCommand(new String[0]).get()); + assertTrue(exception.getCause() instanceof ClosingException); + } finally { + // restart mock to let other tests pass if this one failed + startRustCoreLibMock(null); + } } } diff --git a/java/client/src/test/java/glide/connectors/resources/ThreadPoolResourceAllocatorTest.java b/java/client/src/test/java/glide/connectors/resources/ThreadPoolResourceAllocatorTest.java index 74f45779ef..3ce2052582 100644 --- a/java/client/src/test/java/glide/connectors/resources/ThreadPoolResourceAllocatorTest.java +++ b/java/client/src/test/java/glide/connectors/resources/ThreadPoolResourceAllocatorTest.java @@ -3,9 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.mockito.internal.verification.VerificationModeFactory.times; import io.netty.channel.EventLoop; import io.netty.channel.EventLoopGroup; @@ -15,8 +13,6 @@ public class ThreadPoolResourceAllocatorTest { - ThreadPoolResourceAllocator service; - @BeforeEach public void init() { var threadPoolResource = ThreadPoolResourceAllocator.getOrCreate(() -> null); @@ -30,19 +26,19 @@ public void init() { public void getOrCreate_returns_default_after_repeated_calls() { ThreadPoolResource mockedThreadPoolResource = mock(ThreadPoolResource.class); EventLoopGroup mockedEventLoopGroup = mock(EventLoop.class); - Supplier threadPoolSupplier = mock(Supplier.class); + + Supplier threadPoolSupplier = () -> mockedThreadPoolResource; when(mockedThreadPoolResource.getEventLoopGroup()).thenReturn(mockedEventLoopGroup); when(mockedEventLoopGroup.isShuttingDown()).thenReturn(false); - when(threadPoolSupplier.get()).thenReturn(mockedThreadPoolResource); - ThreadPoolResource theResource = service.getOrCreate(threadPoolSupplier); + ThreadPoolResource theResource = ThreadPoolResourceAllocator.getOrCreate(threadPoolSupplier); assertEquals(mockedThreadPoolResource, theResource); // Ensure that supplier only is invoked once to set up the shared resource - ThreadPoolResource theSameResource = service.getOrCreate(threadPoolSupplier); + ThreadPoolResource theSameResource = + ThreadPoolResourceAllocator.getOrCreate(threadPoolSupplier); assertEquals(mockedThreadPoolResource, theSameResource); - verify(threadPoolSupplier, times(1)).get(); // teardown when(mockedEventLoopGroup.isShuttingDown()).thenReturn(true); @@ -52,19 +48,19 @@ public void getOrCreate_returns_default_after_repeated_calls() { public void getOrCreate_returns_new_thread_pool_after_shutdown() { ThreadPoolResource mockedThreadPoolResource = mock(ThreadPoolResource.class); EventLoopGroup mockedEventLoopGroup = mock(EventLoop.class); - Supplier threadPoolSupplier = mock(Supplier.class); + + Supplier threadPoolSupplier = () -> mockedThreadPoolResource; when(mockedThreadPoolResource.getEventLoopGroup()).thenReturn(mockedEventLoopGroup); when(mockedEventLoopGroup.isShuttingDown()).thenReturn(true); - when(threadPoolSupplier.get()).thenReturn(mockedThreadPoolResource); - ThreadPoolResource theResource = service.getOrCreate(threadPoolSupplier); + ThreadPoolResource theResource = ThreadPoolResourceAllocator.getOrCreate(threadPoolSupplier); assertEquals(mockedThreadPoolResource, theResource); // Ensure that supplier only is invoked once to set up the shared resource - ThreadPoolResource theSameResource = service.getOrCreate(threadPoolSupplier); + ThreadPoolResource theSameResource = + ThreadPoolResourceAllocator.getOrCreate(threadPoolSupplier); assertEquals(mockedThreadPoolResource, theSameResource); - verify(threadPoolSupplier, times(2)).get(); // teardown when(mockedEventLoopGroup.isShuttingDown()).thenReturn(true); diff --git a/java/client/src/test/java/glide/ffi/FfiTest.java b/java/client/src/test/java/glide/ffi/FfiTest.java index c0af584ac0..73c9082c20 100644 --- a/java/client/src/test/java/glide/ffi/FfiTest.java +++ b/java/client/src/test/java/glide/ffi/FfiTest.java @@ -65,7 +65,7 @@ public void redisValueToJavaValue_Okay() { } @ParameterizedTest - @ValueSource(longs = {0L, 100L, 774L, Integer.MAX_VALUE + 1, Integer.MIN_VALUE - 1}) + @ValueSource(longs = {0L, 100L, 774L, Integer.MAX_VALUE + 1L, Integer.MIN_VALUE - 1L}) public void redisValueToJavaValue_Int(Long input) { long ptr = FfiTest.createLeakedInt(input); Object longValue = RedisValueResolver.valueFromPointer(ptr); @@ -98,8 +98,8 @@ public void redisValueToJavaValue_Map() { long[] values = {1L, 2L, 3L}; long ptr = FfiTest.createLeakedMap(keys, values); Object mapValue = RedisValueResolver.valueFromPointer(ptr); - assertTrue(mapValue instanceof HashMap); - HashMap result = (HashMap) mapValue; + assertTrue(mapValue instanceof HashMap); + HashMap result = (HashMap) mapValue; assertAll( () -> assertEquals(1L, result.get(12L)), () -> assertEquals(2L, result.get(14L)), @@ -134,8 +134,8 @@ public void redisValueToJavaValue_Set() { long[] array = {1L, 2L, 2L}; long ptr = FfiTest.createLeakedLongSet(array); Object longSetValue = RedisValueResolver.valueFromPointer(ptr); - assertTrue(longSetValue instanceof HashSet); - HashSet result = (HashSet) longSetValue; + assertTrue(longSetValue instanceof HashSet); + HashSet result = (HashSet) longSetValue; assertAll( () -> assertTrue(result.contains(1L)), () -> assertTrue(result.contains(2L)), diff --git a/java/client/src/test/java/glide/managers/ConnectionManagerTest.java b/java/client/src/test/java/glide/managers/ConnectionManagerTest.java index b04dd5b312..79389fcde1 100644 --- a/java/client/src/test/java/glide/managers/ConnectionManagerTest.java +++ b/java/client/src/test/java/glide/managers/ConnectionManagerTest.java @@ -40,21 +40,21 @@ public class ConnectionManagerTest { ChannelHandler channel; - private static String HOST = "aws.com"; - private static int PORT = 9999; + private static final String HOST = "aws.com"; + private static final int PORT = 9999; - private static String USERNAME = "JohnDoe"; - private static String PASSWORD = "Password1"; + private static final String USERNAME = "JohnDoe"; + private static final String PASSWORD = "Password1"; - private static int NUM_OF_RETRIES = 5; - private static int FACTOR = 10; - private static int EXPONENT_BASE = 50; + private static final int NUM_OF_RETRIES = 5; + private static final int FACTOR = 10; + private static final int EXPONENT_BASE = 50; - private static int DATABASE_ID = 1; + private static final int DATABASE_ID = 1; - private static int REQUEST_TIMEOUT = 3; + private static final int REQUEST_TIMEOUT = 3; - private static String CLIENT_NAME = "ClientName"; + private static final String CLIENT_NAME = "ClientName"; @BeforeEach public void setUp() { diff --git a/java/client/src/test/java/glide/utils/RustCoreMock.java b/java/client/src/test/java/glide/utils/RustCoreMock.java index 8ef787948e..b9bc53bae6 100644 --- a/java/client/src/test/java/glide/utils/RustCoreMock.java +++ b/java/client/src/test/java/glide/utils/RustCoreMock.java @@ -21,6 +21,7 @@ import java.nio.file.Files; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import redis_request.RedisRequestOuterClass.RedisRequest; @@ -67,13 +68,6 @@ public static Response.Builder OK() { } } - public abstract static class GlideMockConnectAll extends GlideMockProtobuf { - @Override - public Response connection(ConnectionRequest request) { - return Response.newBuilder().build(); - } - } - /** Thread pool supplied to Netty to perform all async IO. */ private final EventLoopGroup group; @@ -113,7 +107,7 @@ private RustCoreMock() { new ChannelInitializer() { @Override - protected void initChannel(DomainSocketChannel ch) throws Exception { + protected void initChannel(@NonNull DomainSocketChannel ch) { ch.pipeline() // https://netty.io/4.1/api/io/netty/handler/codec/protobuf/ProtobufEncoder.html .addLast("frameDecoder", new ProtobufVarint32FrameDecoder()) @@ -155,7 +149,8 @@ private class UdsServer extends ChannelInboundHandlerAdapter { private final AtomicBoolean anybodyConnected = new AtomicBoolean(false); @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + public void channelRead(@NonNull ChannelHandlerContext ctx, @NonNull Object msg) + throws Exception { var buf = (ByteBuf) msg; var bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); @@ -165,7 +160,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception return; } var handler = (GlideMockProtobuf) messageProcessor; - Response response = null; + Response response; if (!anybodyConnected.get()) { var connection = ConnectionRequest.parseFrom(bytes); response = handler.connection(connection); @@ -180,7 +175,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception } @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); failed.setPlain(true); diff --git a/java/integTest/src/test/java/glide/SharedClientTests.java b/java/integTest/src/test/java/glide/SharedClientTests.java index fa343db818..e5d11b2546 100644 --- a/java/integTest/src/test/java/glide/SharedClientTests.java +++ b/java/integTest/src/test/java/glide/SharedClientTests.java @@ -88,7 +88,8 @@ private static Stream clientAndDataSize() { @MethodSource("clientAndDataSize") public void client_can_handle_concurrent_workload(BaseClient client, int valueSize) { ExecutorService executorService = Executors.newCachedThreadPool(); - CompletableFuture[] futures = new CompletableFuture[100]; + @SuppressWarnings("unchecked") + CompletableFuture[] futures = new CompletableFuture[100]; for (int i = 0; i < 100; i++) { futures[i] = diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index fc419988b4..ad11ca3322 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -51,6 +51,7 @@ import lombok.SneakyThrows; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -686,6 +687,27 @@ public void lpush_lpop_lrange_type_error(BaseClient client) { assertTrue(lrangeException.getCause() instanceof RequestException); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void lindex(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String[] valueArray = new String[] {"value1", "value2"}; + + assertEquals(2, client.lpush(key1, valueArray).get()); + assertEquals(valueArray[1], client.lindex(key1, 0).get()); + assertEquals(valueArray[0], client.lindex(key1, -1).get()); + assertNull(client.lindex(key1, 3).get()); + assertNull(client.lindex(key2, 3).get()); + + // Key exists, but it is not a List + assertEquals(OK, client.set(key2, "value").get()); + Exception executionException = + assertThrows(ExecutionException.class, () -> client.lindex(key2, 0).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + @SneakyThrows @ParameterizedTest @MethodSource("getClients") @@ -1108,7 +1130,7 @@ public void zadd_and_zaddIncr_with_NX_XX(BaseClient client) { assertEquals(0, client.zadd(key, membersScores, onlyIfExistsOptions).get()); assertEquals(3, client.zadd(key, membersScores, onlyIfDoesNotExistOptions).get()); - assertEquals(null, client.zaddIncr(key, "one", 5, onlyIfDoesNotExistOptions).get()); + assertNull(client.zaddIncr(key, "one", 5, onlyIfDoesNotExistOptions).get()); assertEquals(6, client.zaddIncr(key, "one", 5, onlyIfExistsOptions).get()); } @@ -1137,25 +1159,24 @@ public void zadd_and_zaddIncr_with_GT_LT(BaseClient client) { assertEquals(1, client.zadd(key, membersScores, scoreGreaterThanOptions, true).get()); assertEquals(0, client.zadd(key, membersScores, scoreLessThanOptions, true).get()); assertEquals(7, client.zaddIncr(key, "one", -3, scoreLessThanOptions).get()); - assertEquals(null, client.zaddIncr(key, "one", -3, scoreGreaterThanOptions).get()); + assertNull(client.zaddIncr(key, "one", -3, scoreGreaterThanOptions).get()); } - @SneakyThrows - @ParameterizedTest - @MethodSource("getClients") - public void zadd_illegal_arguments(BaseClient client) { + // TODO move to another class + @Test + public void zadd_illegal_arguments() { ZaddOptions existsGreaterThanOptions = ZaddOptions.builder() .conditionalChange(ZaddOptions.ConditionalChange.ONLY_IF_DOES_NOT_EXIST) .updateOptions(ZaddOptions.UpdateOptions.SCORE_GREATER_THAN_CURRENT) .build(); - assertThrows(IllegalArgumentException.class, () -> existsGreaterThanOptions.toArgs()); + assertThrows(IllegalArgumentException.class, existsGreaterThanOptions::toArgs); ZaddOptions existsLessThanOptions = ZaddOptions.builder() .conditionalChange(ZaddOptions.ConditionalChange.ONLY_IF_DOES_NOT_EXIST) .updateOptions(ZaddOptions.UpdateOptions.SCORE_LESS_THAN_CURRENT) .build(); - assertThrows(IllegalArgumentException.class, () -> existsLessThanOptions.toArgs()); + assertThrows(IllegalArgumentException.class, existsLessThanOptions::toArgs); ZaddOptions options = ZaddOptions.builder() .conditionalChange(ZaddOptions.ConditionalChange.ONLY_IF_DOES_NOT_EXIST) @@ -1296,6 +1317,33 @@ public void zrank(BaseClient client) { assertTrue(executionException.getCause() instanceof RequestException); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void zmscore(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + Map membersScores = Map.of("one", 1.0, "two", 2.0, "three", 3.0); + assertEquals(3, client.zadd(key1, membersScores).get()); + assertArrayEquals( + new Double[] {1.0, 2.0, 3.0}, + client.zmscore(key1, new String[] {"one", "two", "three"}).get()); + assertArrayEquals( + new Double[] {1.0, null, null, 3.0}, + client + .zmscore(key1, new String[] {"one", "nonExistentMember", "nonExistentMember", "three"}) + .get()); + assertArrayEquals( + new Double[] {null}, client.zmscore("nonExistentKey", new String[] {"one"}).get()); + + // Key exists, but it is not a set + assertEquals(OK, client.set(key2, "bar").get()); + ExecutionException executionException = + assertThrows( + ExecutionException.class, () -> client.zmscore(key2, new String[] {"one"}).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 987a59aa3d..d55008eb8a 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -80,6 +80,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.lpush(key5, new String[] {value1, value1, value2, value3, value3}); baseTransaction.llen(key5); + baseTransaction.lindex(key5, 0); baseTransaction.lrem(key5, 1, value1); baseTransaction.ltrim(key5, 1, -1); baseTransaction.lrange(key5, 0, -2); @@ -101,6 +102,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.zaddIncr(key8, "one", 3); baseTransaction.zrem(key8, new String[] {"one"}); baseTransaction.zcard(key8); + baseTransaction.zmscore(key8, new String[] {"two", "three"}); baseTransaction.zrange(key8, new RangeByIndex(0, 1)); baseTransaction.zrangeWithScores(key8, new RangeByIndex(0, 1)); baseTransaction.zscore(key8, "two"); @@ -172,6 +174,7 @@ public static Object[] transactionTestResult() { 10.5, 5L, 5L, + value3, // lindex(key5, 0) 1L, OK, new String[] {value3, value2}, @@ -190,6 +193,7 @@ public static Object[] transactionTestResult() { 4.0, 1L, 2L, + new Double[] {2.0, 3.0}, // zmscore(key8, new String[] {"two", "three"}) new String[] {"two", "three"}, // zrange Map.of("two", 2.0, "three", 3.0), // zrangeWithScores 2.0, // zscore(key8, "two") diff --git a/java/src/lib.rs b/java/src/lib.rs index 9730b73cc4..eb81b165f1 100644 --- a/java/src/lib.rs +++ b/java/src/lib.rs @@ -45,13 +45,15 @@ fn redis_value_to_java<'local>(env: &mut JNIEnv<'local>, val: Value) -> JObject< items.into() } Value::Map(map) => { - let hashmap = env.new_object("java/util/HashMap", "()V", &[]).unwrap(); + let linked_hash_map = env + .new_object("java/util/LinkedHashMap", "()V", &[]) + .unwrap(); for (key, value) in map { let java_key = redis_value_to_java(env, key); let java_value = redis_value_to_java(env, value); env.call_method( - &hashmap, + &linked_hash_map, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", &[(&java_key).into(), (&java_value).into()], @@ -59,7 +61,7 @@ fn redis_value_to_java<'local>(env: &mut JNIEnv<'local>, val: Value) -> JObject< .unwrap(); } - hashmap + linked_hash_map } Value::Double(float) => env .new_object("java/lang/Double", "(D)V", &[float.into()]) diff --git a/node/DEVELOPER.md b/node/DEVELOPER.md index a6d019eea3..dfbe4b4172 100644 --- a/node/DEVELOPER.md +++ b/node/DEVELOPER.md @@ -54,17 +54,17 @@ source "$HOME/.cargo/env" Before starting this step, make sure you've installed all software requirments. -1. Clone the repository: +1. Clone the repository: ```bash VERSION=0.1.0 # You can modify this to other released version or set it to "main" to get the unstable branch git clone --branch ${VERSION} https://github.com/aws/glide-for-redis.git cd glide-for-redis ``` -2. Initialize git submodule: +2. Initialize git submodule: ```bash git submodule update --init --recursive ``` -3. Install all node dependencies: +3. Install all node dependencies: ```bash cd node npm i @@ -72,35 +72,36 @@ Before starting this step, make sure you've installed all software requirments. npm i cd .. ``` -4. Build the Node wrapper: - Choose a build option from the following and run it from the `node` folder: +4. Build the Node wrapper (Choose a build option from the following and run it from the `node` folder): - 1. Build in release mode, stripped from all debug symbols (optimized and minimized binary size): + 1. Build in release mode, stripped from all debug symbols (optimized and minimized binary size): - ```bash - npm run build:release - ``` + ```bash + npm run build:release + ``` - 2. Build in release mode with debug symbols (optimized but large binary size): + 2. Build in release mode with debug symbols (optimized but large binary size): - ```bash - npm run build:benchmark - ``` + ```bash + npm run build:benchmark + ``` - 3. For testing purposes, you can execute an unoptimized but fast build using: - `bash + 3. For testing purposes, you can execute an unoptimized but fast build using: + ```bash npm run build - ` Once building completed, you'll find the compiled JavaScript code in the`./build-ts` folder. + ``` + + Once building completed, you'll find the compiled JavaScript code in the`./build-ts` folder. -5. Run tests: +5. Run tests: 1. Ensure that you have installed redis-server and redis-cli on your host. You can find the Redis installation guide at the following link: [Redis Installation Guide](https://redis.io/docs/install/install-redis/install-redis-on-linux/). 2. Execute the following command from the node folder: ```bash npm test ``` -6. Integrating the built GLIDE package into your project: - Add the package to your project using the folder path with the command `npm install /node`. +6. Integrating the built GLIDE package into your project: + Add the package to your project using the folder path with the command `npm install /node`. - For a fast build, execute `npm run build`. This will perform a full, unoptimized build, which is suitable for developing tests. Keep in mind that performance is significantly affected in an unoptimized build, so it's required to build with the `build:release` or `build:benchmark` option when measuring performance. - If your modifications are limited to the TypeScript code, run `npm run build-external` to build the external package without rebuilding the internal package. diff --git a/node/THIRD_PARTY_LICENSES_NODE b/node/THIRD_PARTY_LICENSES_NODE index 8214fa4251..1bfa615b32 100644 --- a/node/THIRD_PARTY_LICENSES_NODE +++ b/node/THIRD_PARTY_LICENSES_NODE @@ -3503,7 +3503,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bumpalo:3.15.4 +Package: bumpalo:3.16.0 The following copyrights and licenses were found in the source code of this package: @@ -10092,7 +10092,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: getrandom:0.2.13 +Package: getrandom:0.2.14 The following copyrights and licenses were found in the source code of this package: @@ -36652,7 +36652,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -Package: @types:node:20.12.5 +Package: @types:node:20.12.7 The following copyrights and licenses were found in the source code of this package: diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index c7b7854694..220f8c3e57 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -12,7 +12,10 @@ import * as net from "net"; import { Buffer, BufferWriter, Reader, Writer } from "protobufjs"; import { ExpireOptions, - ScoreLimit, + RangeByIndex, + RangeByLex, + RangeByScore, + ScoreBoundary, SetOptions, StreamAddOptions, StreamReadOptions, @@ -75,6 +78,8 @@ import { createZcount, createZpopmax, createZpopmin, + createZrange, + createZrangeWithScores, createZrank, createZrem, createZremRangeByRank, @@ -1242,6 +1247,20 @@ export class BaseClient { * @param key - The key of the set. * @returns the value of the popped member. * If `key` does not exist, null will be returned. + * + * @example + * ```typescript + * // Example usage of spop method to remove and return a random member from a set + * const result = await client.spop("my_set"); + * console.log(result); // Output: 'member1' - Removes and returns a random member from the set "my_set". + * ``` + * + * @example + * ```typescript + * // Example usage of spop method with non-existing key + * const result = await client.spop("non_existing_key"); + * console.log(result); // Output: null + * ``` */ public spop(key: string): Promise { return this.createWritePromise(createSPop(key)); @@ -1254,6 +1273,18 @@ export class BaseClient { * @param count - The count of the elements to pop from the set. * @returns A list of popped elements will be returned depending on the set's length. * If `key` does not exist, empty list will be returned. + * + * @example + * // Example usage of spopCount method to remove and return multiple random members from a set + * const result = await client.spopCount("my_set", 2); + * console.log(result); // Output: ['member2', 'member3'] - Removes and returns 2 random members from the set "my_set". + * + * @example + * ```typescript + * // Example usage of spopCount method with non-existing key + * const result = await client.spopCount("non_existing_key"); + * console.log(result); // Output: [] + * ``` */ public spopCount(key: string, count: number): Promise { return this.createWritePromise(createSPop(key, count)); @@ -1669,12 +1700,94 @@ export class BaseClient { */ public zcount( key: string, - minScore: ScoreLimit, - maxScore: ScoreLimit, + minScore: ScoreBoundary, + maxScore: ScoreBoundary, ): Promise { return this.createWritePromise(createZcount(key, minScore, maxScore)); } + /** Returns the specified range of elements in the sorted set stored at `key`. + * ZRANGE can perform different types of range queries: by index (rank), by the score, or by lexicographical order. + * + * See https://redis.io/commands/zrange/ for more details. + * To get the elements with their scores, see `zrangeWithScores`. + * + * @param key - The key of the sorted set. + * @param rangeQuery - The range query object representing the type of range query to perform. + * For range queries by index (rank), use RangeByIndex. + * For range queries by lexicographical order, use RangeByLex. + * For range queries by score, use RangeByScore. + * @param reverse - If true, reverses the sorted set, with index 0 as the element with the highest score. + * @returns A list of elements within the specified range. + * If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty array. + * + * @example + * ```typescript + * // Example usage of zrange method to retrieve all members of a sorted set in ascending order + * const result = await client.zrange("my_sorted_set", { start: 0, stop: -1 }); + * console.log(result1); // Output: ['member1', 'member2', 'member3'] - Returns all members in ascending order. + * + * @example + * // Example usage of zrange method to retrieve members within a score range in ascending order + * const result = await client.zrange("my_sorted_set", { + * start: "negativeInfinity", + * stop: { value: 3, isInclusive: false }, + * type: "byScore", + * }); + * console.log(result); // Output: ['member2', 'member3'] - Returns members with scores within the range of negative infinity to 3, in ascending order. + * ``` + */ + public zrange( + key: string, + rangeQuery: RangeByScore | RangeByLex | RangeByIndex, + reverse: boolean = false, + ): Promise { + return this.createWritePromise(createZrange(key, rangeQuery, reverse)); + } + + /** Returns the specified range of elements with their scores in the sorted set stored at `key`. + * Similar to ZRANGE but with a WITHSCORE flag. + * See https://redis.io/commands/zrange/ for more details. + * + * @param key - The key of the sorted set. + * @param rangeQuery - The range query object representing the type of range query to perform. + * For range queries by index (rank), use RangeByIndex. + * For range queries by lexicographical order, use RangeByLex. + * For range queries by score, use RangeByScore. + * @param reverse - If true, reverses the sorted set, with index 0 as the element with the highest score. + * @returns A map of elements and their scores within the specified range. + * If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty map. + * + * @example + * ```typescript + * // Example usage of zrangeWithScores method to retrieve members within a score range with their scores + * const result = await client.zrangeWithScores("my_sorted_set", { + * start: { value: 10, isInclusive: false }, + * stop: { value: 20, isInclusive: false }, + * type: "byScore", + * }); + * console.log(result); // Output: {'member1': 10.5, 'member2': 15.2} - Returns members with scores between 10 and 20 with their scores. + * + * @example + * // Example usage of zrangeWithScores method to retrieve members within a score range with their scores + * const result = await client.zrangeWithScores("my_sorted_set", { + * start: "negativeInfinity", + * stop: { value: 3, isInclusive: false }, + * type: "byScore", + * }); + * console.log(result); // Output: {'member4': -2.0, 'member7': 1.5} - Returns members with scores within the range of negative infinity to 3, with their scores. + * ``` + */ + public zrangeWithScores( + key: string, + rangeQuery: RangeByScore | RangeByLex | RangeByIndex, + reverse: boolean = false, + ): Promise> { + return this.createWritePromise( + createZrangeWithScores(key, rangeQuery, reverse), + ); + } + /** Returns the length of the string value stored at `key`. * See https://redis.io/commands/strlen/ for more details. * @@ -1876,8 +1989,8 @@ export class BaseClient { */ public zremRangeByScore( key: string, - minScore: ScoreLimit, - maxScore: ScoreLimit, + minScore: ScoreBoundary, + maxScore: ScoreBoundary, ): Promise { return this.createWritePromise( createZremRangeByScore(key, minScore, maxScore), diff --git a/node/src/Commands.ts b/node/src/Commands.ts index bbdb756b49..47f88c69eb 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -811,42 +811,181 @@ export function createZscore( return createCommand(RequestType.ZScore, [key, member]); } -export type ScoreLimit = +export type ScoreBoundary = + /** + * Positive infinity bound for sorted set. + */ | `positiveInfinity` + /** + * Negative infinity bound for sorted set. + */ | `negativeInfinity` + /** + * Represents a specific numeric score boundary in a sorted set. + */ | { - bound: number; + /** + * The score value. + */ + value: T; + /** + * Whether the score value is inclusive. Defaults to True. + */ isInclusive?: boolean; }; -function getScoreLimitArg(score: ScoreLimit): string { +/** + * Represents a range by index (rank) in a sorted set. + * The `start` and `stop` arguments represent zero-based indexes. + */ +export type RangeByIndex = { + /** + * The start index of the range. + */ + start: number; + /** + * The stop index of the range. + */ + stop: number; +}; + +/** + * Represents a range by score or a range by lex in a sorted set. + * The `start` and `stop` arguments represent score boundaries. + */ +type SortedSetRange = { + /** + * The start boundary. + */ + start: ScoreBoundary; + /** + * The stop boundary. + */ + stop: ScoreBoundary; + /** + * The limit argument for a range query. + * Represents a limit argument for a range query in a sorted set to + * be used in [ZRANGE](https://redis.io/commands/zrange) command. + * + * The optional LIMIT argument can be used to obtain a sub-range from the matching elements + * (similar to SELECT LIMIT offset, count in SQL). + */ + limit?: { + /** + * The offset from the start of the range. + */ + offset: number; + /** + * The number of elements to include in the range. + * A negative count returns all elements from the offset. + */ + count: number; + }; +}; + +export type RangeByScore = SortedSetRange & { type: "byScore" }; +export type RangeByLex = SortedSetRange & { type: "byLex" }; + +/** + * Returns a string representation of a score boundary in Redis protocol format. + * @param score - The score boundary object containing value and inclusivity information. + * @param isLex - Indicates whether to return lexical representation for positive/negative infinity. + * @returns A string representation of the score boundary in Redis protocol format. + */ +function getScoreBoundaryArg( + score: ScoreBoundary | ScoreBoundary, + isLex: boolean = false, +): string { if (score == "positiveInfinity") { - return "+inf"; + return isLex ? "+" : "+inf"; } else if (score == "negativeInfinity") { - return "-inf"; + return isLex ? "-" : "-inf"; + } + + if (score.isInclusive == false) { + return "(" + score.value.toString(); } - const value = - score.isInclusive == false - ? "(" + score.bound.toString() - : score.bound.toString(); + const value = isLex ? "[" + score.value.toString() : score.value.toString(); return value; } +function createZrangeArgs( + key: string, + rangeQuery: RangeByScore | RangeByLex | RangeByIndex, + reverse: boolean, + withScores: boolean, +): string[] { + const args: string[] = [key]; + + if (typeof rangeQuery.start != "number") { + rangeQuery = rangeQuery as RangeByScore | RangeByLex; + const isLex = rangeQuery.type == "byLex"; + args.push(getScoreBoundaryArg(rangeQuery.start, isLex)); + args.push(getScoreBoundaryArg(rangeQuery.stop, isLex)); + args.push(isLex == true ? "BYLEX" : "BYSCORE"); + } else { + args.push(rangeQuery.start.toString()); + args.push(rangeQuery.stop.toString()); + } + + if (reverse) { + args.push("REV"); + } + + if ("limit" in rangeQuery && rangeQuery.limit !== undefined) { + args.push( + "LIMIT", + String(rangeQuery.limit.offset), + String(rangeQuery.limit.count), + ); + } + + if (withScores) { + args.push("WITHSCORES"); + } + + return args; +} + /** * @internal */ export function createZcount( key: string, - minScore: ScoreLimit, - maxScore: ScoreLimit, + minScore: ScoreBoundary, + maxScore: ScoreBoundary, ): redis_request.Command { const args = [key]; - args.push(getScoreLimitArg(minScore)); - args.push(getScoreLimitArg(maxScore)); + args.push(getScoreBoundaryArg(minScore)); + args.push(getScoreBoundaryArg(maxScore)); return createCommand(RequestType.Zcount, args); } +/** + * @internal + */ +export function createZrange( + key: string, + rangeQuery: RangeByIndex | RangeByScore | RangeByLex, + reverse: boolean = false, +): redis_request.Command { + const args = createZrangeArgs(key, rangeQuery, reverse, false); + return createCommand(RequestType.Zrange, args); +} + +/** + * @internal + */ +export function createZrangeWithScores( + key: string, + rangeQuery: RangeByIndex | RangeByScore | RangeByLex, + reverse: boolean = false, +): redis_request.Command { + const args = createZrangeArgs(key, rangeQuery, reverse, true); + return createCommand(RequestType.Zrange, args); +} + /** * @internal */ @@ -927,12 +1066,12 @@ export function createZremRangeByRank( */ export function createZremRangeByScore( key: string, - minScore: ScoreLimit, - maxScore: ScoreLimit, + minScore: ScoreBoundary, + maxScore: ScoreBoundary, ): redis_request.Command { const args = [key]; - args.push(getScoreLimitArg(minScore)); - args.push(getScoreLimitArg(maxScore)); + args.push(getScoreBoundaryArg(minScore)); + args.push(getScoreBoundaryArg(maxScore)); return createCommand(RequestType.ZRemRangeByScore, args); } diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index c6b90c912a..b2fbdef317 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -5,7 +5,10 @@ import { ExpireOptions, InfoOptions, - ScoreLimit, + RangeByIndex, + RangeByLex, + RangeByScore, + ScoreBoundary, SetOptions, StreamAddOptions, StreamReadOptions, @@ -80,6 +83,8 @@ import { createZcount, createZpopmax, createZpopmin, + createZrange, + createZrangeWithScores, createZrank, createZrem, createZremRangeByRank, @@ -925,10 +930,62 @@ export class BaseTransaction> { * If `key` does not exist, it is treated as an empty sorted set, and the command returns 0. * If `minScore` is greater than `maxScore`, 0 is returned. */ - public zcount(key: string, minScore: ScoreLimit, maxScore: ScoreLimit): T { + public zcount( + key: string, + minScore: ScoreBoundary, + maxScore: ScoreBoundary, + ): T { return this.addAndReturn(createZcount(key, minScore, maxScore)); } + /** Returns the specified range of elements in the sorted set stored at `key`. + * ZRANGE can perform different types of range queries: by index (rank), by the score, or by lexicographical order. + * + * See https://redis.io/commands/zrange/ for more details. + * To get the elements with their scores, see `zrangeWithScores`. + * + * @param key - The key of the sorted set. + * @param rangeQuery - The range query object representing the type of range query to perform. + * For range queries by index (rank), use RangeByIndex. + * For range queries by lexicographical order, use RangeByLex. + * For range queries by score, use RangeByScore. + * @param reverse - If true, reverses the sorted set, with index 0 as the element with the highest score. + * + * Command Response - A list of elements within the specified range. + * If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty array. + */ + public zrange( + key: string, + rangeQuery: RangeByScore | RangeByLex | RangeByIndex, + reverse: boolean = false, + ): T { + return this.addAndReturn(createZrange(key, rangeQuery, reverse)); + } + + /** Returns the specified range of elements with their scores in the sorted set stored at `key`. + * Similar to ZRANGE but with a WITHSCORE flag. + * See https://redis.io/commands/zrange/ for more details. + * + * @param key - The key of the sorted set. + * @param rangeQuery - The range query object representing the type of range query to perform. + * For range queries by index (rank), use RangeByIndex. + * For range queries by lexicographical order, use RangeByLex. + * For range queries by score, use RangeByScore. + * @param reverse - If true, reverses the sorted set, with index 0 as the element with the highest score. + * + * Command Response - A map of elements and their scores within the specified range. + * If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty map. + */ + public zrangeWithScores( + key: string, + rangeQuery: RangeByScore | RangeByLex | RangeByIndex, + reverse: boolean = false, + ): T { + return this.addAndReturn( + createZrangeWithScores(key, rangeQuery, reverse), + ); + } + /** Returns the string representation of the type of the value stored at `key`. * See https://redis.io/commands/type/ for more details. * @@ -1037,8 +1094,8 @@ export class BaseTransaction> { */ public zremRangeByScore( key: string, - minScore: ScoreLimit, - maxScore: ScoreLimit, + minScore: ScoreBoundary, + maxScore: ScoreBoundary, ): T { return this.addAndReturn( createZremRangeByScore(key, minScore, maxScore), diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 8047052dd8..eedb43a015 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1550,25 +1550,25 @@ export function runBaseTests(config: { expect( await client.zcount( key1, - { bound: 1, isInclusive: false }, - { bound: 3, isInclusive: false }, + { value: 1, isInclusive: false }, + { value: 3, isInclusive: false }, ), ).toEqual(1); expect( await client.zcount( key1, - { bound: 1, isInclusive: false }, - { bound: 3 }, + { value: 1, isInclusive: false }, + { value: 3 }, ), ).toEqual(2); expect( await client.zcount(key1, "negativeInfinity", { - bound: 3, + value: 3, }), ).toEqual(3); expect( await client.zcount(key1, "positiveInfinity", { - bound: 3, + value: 3, }), ).toEqual(0); expect( @@ -1588,6 +1588,217 @@ export function runBaseTests(config: { config.timeout, ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrange by index test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + + expect(await client.zrange(key, { start: 0, stop: 1 })).toEqual( + ["one", "two"], + ); + expect( + await client.zrangeWithScores(key, { start: 0, stop: -1 }), + ).toEqual({ one: 1.0, two: 2.0, three: 3.0 }); + expect( + await client.zrange(key, { start: 0, stop: 1 }, true), + ).toEqual(["three", "two"]); + expect(await client.zrange(key, { start: 3, stop: 1 })).toEqual( + [], + ); + expect( + await client.zrangeWithScores(key, { start: 3, stop: 1 }), + ).toEqual({}); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrange by score test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const membersScores = { one: 1, two: 2, three: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + + expect( + await client.zrange(key, { + start: "negativeInfinity", + stop: { value: 3, isInclusive: false }, + type: "byScore", + }), + ).toEqual(["one", "two"]); + + expect( + await client.zrangeWithScores(key, { + start: "negativeInfinity", + stop: "positiveInfinity", + type: "byScore", + }), + ).toEqual({ one: 1.0, two: 2.0, three: 3.0 }); + + expect( + await client.zrange( + key, + { + start: { value: 3, isInclusive: false }, + stop: "negativeInfinity", + type: "byScore", + }, + true, + ), + ).toEqual(["two", "one"]); + + expect( + await client.zrange(key, { + start: "negativeInfinity", + stop: "positiveInfinity", + limit: { offset: 1, count: 2 }, + type: "byScore", + }), + ).toEqual(["two", "three"]); + + expect( + await client.zrange( + key, + { + start: "negativeInfinity", + stop: { value: 3, isInclusive: false }, + type: "byScore", + }, + true, + ), + ).toEqual([]); + + expect( + await client.zrange(key, { + start: "positiveInfinity", + stop: { value: 3, isInclusive: false }, + type: "byScore", + }), + ).toEqual([]); + + expect( + await client.zrangeWithScores( + key, + { + start: "negativeInfinity", + stop: { value: 3, isInclusive: false }, + type: "byScore", + }, + true, + ), + ).toEqual({}); + + expect( + await client.zrangeWithScores(key, { + start: "positiveInfinity", + stop: { value: 3, isInclusive: false }, + type: "byScore", + }), + ).toEqual({}); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrange by lex test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + const membersScores = { a: 1, b: 2, c: 3 }; + expect(await client.zadd(key, membersScores)).toEqual(3); + + expect( + await client.zrange(key, { + start: "negativeInfinity", + stop: { value: "c", isInclusive: false }, + type: "byLex", + }), + ).toEqual(["a", "b"]); + + expect( + await client.zrange(key, { + start: "negativeInfinity", + stop: "positiveInfinity", + limit: { offset: 1, count: 2 }, + type: "byLex", + }), + ).toEqual(["b", "c"]); + + expect( + await client.zrange( + key, + { + start: { value: "c", isInclusive: false }, + stop: "negativeInfinity", + type: "byLex", + }, + true, + ), + ).toEqual(["b", "a"]); + + expect( + await client.zrange( + key, + { + start: "negativeInfinity", + stop: { value: "c", isInclusive: false }, + type: "byLex", + }, + true, + ), + ).toEqual([]); + + expect( + await client.zrange(key, { + start: "positiveInfinity", + stop: { value: "c", isInclusive: false }, + type: "byLex", + }), + ).toEqual([]); + }, protocol); + }, + config.timeout, + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `zrange different typesn of keys test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + expect( + await client.zrange("nonExistingKey", { + start: 0, + stop: 1, + }), + ).toEqual([]); + + expect( + await client.zrangeWithScores("nonExistingKey", { + start: 0, + stop: 1, + }), + ).toEqual({}); + + expect(await client.set(key, "value")).toEqual("OK"); + + await expect( + client.zrange(key, { start: 0, stop: 1 }), + ).rejects.toThrow(); + + await expect( + client.zrangeWithScores(key, { start: 0, stop: 1 }), + ).rejects.toThrow(); + }, protocol); + }, + config.timeout, + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `type test_%p`, async (protocol) => { @@ -1956,15 +2167,15 @@ export function runBaseTests(config: { expect( await client.zremRangeByScore( key, - { bound: 1, isInclusive: false }, - { bound: 2 }, + { value: 1, isInclusive: false }, + { value: 2 }, ), ).toEqual(1); expect( await client.zremRangeByScore( key, - { bound: 1 }, + { value: 1 }, "negativeInfinity", ), ).toEqual(0); diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index 02b279b98a..7288ee541f 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -172,7 +172,11 @@ export async function transactionTest( args.push(4); baseTransaction.zscore(key8, "member2"); args.push(3.0); - baseTransaction.zcount(key8, { bound: 2 }, "positiveInfinity"); + baseTransaction.zrange(key8, { start: 0, stop: -1 }); + args.push(["member2", "member3", "member4", "member5"]); + baseTransaction.zrangeWithScores(key8, { start: 0, stop: -1 }); + args.push({ member2: 3, member3: 3.5, member4: 4, member5: 5 }); + baseTransaction.zcount(key8, { value: 2 }, "positiveInfinity"); args.push(4); baseTransaction.zpopmin(key8); args.push({ member2: 3.0 }); diff --git a/python/THIRD_PARTY_LICENSES_PYTHON b/python/THIRD_PARTY_LICENSES_PYTHON index 23dff965f8..8be06cc158 100644 --- a/python/THIRD_PARTY_LICENSES_PYTHON +++ b/python/THIRD_PARTY_LICENSES_PYTHON @@ -3451,7 +3451,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bumpalo:3.15.4 +Package: bumpalo:3.16.0 The following copyrights and licenses were found in the source code of this package: @@ -9734,7 +9734,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: getrandom:0.2.13 +Package: getrandom:0.2.14 The following copyrights and licenses were found in the source code of this package: @@ -40490,7 +40490,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: types-protobuf:4.24.0.20240311 +Package: types-protobuf:4.24.0.20240408 The following copyrights and licenses were found in the source code of this package: diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 7bc02edf76..64d28fcb0c 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -294,6 +294,25 @@ async def strlen(self, key: str) -> int: """ return cast(int, await self._execute_command(RequestType.Strlen, [key])) + async def rename(self, key: str, new_key: str) -> TOK: + """ + Renames `key` to `new_key`. + If `newkey` already exists it is overwritten. + In Cluster mode, both `key` and `newkey` must be in the same hash slot, + meaning that in practice only keys that have the same hash tag can be reliably renamed in cluster. + See https://redis.io/commands/rename/ for more details. + + Args: + key (str) : The key to rename. + new_key (str) : The new name of the key. + + Returns: + OK: If the `key` was successfully renamed, return "OK". If `key` does not exist, an error is thrown. + """ + return cast( + TOK, await self._execute_command(RequestType.Rename, [key, new_key]) + ) + async def delete(self, keys: List[str]) -> int: """ Delete one or more keys from the database. A key is ignored if it does not exist. @@ -1702,7 +1721,7 @@ async def zrange_withscores( Examples: >>> await client.zrange_withscores("my_sorted_set", RangeByScore(ScoreBoundary(10), ScoreBoundary(20))) {'member1': 10.5, 'member2': 15.2} # Returns members with scores between 10 and 20 with their scores. - >>> await client.zrange("my_sorted_set", RangeByScore(start=InfBound.NEG_INF, stop=ScoreBoundary(3))) + >>> await client.zrange_withscores("my_sorted_set", RangeByScore(start=InfBound.NEG_INF, stop=ScoreBoundary(3))) {'member4': -2.0, 'member7': 1.5} # Returns members with with scores within the range of negative infinity to 3, with their scores. """ args = _create_zrange_args(key, range_query, reverse, with_scores=True) diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 233c584c0c..6cce687157 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -128,6 +128,23 @@ def strlen(self: TTransaction, key: str) -> TTransaction: """ return self.append_command(RequestType.Strlen, [key]) + def rename(self: TTransaction, key: str, new_key: str) -> TTransaction: + """ + Renames `key` to `new_key`. + If `newkey` already exists it is overwritten. + In Cluster mode, both `key` and `newkey` must be in the same hash slot, + meaning that in practice only keys that have the same hash tag can be reliably renamed in cluster. + See https://redis.io/commands/rename/ for more details. + + Args: + key (str) : The key to rename. + new_key (str) : The new name of the key. + + Command response: + OK: If the `key` was successfully renamed, return "OK". If `key` does not exist, the transaction fails with an error. + """ + return self.append_command(RequestType.Rename, [key, new_key]) + def custom_command(self: TTransaction, command_args: List[str]) -> TTransaction: """ Executes a single command, without checking inputs. diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 7d5131b201..3ab9a00d86 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -994,6 +994,19 @@ async def test_strlen(self, redis_client: TRedisClient): with pytest.raises(RequestError): assert await redis_client.strlen(key2) + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_rename(self, redis_client: TRedisClient): + key1 = "{" + get_random_string(10) + "}" + assert await redis_client.set(key1, "foo") == OK + assert await redis_client.rename(key1, key1 + "_rename") == OK + assert await redis_client.exists([key1 + "_rename"]) == 1 + + with pytest.raises(RequestError): + assert await redis_client.rename( + "{same_slot}" + "non_existing_key", "{same_slot}" + "_rename" + ) + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_exists(self, redis_client: TRedisClient): diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 2828bdd620..84b01639bc 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -53,12 +53,15 @@ async def transaction_test( transaction.persist(key) args.append(False) - transaction.exists([key]) + transaction.rename(key, key2) + args.append(OK) + + transaction.exists([key2]) args.append(1) - transaction.delete([key]) + transaction.delete([key2]) args.append(1) - transaction.get(key) + transaction.get(key2) args.append(None) transaction.mset({key: value, key2: value2})