From 44ef9a9b4d236fb6e0e9c7ec7bdf4b353a68e229 Mon Sep 17 00:00:00 2001 From: Shachar Langbeheim <98546660+shachlanAmazon@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:21:00 +0200 Subject: [PATCH 01/65] Add initial stream commands to protobuf. (#998) Co-authored-by: nihohit --- glide-core/src/protobuf/redis_request.proto | 7 +++++++ glide-core/src/socket_listener.rs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 06380f511c..0389ce82d3 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -116,6 +116,13 @@ enum RequestType { Strlen = 72; Lindex = 73; ZPopMax = 74; + XRead = 75; + XAdd = 76; + XReadGroup = 77; + XAck = 78; + XTrim = 79; + XGroupCreate = 80; + XGroupDestroy = 81; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 06879e8e91..d925c80172 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -353,6 +353,13 @@ fn get_command(request: &Command) -> Option { RequestType::Strlen => Some(cmd("STRLEN")), RequestType::Lindex => Some(cmd("LINDEX")), RequestType::ZPopMax => Some(cmd("ZPOPMAX")), + RequestType::XAck => Some(cmd("XACK")), + RequestType::XAdd => Some(cmd("XADD")), + RequestType::XReadGroup => Some(cmd("XREADGROUP")), + RequestType::XRead => Some(cmd("XREAD")), + RequestType::XGroupCreate => Some(get_two_word_command("XGROUP", "CREATE")), + RequestType::XGroupDestroy => Some(get_two_word_command("XGROUP", "DESTROY")), + RequestType::XTrim => Some(cmd("XTRIM")), } } From b556002e7456256c605a235a9b48a7d528a9388e Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Thu, 22 Feb 2024 10:20:52 -0800 Subject: [PATCH 02/65] Bump `arduino/setup-protoc` version in CI (#1018) * Bump `arduino/setup-protoc` version. Signed-off-by: Yury-Fridlyand --- .github/workflows/csharp.yml | 2 +- .github/workflows/install-shared-dependencies/action.yml | 2 +- .github/workflows/lint-rust/action.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index dafb45bf25..c32120d93a 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -43,7 +43,7 @@ jobs: redis-version: ${{ matrix.redis }} - name: Install protoc (protobuf) - uses: arduino/setup-protoc@v2.1.0 + uses: arduino/setup-protoc@v3 with: version: "25.1" diff --git a/.github/workflows/install-shared-dependencies/action.yml b/.github/workflows/install-shared-dependencies/action.yml index c7e93e69a4..2174952692 100644 --- a/.github/workflows/install-shared-dependencies/action.yml +++ b/.github/workflows/install-shared-dependencies/action.yml @@ -52,7 +52,7 @@ runs: targets: ${{ inputs.target }} - name: Install protoc (protobuf) - uses: arduino/setup-protoc@v2.1.0 + uses: arduino/setup-protoc@v3 with: version: "25.1" repo-token: ${{ inputs.github-token }} diff --git a/.github/workflows/lint-rust/action.yml b/.github/workflows/lint-rust/action.yml index ef62499b29..e1cbad7d14 100644 --- a/.github/workflows/lint-rust/action.yml +++ b/.github/workflows/lint-rust/action.yml @@ -18,7 +18,7 @@ runs: - uses: Swatinem/rust-cache@v2 - name: Install protoc (protobuf) - uses: arduino/setup-protoc@v2.1.0 + uses: arduino/setup-protoc@v3 with: version: "25.1" From e264963428bb982b5b0fdef823426490a66e5111 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:01:55 -0800 Subject: [PATCH 03/65] Java: Added Hmget command for BaseClient and BaseTransaction (Hash Commands) (#1015) * Java: Added Hmget command for BaseClient and BaseTransaction (Hash Commands) * Fixed documentation styling. * Minor updates based on PR comments. --- .../src/main/java/glide/api/BaseClient.java | 8 ++++++ .../java/glide/api/commands/HashCommands.java | 19 ++++++++++++++ .../glide/api/models/BaseTransaction.java | 20 ++++++++++++++ .../test/java/glide/api/RedisClientTest.java | 26 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 4 +++ .../test/java/glide/SharedCommandTests.java | 20 ++++++++++++++ .../java/glide/TransactionTestUtilities.java | 2 ++ 7 files changed, 99 insertions(+) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index f0a30e30d2..6094fe30bc 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -11,6 +11,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; +import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; @@ -297,6 +298,13 @@ public CompletableFuture hdel(@NonNull String key, @NonNull String[] field return commandManager.submitNewCommand(HashDel, args, this::handleLongResponse); } + @Override + public CompletableFuture hmget(@NonNull String key, @NonNull String[] fields) { + String[] arguments = ArrayUtils.addFirst(fields, key); + return commandManager.submitNewCommand( + HashMGet, arguments, response -> castArray(handleArrayResponse(response), String.class)); + } + @Override public CompletableFuture sadd(String key, String[] members) { String[] arguments = ArrayUtils.addFirst(members, key); diff --git a/java/client/src/main/java/glide/api/commands/HashCommands.java b/java/client/src/main/java/glide/api/commands/HashCommands.java index 22837b5531..dd9b0085c9 100644 --- a/java/client/src/main/java/glide/api/commands/HashCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashCommands.java @@ -45,4 +45,23 @@ public interface HashCommands { * If key does not exist, it is treated as an empty hash and it returns 0.
*/ CompletableFuture hdel(String key, String[] fields); + + /** + * Returns the values associated with the specified fields in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param fields The fields in the hash stored at key to retrieve from the database. + * @return An array of values associated with the given fields, in the same order as they are + * requested.
+ * For every field that does not exist in the hash, a null value is returned.
+ * If key does not exist, it is treated as an empty hash, and it returns an array + * of null values.
+ * @example + *
+     * String[] values = client.hmget("my_hash", new String[] {"field1", "field2"}).get()
+     * assert values == new String[] {"value1", "value2"}
+     * 
+ */ + CompletableFuture hmget(String key, String[] fields); } diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index 734456e25b..e7151ea3ac 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -10,6 +10,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; +import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; @@ -362,6 +363,25 @@ public T hdel(@NonNull String key, @NonNull String[] fields) { return getThis(); } + /** + * Returns the values associated with the specified fields in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param fields The fields in the hash stored at key to retrieve from the database. + * @return Command Response - An array of values associated with the given fields, in the same + * order as they are requested.
+ * For every field that does not exist in the hash, a null value is returned.
+ * If key does not exist, it is treated as an empty hash, and it returns an array + * of null values.
+ */ + public T hmget(@NonNull String key, @NonNull String[] fields) { + ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(fields, key)); + + protobufTransaction.addCommands(buildCommand(HashMGet, commandArgs)); + return getThis(); + } + /** * Add specified members to the set stored at key. Specified members that are already * a member of this set are ignored. diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 64e2a75f78..8a1086dc39 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -20,6 +20,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; +import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; @@ -568,6 +569,31 @@ public void hdel_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void hmget_success() { + // setup + String key = "testKey"; + String[] fields = {"testField1", "testField2"}; + String[] args = {"testKey", "testField1", "testField2"}; + String[] value = {"testValue1", "testValue2"}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(HashMGet), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.hmget(key, fields); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void sadd_returns_success() { diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index c8e193ce59..58d7fc7a96 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -10,6 +10,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; +import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; @@ -91,6 +92,9 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.mget(new String[] {"key"}); results.add(Pair.of(MGet, ArgsArray.newBuilder().addArgs("key").build())); + transaction.hmget("key", new String[] {"field"}); + results.add(Pair.of(HashMGet, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + transaction.incr("key"); results.add(Pair.of(Incr, ArgsArray.newBuilder().addArgs("key").build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 1ac025ebe3..bc8eceb8cd 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -424,6 +424,26 @@ public void hdel_multiple_existing_fields_non_existing_field_non_existing_key(Ba assertEquals(0, client.hdel("non_existing_key", new String[] {field3}).get()); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void hmget_multiple_existing_fields_non_existing_field_non_existing_key( + BaseClient client) { + String key = UUID.randomUUID().toString(); + String field1 = UUID.randomUUID().toString(); + String field2 = UUID.randomUUID().toString(); + String value = UUID.randomUUID().toString(); + Map fieldValueMap = Map.of(field1, value, field2, value); + + assertEquals(2, client.hset(key, fieldValueMap).get()); + assertArrayEquals( + new String[] {value, null, value}, + client.hmget(key, new String[] {field1, "non_existing_field", field2}).get()); + assertArrayEquals( + new String[] {null, null}, + client.hmget("non_existing_key", new String[] {field1, field2}).get()); + } + @SneakyThrows @ParameterizedTest @MethodSource("getClients") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index d8e1dd8d1a..e194c9b364 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -44,6 +44,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.hset(key4, Map.of(field1, value1, field2, value2)); baseTransaction.hget(key4, field1); + baseTransaction.hmget(key4, new String[] {field1, "non_existing_field", field2}); baseTransaction.hdel(key4, new String[] {field1}); baseTransaction.sadd(key5, new String[] {"baz", "foo"}); @@ -72,6 +73,7 @@ public static Object[] transactionTestResult() { 0.5, 2L, value1, + new String[] {value1, null, value2}, 1L, 2L, 1L, From e71c8e66e0ec7ac7ea89dfc95ce584277a415e0e Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:28:18 -0800 Subject: [PATCH 04/65] Java: Added Hexists command for BaseClient and BaseTransaction (Hash Commands) (#1016) * Java: Added Hexists command for BaseClient and BaseTransaction (Hash Commands) * Added examples to documentation. * Minor fixes based on PR review. * Minor updates based on PR comments. * Minor update to TransactionTestUtilities. --- .../src/main/java/glide/api/BaseClient.java | 11 ++++++++ .../java/glide/api/commands/HashCommands.java | 18 +++++++++++++ .../glide/api/models/BaseTransaction.java | 18 +++++++++++++ .../test/java/glide/api/RedisClientTest.java | 26 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 5 ++++ .../test/java/glide/SharedCommandTests.java | 16 ++++++++++++ .../java/glide/TransactionTestUtilities.java | 2 ++ 7 files changed, 96 insertions(+) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 6094fe30bc..537ba0e662 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -10,6 +10,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Exists; import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; +import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; @@ -174,6 +175,10 @@ protected String handleStringOrNullResponse(Response response) throws RedisExcep return handleRedisResponse(String.class, true, response); } + protected Boolean handleBooleanResponse(Response response) throws RedisException { + return handleRedisResponse(Boolean.class, false, response); + } + protected Long handleLongResponse(Response response) throws RedisException { return handleRedisResponse(Long.class, false, response); } @@ -305,6 +310,12 @@ public CompletableFuture hmget(@NonNull String key, @NonNull String[] HashMGet, arguments, response -> castArray(handleArrayResponse(response), String.class)); } + @Override + public CompletableFuture hexists(@NonNull String key, @NonNull String field) { + return commandManager.submitNewCommand( + HashExists, new String[] {key, field}, this::handleBooleanResponse); + } + @Override public CompletableFuture sadd(String key, String[] members) { String[] arguments = ArrayUtils.addFirst(members, key); diff --git a/java/client/src/main/java/glide/api/commands/HashCommands.java b/java/client/src/main/java/glide/api/commands/HashCommands.java index dd9b0085c9..f430e85b28 100644 --- a/java/client/src/main/java/glide/api/commands/HashCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashCommands.java @@ -64,4 +64,22 @@ public interface HashCommands { * */ CompletableFuture hmget(String key, String[] fields); + + /** + * Returns if field is an existing field in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param field The field to check in the hash stored at key. + * @return True if the hash contains the specified field. If the hash does not + * contain the field, or if the key does not exist, it returns False. + * @example + *
+     * Boolean exists = client.hexists("my_hash", "field1").get()
+     * assert exists
+     * Boolean exists = client.hexists("my_hash", "non_existent_field").get()
+     * assert !exists
+     * 
+ */ + CompletableFuture hexists(String key, String field); } diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index e7151ea3ac..6e5c79510f 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -9,6 +9,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Exists; import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; +import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; @@ -382,6 +383,23 @@ public T hmget(@NonNull String key, @NonNull String[] fields) { return getThis(); } + /** + * Returns if field is an existing field in the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param field The field to check in the hash stored at key. + * @return Command Response - True if the hash contains the specified field. If the + * hash does not contain the field, or if the key does not exist, it returns False + * . + */ + public T hexists(@NonNull String key, @NonNull String field) { + ArgsArray commandArgs = buildArgs(key, field); + + protobufTransaction.addCommands(buildCommand(HashExists, commandArgs)); + return getThis(); + } + /** * Add specified members to the set stored at key. Specified members that are already * a member of this set are ignored. diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 8a1086dc39..55947bb0a4 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -19,6 +19,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Exists; import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; +import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; @@ -594,6 +595,31 @@ public void hmget_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void hexists_success() { + // setup + String key = "testKey"; + String field = "testField"; + String[] args = new String[] {key, field}; + Boolean value = true; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(HashExists), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.hexists(key, field); + Boolean payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void sadd_returns_success() { diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index 58d7fc7a96..d4172c37d1 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -9,6 +9,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Exists; import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; +import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; @@ -122,6 +123,10 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.hdel("key", new String[] {"field"}); results.add(Pair.of(HashDel, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + transaction.hexists("key", "field"); + results.add( + Pair.of(HashExists, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + transaction.sadd("key", new String[] {"value"}); results.add(Pair.of(SAdd, ArgsArray.newBuilder().addArgs("key").addArgs("value").build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index bc8eceb8cd..e688b78696 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -10,6 +10,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -444,6 +445,21 @@ public void hmget_multiple_existing_fields_non_existing_field_non_existing_key( client.hmget("non_existing_key", new String[] {field1, field2}).get()); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void hexists_existing_field_non_existing_field_non_existing_key(BaseClient client) { + String key = UUID.randomUUID().toString(); + String field1 = UUID.randomUUID().toString(); + String field2 = UUID.randomUUID().toString(); + Map fieldValueMap = Map.of(field1, "value1", field2, "value1"); + + assertEquals(2, client.hset(key, fieldValueMap).get()); + assertTrue(client.hexists(key, field1).get()); + assertFalse(client.hexists(key, "non_existing_field").get()); + assertFalse(client.hexists("non_existing_key", field2).get()); + } + @SneakyThrows @ParameterizedTest @MethodSource("getClients") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index e194c9b364..fc0ac386d1 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -44,6 +44,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.hset(key4, Map.of(field1, value1, field2, value2)); baseTransaction.hget(key4, field1); + baseTransaction.hexists(key4, field2); baseTransaction.hmget(key4, new String[] {field1, "non_existing_field", field2}); baseTransaction.hdel(key4, new String[] {field1}); @@ -73,6 +74,7 @@ public static Object[] transactionTestResult() { 0.5, 2L, value1, + true, new String[] {value1, null, value2}, 1L, 2L, From def506b5dc483f90da48afc54df799923e777ffb Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:56:36 -0800 Subject: [PATCH 05/65] Java: Added Hgetall command for BaseClient and BaseTransaction (Hash Commands) (#1017) * Java: Added Hgetall command for BaseClient and BaseTransaction (Hash Commands) * Minor fixes based on PR review. * Minor updates based on PR comments. * Spotless. --- .../src/main/java/glide/api/BaseClient.java | 6 +++++ .../java/glide/api/commands/HashCommands.java | 16 +++++++++++ .../glide/api/models/BaseTransaction.java | 17 ++++++++++++ .../test/java/glide/api/RedisClientTest.java | 27 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 10 ++++--- .../test/java/glide/SharedCommandTests.java | 15 +++++++++++ .../java/glide/TransactionTestUtilities.java | 2 ++ 7 files changed, 90 insertions(+), 3 deletions(-) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 537ba0e662..4ca713f866 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -12,6 +12,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; +import static redis_request.RedisRequestOuterClass.RequestType.HashGetAll; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; @@ -316,6 +317,11 @@ public CompletableFuture hexists(@NonNull String key, @NonNull String f HashExists, new String[] {key, field}, this::handleBooleanResponse); } + @Override + public CompletableFuture> hgetall(@NonNull String key) { + return commandManager.submitNewCommand(HashGetAll, new String[] {key}, this::handleMapResponse); + } + @Override public CompletableFuture sadd(String key, String[] members) { String[] arguments = ArrayUtils.addFirst(members, key); diff --git a/java/client/src/main/java/glide/api/commands/HashCommands.java b/java/client/src/main/java/glide/api/commands/HashCommands.java index f430e85b28..f4312aae57 100644 --- a/java/client/src/main/java/glide/api/commands/HashCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashCommands.java @@ -82,4 +82,20 @@ public interface HashCommands { * */ CompletableFuture hexists(String key, String field); + + /** + * Returns all fields and values of the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @return A Map of fields and their values stored in the hash. Every field name in + * the map is associated with its corresponding value.
+ * If key does not exist, it returns an empty map. + * @example + *
+     * Map fieldValueMap = client.hgetall("my_hash").get()
+     * assert fieldValueMap.equals(Map.of(field1", "value1", "field2", "value2"))
+     * 
+ */ + CompletableFuture> hgetall(String key); } diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index 6e5c79510f..8fb40d1daf 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -11,6 +11,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; +import static redis_request.RedisRequestOuterClass.RequestType.HashGetAll; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; @@ -400,6 +401,22 @@ public T hexists(@NonNull String key, @NonNull String field) { return getThis(); } + /** + * Returns all fields and values of the hash stored at key. + * + * @see redis.io for details. + * @param key The key of the hash. + * @return Command Response - A Map of fields and their values stored in the hash. + * Every field name in the map is associated with its corresponding value.
+ * If key does not exist, it returns an empty map. + */ + public T hgetall(@NonNull String key) { + ArgsArray commandArgs = buildArgs(key); + + protobufTransaction.addCommands(buildCommand(HashGetAll, commandArgs)); + return getThis(); + } + /** * Add specified members to the set stored at key. Specified members that are already * a member of this set are ignored. diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 55947bb0a4..0b82e90867 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -21,6 +21,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; +import static redis_request.RedisRequestOuterClass.RequestType.HashGetAll; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; @@ -620,6 +621,32 @@ public void hexists_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void hgetall_success() { + // setup + String key = "testKey"; + String[] args = new String[] {key}; + Map value = new LinkedHashMap<>(); + value.put("key1", "field1"); + value.put("key2", "field2"); + + CompletableFuture> testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(value); + + // match on protobuf request + when(commandManager.>submitNewCommand(eq(HashGetAll), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture> response = service.hgetall(key); + Map payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void sadd_returns_success() { diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index d4172c37d1..c65d37f4b8 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -11,6 +11,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; +import static redis_request.RedisRequestOuterClass.RequestType.HashGetAll; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; @@ -93,9 +94,6 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.mget(new String[] {"key"}); results.add(Pair.of(MGet, ArgsArray.newBuilder().addArgs("key").build())); - transaction.hmget("key", new String[] {"field"}); - results.add(Pair.of(HashMGet, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); - transaction.incr("key"); results.add(Pair.of(Incr, ArgsArray.newBuilder().addArgs("key").build())); @@ -123,10 +121,16 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.hdel("key", new String[] {"field"}); results.add(Pair.of(HashDel, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + transaction.hmget("key", new String[] {"field"}); + results.add(Pair.of(HashMGet, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + transaction.hexists("key", "field"); results.add( Pair.of(HashExists, ArgsArray.newBuilder().addArgs("key").addArgs("field").build())); + transaction.hgetall("key"); + results.add(Pair.of(HashGetAll, ArgsArray.newBuilder().addArgs("key").build())); + transaction.sadd("key", new String[] {"value"}); results.add(Pair.of(SAdd, ArgsArray.newBuilder().addArgs("key").addArgs("value").build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index e688b78696..9e3bc1dc16 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -460,6 +460,21 @@ public void hexists_existing_field_non_existing_field_non_existing_key(BaseClien assertFalse(client.hexists("non_existing_key", field2).get()); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void hgetall_multiple_existing_fields_existing_key_non_existing_key(BaseClient client) { + String key = UUID.randomUUID().toString(); + String field1 = UUID.randomUUID().toString(); + String field2 = UUID.randomUUID().toString(); + String value = UUID.randomUUID().toString(); + Map fieldValueMap = Map.of(field1, value, field2, value); + + assertEquals(2, client.hset(key, fieldValueMap).get()); + assertEquals(fieldValueMap, client.hgetall(key).get()); + assertEquals(Map.of(), client.hgetall("non_existing_key").get()); + } + @SneakyThrows @ParameterizedTest @MethodSource("getClients") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index fc0ac386d1..3c063ba586 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -46,6 +46,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.hget(key4, field1); baseTransaction.hexists(key4, field2); baseTransaction.hmget(key4, new String[] {field1, "non_existing_field", field2}); + baseTransaction.hgetall(key4); baseTransaction.hdel(key4, new String[] {field1}); baseTransaction.sadd(key5, new String[] {"baz", "foo"}); @@ -76,6 +77,7 @@ public static Object[] transactionTestResult() { value1, true, new String[] {value1, null, value2}, + Map.of(field1, value1, field2, value2), 1L, 2L, 1L, From 736e636e2adce542321bcb848055f09d428dcf0f Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:25:33 -0800 Subject: [PATCH 06/65] Java: Added Hincrby and HincrbyFloat BaseClient and BaseTransaction (Hash Commands) (#1012) * Java: Added Hincrby and Hincrby BaseClient and BaseTransaction. (Hash Commands) * Added examples to documentation. Removed clusterTransactionTest file. * Minor updates based on PR comments. * Minor updates based on PR comments. * Spotless. --- .../src/main/java/glide/api/BaseClient.java | 17 ++++++ .../java/glide/api/commands/HashCommands.java | 45 ++++++++++++++++ .../glide/api/models/BaseTransaction.java | 47 ++++++++++++++++ .../test/java/glide/api/RedisClientTest.java | 54 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 14 +++++ .../test/java/glide/SharedCommandTests.java | 52 ++++++++++++++++++ .../java/glide/TransactionTestUtilities.java | 6 +++ 7 files changed, 235 insertions(+) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 4ca713f866..cc3fede22d 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -13,6 +13,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; import static redis_request.RedisRequestOuterClass.RequestType.HashGetAll; +import static redis_request.RedisRequestOuterClass.RequestType.HashIncrBy; +import static redis_request.RedisRequestOuterClass.RequestType.HashIncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; @@ -322,6 +324,21 @@ public CompletableFuture> hgetall(@NonNull String key) { return commandManager.submitNewCommand(HashGetAll, new String[] {key}, this::handleMapResponse); } + @Override + public CompletableFuture hincrBy(@NonNull String key, @NonNull String field, long amount) { + return commandManager.submitNewCommand( + HashIncrBy, new String[] {key, field, Long.toString(amount)}, this::handleLongResponse); + } + + @Override + public CompletableFuture hincrByFloat( + @NonNull String key, @NonNull String field, double amount) { + return commandManager.submitNewCommand( + HashIncrByFloat, + new String[] {key, field, Double.toString(amount)}, + this::handleDoubleResponse); + } + @Override public CompletableFuture sadd(String key, String[] members) { String[] arguments = ArrayUtils.addFirst(members, key); diff --git a/java/client/src/main/java/glide/api/commands/HashCommands.java b/java/client/src/main/java/glide/api/commands/HashCommands.java index f4312aae57..5ffe906882 100644 --- a/java/client/src/main/java/glide/api/commands/HashCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashCommands.java @@ -98,4 +98,49 @@ public interface HashCommands { * */ CompletableFuture> hgetall(String key); + + /** + * Increments the number stored at field in the hash stored at key by + * increment. By using a negative increment value, the value stored at field in the + * hash stored at key is decremented. If field or key does + * not exist, it is set to 0 before performing the operation. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param field The field in the hash stored at key to increment or decrement its + * value. + * @param amount The amount by which to increment or decrement the field's value. Use a negative + * value to decrement. + * @return The value of field in the hash stored at key after the + * increment or decrement. + * @example + *
+     * Long num = client.hincrBy("my_hash", "field1", 5).get()
+     * assert num == 5L
+     * 
+ */ + CompletableFuture hincrBy(String key, String field, long amount); + + /** + * Increment the string representing a floating point number stored at field in the + * hash stored at key by increment. By using a negative increment value, the value + * stored at field in the hash stored at key is decremented. If + * field or key does not exist, it is set to 0 before performing the + * operation. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param field The field in the hash stored at key to increment or decrement its + * value. + * @param amount The amount by which to increment or decrement the field's value. Use a negative + * value to decrement. + * @returns The value of field in the hash stored at key after the + * increment or decrement. + * @example + *
+     * Double num = client.hincrByFloat("my_hash", "field1", 2.5).get()
+     * assert num == 2.5
+     * 
+ */ + CompletableFuture hincrByFloat(String key, String field, double amount); } 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 8fb40d1daf..cae14e5099 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -12,6 +12,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; import static redis_request.RedisRequestOuterClass.RequestType.HashGetAll; +import static redis_request.RedisRequestOuterClass.RequestType.HashIncrBy; +import static redis_request.RedisRequestOuterClass.RequestType.HashIncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; @@ -417,6 +419,51 @@ public T hgetall(@NonNull String key) { return getThis(); } + /** + * Increments the number stored at field in the hash stored at key by + * increment. By using a negative increment value, the value stored at field in the + * hash stored at key is decremented. If field or key does + * not exist, it is set to 0 before performing the operation. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param field The field in the hash stored at key to increment or decrement its + * value. + * @param amount The amount by which to increment or decrement the field's value. Use a negative + * value to decrement. + * @return Command Response - The value of field in the hash stored at key + * after the increment or decrement. + */ + public T hincrBy(@NonNull String key, @NonNull String field, long amount) { + ArgsArray commandArgs = buildArgs(key, field, Long.toString(amount)); + + protobufTransaction.addCommands(buildCommand(HashIncrBy, commandArgs)); + return getThis(); + } + + /** + * Increment the string representing a floating point number stored at field in the + * hash stored at key by increment. By using a negative increment value, the value + * stored at field in the hash stored at key is decremented. If + * field or key does not exist, it is set to 0 before performing the + * operation. + * + * @see redis.io for details. + * @param key The key of the hash. + * @param field The field in the hash stored at key to increment or decrement its + * value. + * @param amount The amount by which to increment or decrement the field's value. Use a negative + * value to decrement. + * @returns Command Response - The value of field in the hash stored at key + * after the increment or decrement. + */ + public T hincrByFloat(@NonNull String key, @NonNull String field, double amount) { + ArgsArray commandArgs = buildArgs(key, field, Double.toString(amount)); + + protobufTransaction.addCommands(buildCommand(HashIncrByFloat, commandArgs)); + return getThis(); + } + /** * Add specified members to the set stored at key. Specified members that are already * a member of this set are ignored. diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 0b82e90867..9b0f7c49d9 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -22,6 +22,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; import static redis_request.RedisRequestOuterClass.RequestType.HashGetAll; +import static redis_request.RedisRequestOuterClass.RequestType.HashIncrBy; +import static redis_request.RedisRequestOuterClass.RequestType.HashIncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; @@ -647,6 +649,58 @@ public void hgetall_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void hincrBy_returns_success() { + // setup + String key = "testKey"; + String field = "field"; + long amount = 1L; + Long value = 10L; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(value); + + // match on protobuf request + when(commandManager.submitNewCommand( + eq(HashIncrBy), eq(new String[] {key, field, Long.toString(amount)}), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.hincrBy(key, field, amount); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void hincrByFloat_returns_success() { + // setup + String key = "testKey"; + String field = "field"; + double amount = 1.0; + Double value = 10.0; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(value); + + // match on protobuf request + when(commandManager.submitNewCommand( + eq(HashIncrByFloat), eq(new String[] {key, field, Double.toString(amount)}), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.hincrByFloat(key, field, amount); + Double payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void sadd_returns_success() { diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index c65d37f4b8..6ec7fbcd7b 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -12,6 +12,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.HashExists; import static redis_request.RedisRequestOuterClass.RequestType.HashGet; import static redis_request.RedisRequestOuterClass.RequestType.HashGetAll; +import static redis_request.RedisRequestOuterClass.RequestType.HashIncrBy; +import static redis_request.RedisRequestOuterClass.RequestType.HashIncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.HashMGet; import static redis_request.RedisRequestOuterClass.RequestType.HashSet; import static redis_request.RedisRequestOuterClass.RequestType.Incr; @@ -131,6 +133,18 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.hgetall("key"); results.add(Pair.of(HashGetAll, ArgsArray.newBuilder().addArgs("key").build())); + transaction.hincrBy("key", "field", 1); + results.add( + Pair.of( + HashIncrBy, + ArgsArray.newBuilder().addArgs("key").addArgs("field").addArgs("1").build())); + + transaction.hincrByFloat("key", "field", 1.5); + results.add( + Pair.of( + HashIncrByFloat, + ArgsArray.newBuilder().addArgs("key").addArgs("field").addArgs("1.5").build())); + transaction.sadd("key", new String[] {"value"}); results.add(Pair.of(SAdd, ArgsArray.newBuilder().addArgs("key").addArgs("value").build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 9e3bc1dc16..270104d7be 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -475,6 +475,58 @@ public void hgetall_multiple_existing_fields_existing_key_non_existing_key(BaseC assertEquals(Map.of(), client.hgetall("non_existing_key").get()); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void hincrBy_hincrByFloat_commands_existing_key_existing_field(BaseClient client) { + String key = UUID.randomUUID().toString(); + String field = UUID.randomUUID().toString(); + Map fieldValueMap = Map.of(field, "10"); + + assertEquals(1, client.hset(key, fieldValueMap).get()); + + assertEquals(11, client.hincrBy(key, field, 1).get()); + assertEquals(15, client.hincrBy(key, field, 4).get()); + assertEquals(16.5, client.hincrByFloat(key, field, 1.5).get()); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void hincrBy_hincrByFloat_commands_non_existing_key_non_existing_field(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String field = UUID.randomUUID().toString(); + Map fieldValueMap = Map.of(field, "10"); + + assertEquals(1, client.hincrBy("non_existing_key_1", field, 1).get()); + assertEquals(1, client.hset(key1, fieldValueMap).get()); + assertEquals(2, client.hincrBy(key1, "non_existing_field_1", 2).get()); + + assertEquals(0.5, client.hincrByFloat("non_existing_key_2", field, 0.5).get()); + assertEquals(1, client.hset(key2, fieldValueMap).get()); + assertEquals(-0.5, client.hincrByFloat(key1, "non_existing_field_2", -0.5).get()); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void hincrBy_hincrByFloat_type_error(BaseClient client) { + String key = UUID.randomUUID().toString(); + String field = UUID.randomUUID().toString(); + Map fieldValueMap = Map.of(field, "foo"); + + assertEquals(1, client.hset(key, fieldValueMap).get()); + + Exception hincrByException = + assertThrows(ExecutionException.class, () -> client.hincrBy(key, field, 2).get()); + assertTrue(hincrByException.getCause() instanceof RequestException); + + Exception hincrByFloatException = + assertThrows(ExecutionException.class, () -> client.hincrByFloat(key, field, 2.5).get()); + assertTrue(hincrByFloatException.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 3c063ba586..ddcd29bb2b 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -17,6 +17,7 @@ public class TransactionTestUtilities { private static final String value2 = UUID.randomUUID().toString(); private static final String field1 = UUID.randomUUID().toString(); private static final String field2 = UUID.randomUUID().toString(); + private static final String field3 = UUID.randomUUID().toString(); public static BaseTransaction transactionTest(BaseTransaction baseTransaction) { @@ -49,6 +50,9 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.hgetall(key4); baseTransaction.hdel(key4, new String[] {field1}); + baseTransaction.hincrBy(key4, field3, 5); + baseTransaction.hincrByFloat(key4, field3, 5.5); + baseTransaction.sadd(key5, new String[] {"baz", "foo"}); baseTransaction.srem(key5, new String[] {"foo"}); baseTransaction.scard(key5); @@ -79,6 +83,8 @@ public static Object[] transactionTestResult() { new String[] {value1, null, value2}, Map.of(field1, value1, field2, value2), 1L, + 5L, + 10.5, 2L, 1L, 1L, From 7c1527774a5cf62a5f5d4a2030d0ff8d336116c1 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Thu, 22 Feb 2024 13:58:31 -0800 Subject: [PATCH 07/65] Fixed Exec return type for cluster client. Now returns Array or Null. (#1019) * Fixed Exec return type for cluster client. Now returns Array or Null. * Added test for when watch transaction failure returns null. --- .../main/java/glide/api/RedisClusterClient.java | 4 ++-- .../glide/cluster/ClusterTransactionTests.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/java/client/src/main/java/glide/api/RedisClusterClient.java b/java/client/src/main/java/glide/api/RedisClusterClient.java index cfdf1bd072..b288d7b70f 100644 --- a/java/client/src/main/java/glide/api/RedisClusterClient.java +++ b/java/client/src/main/java/glide/api/RedisClusterClient.java @@ -71,14 +71,14 @@ protected ClusterValue handleCustomCommandResponse(Route route, Response @Override public CompletableFuture exec(ClusterTransaction transaction) { return commandManager.submitNewCommand( - transaction, Optional.empty(), this::handleArrayResponse); + transaction, Optional.empty(), this::handleArrayOrNullResponse); } @Override public CompletableFuture[]> exec( ClusterTransaction transaction, Route route) { return commandManager - .submitNewCommand(transaction, Optional.ofNullable(route), this::handleArrayResponse) + .submitNewCommand(transaction, Optional.ofNullable(route), this::handleArrayOrNullResponse) .thenApply( objects -> Arrays.stream(objects) diff --git a/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java b/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java index 4eab55caec..6028d39178 100644 --- a/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java +++ b/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java @@ -3,8 +3,11 @@ import static glide.TransactionTestUtilities.transactionTest; import static glide.TransactionTestUtilities.transactionTestResult; +import static glide.api.BaseClient.OK; import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleRoute.RANDOM; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import glide.TestConfiguration; @@ -50,6 +53,17 @@ public void custom_command_info() { assertTrue(((String) result[0]).contains("# Stats")); } + @Test + @SneakyThrows + public void WATCH_transaction_failure_returns_null() { + ClusterTransaction transaction = new ClusterTransaction(); + transaction.get("key"); + assertEquals( + OK, clusterClient.customCommand(new String[] {"WATCH", "key"}).get().getSingleValue()); + assertEquals(OK, clusterClient.set("key", "foo").get()); + assertNull(clusterClient.exec(transaction).get()); + } + @Test @SneakyThrows public void info_simple_route_test() { From d2d6b2f7fc9e8814f666646dbc1a2bda9f2e55e6 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Thu, 22 Feb 2024 14:48:13 -0800 Subject: [PATCH 08/65] Java: Add `UNLINK()` command (#976) * Java: Add Unlink command back after merge Signed-off-by: Andrew Carbonetto * Doc fixes Signed-off-by: Andrew Carbonetto * Update unlink for review comments Signed-off-by: Andrew Carbonetto * Fix merge conflicts Signed-off-by: Andrew Carbonetto * Minor documentation update to redisClientTest. * Added unlink test to transactions. --------- Signed-off-by: Andrew Carbonetto Co-authored-by: SanHalacogluImproving --- .../src/main/java/glide/api/BaseClient.java | 6 ++++ .../api/commands/GenericBaseCommands.java | 18 ++++++++++++ .../glide/api/models/BaseTransaction.java | 18 ++++++++++++ .../test/java/glide/api/RedisClientTest.java | 23 +++++++++++++++ .../glide/api/models/TransactionTests.java | 4 +++ .../test/java/glide/SharedCommandTests.java | 28 +++++++++++++++++++ .../java/glide/TransactionTestUtilities.java | 8 ++++++ 7 files changed, 105 insertions(+) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index cc3fede22d..b24ac3ab96 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -28,6 +28,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.SMembers; import static redis_request.RedisRequestOuterClass.RequestType.SRem; import static redis_request.RedisRequestOuterClass.RequestType.SetString; +import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import glide.api.commands.ConnectionManagementCommands; import glide.api.commands.GenericBaseCommands; @@ -365,4 +366,9 @@ public CompletableFuture scard(String key) { public CompletableFuture exists(@NonNull String[] keys) { return commandManager.submitNewCommand(Exists, keys, this::handleLongResponse); } + + @Override + public CompletableFuture unlink(@NonNull String[] keys) { + return commandManager.submitNewCommand(Unlink, keys, this::handleLongResponse); + } } diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index ae39900e45..13a5b05353 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -35,4 +35,22 @@ public interface GenericBaseCommands { * */ CompletableFuture exists(String[] keys); + + /** + * Unlink (delete) multiple keys from the database. A key is ignored if it does not + * exist. This command, similar to DEL, removes + * specified keys and ignores non-existent ones. However, this command does not block the server, + * while DEL does. + * + * @see redis.io for details. + * @param keys The list of keys to unlink. + * @return The number of keys that were unlinked. + * @example + *

+ *

+     * long result = client.unlink("my_key").get();
+     * assert result == 1L;
+     * 
+ */ + CompletableFuture unlink(String[] keys); } 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 cae14e5099..096000d458 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -28,6 +28,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.SMembers; import static redis_request.RedisRequestOuterClass.RequestType.SRem; import static redis_request.RedisRequestOuterClass.RequestType.SetString; +import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import glide.api.models.commands.InfoOptions; import glide.api.models.commands.InfoOptions.Section; @@ -547,6 +548,23 @@ public T exists(String[] keys) { return getThis(); } + /** + * Unlink (delete) multiple keys from the database. A key is ignored if it does not + * exist. This command, similar to DEL, removes specified keys and ignores non-existent ones. + * However, this command does not block the server, while DEL does. + * + * @see redis.io for details. + * @param keys The list of keys to unlink. + * @return Command Response - The number of keys that were unlinked. + */ + public T unlink(String[] keys) { + ArgsArray commandArgs = buildArgs(keys); + + protobufTransaction.addCommands(buildCommand(Unlink, commandArgs)); + return getThis(); + } + /** Build protobuf {@link Command} object for given command and arguments. */ protected Command buildCommand(RequestType requestType) { return buildCommand(requestType, buildArgs()); diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 9b0f7c49d9..e8e5f9f7b7 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -38,6 +38,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.SMembers; import static redis_request.RedisRequestOuterClass.RequestType.SRem; import static redis_request.RedisRequestOuterClass.RequestType.SetString; +import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import glide.api.models.commands.InfoOptions; import glide.api.models.commands.SetOptions; @@ -153,6 +154,28 @@ public void del_returns_long_success() { assertEquals(numberDeleted, result); } + @SneakyThrows + @Test + 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); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Unlink), eq(keys), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.unlink(keys); + Long result = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(numberUnlinked, result); + } + @SneakyThrows @Test public void get_returns_success() { diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index 6ec7fbcd7b..db21eef29c 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -28,6 +28,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.SMembers; import static redis_request.RedisRequestOuterClass.RequestType.SRem; import static redis_request.RedisRequestOuterClass.RequestType.SetString; +import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import glide.api.models.commands.InfoOptions; import glide.api.models.commands.SetOptions; @@ -75,6 +76,9 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.del(new String[] {"key1", "key2"}); results.add(Pair.of(Del, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").build())); + transaction.unlink(new String[] {"key1", "key2"}); + results.add(Pair.of(Unlink, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").build())); + transaction.ping(); results.add(Pair.of(Ping, ArgsArray.newBuilder().build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 270104d7be..e13e6bee89 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -93,6 +93,34 @@ public void ping_with_message(BaseClient client) { assertEquals("H3LL0", data); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void unlink_multiple_keys(BaseClient client) { + String key1 = "{key}" + UUID.randomUUID(); + String key2 = "{key}" + UUID.randomUUID(); + String key3 = "{key}" + UUID.randomUUID(); + String value = UUID.randomUUID().toString(); + + String setResult = client.set(key1, value).get(); + assertEquals(OK, setResult); + setResult = client.set(key2, value).get(); + assertEquals(OK, setResult); + setResult = client.set(key3, value).get(); + assertEquals(OK, setResult); + + Long unlinkedKeysNum = client.unlink(new String[] {key1, key2, key3}).get(); + assertEquals(3L, unlinkedKeysNum); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void unlink_non_existent_key(BaseClient client) { + Long unlinkedKeysNum = client.unlink(new String[] {UUID.randomUUID().toString()}).get(); + assertEquals(0L, unlinkedKeysNum); + } + @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 ddcd29bb2b..9785cfb162 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -32,6 +32,9 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.del(new String[] {key1}); baseTransaction.get(key1); + baseTransaction.unlink(new String[] {key2}); + baseTransaction.get(key2); + baseTransaction.mset(Map.of(key1, value2, key2, value1)); baseTransaction.mget(new String[] {key1, key2}); @@ -43,6 +46,8 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.incrByFloat(key3, 0.5); + baseTransaction.unlink(new String[] {key3}); + baseTransaction.hset(key4, Map.of(field1, value1, field2, value2)); baseTransaction.hget(key4, field1); baseTransaction.hexists(key4, field2); @@ -70,6 +75,8 @@ public static Object[] transactionTestResult() { 1L, 1L, null, + 1L, + null, "OK", new String[] {value2, value1}, 1L, @@ -77,6 +84,7 @@ public static Object[] transactionTestResult() { 2L, 0L, 0.5, + 1L, 2L, value1, true, From 8629d261deb4b7bf0a85ad998a5119208fccfd2e Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Thu, 22 Feb 2024 15:29:46 -0800 Subject: [PATCH 09/65] Java: Add `SELECT()` command for standalone (#984) * Java: Add `SELECT()` command for standalone (#97) * Java: Add SELECT() command for standalone Signed-off-by: Andrew Carbonetto * Fix IT tests Signed-off-by: Andrew Carbonetto --------- Signed-off-by: Andrew Carbonetto * Java: Add SADD, SREM, SMEMBERS, and SCARD commands (Set Commands) * Spotless; Doc update Signed-off-by: Andrew Carbonetto * Spotless Signed-off-by: Andrew Carbonetto * Doc fixes Signed-off-by: Andrew Carbonetto * Add tests for SELECT() Signed-off-by: Andrew Carbonetto * Spotless Signed-off-by: Andrew Carbonetto * Spotless. Added Standalone TransactionTests. --------- Signed-off-by: Andrew Carbonetto Co-authored-by: aaron-congo Co-authored-by: SanHalacogluImproving --- .../src/main/java/glide/api/RedisClient.java | 7 ++++ .../commands/ServerManagementCommands.java | 9 +++++ .../java/glide/api/models/Transaction.java | 17 +++++++++ .../test/java/glide/api/RedisClientTest.java | 23 ++++++++++++ .../models/StandaloneTransactionTests.java | 35 +++++++++++++++++++ java/integTest/build.gradle | 2 ++ .../java/glide/standalone/CommandTests.java | 29 +++++++++++++++ .../glide/standalone/TransactionTests.java | 14 ++++++++ 8 files changed, 136 insertions(+) create mode 100644 java/client/src/test/java/glide/api/models/StandaloneTransactionTests.java diff --git a/java/client/src/main/java/glide/api/RedisClient.java b/java/client/src/main/java/glide/api/RedisClient.java index 94af58bd42..76389b1d9b 100644 --- a/java/client/src/main/java/glide/api/RedisClient.java +++ b/java/client/src/main/java/glide/api/RedisClient.java @@ -3,6 +3,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand; import static redis_request.RedisRequestOuterClass.RequestType.Info; +import static redis_request.RedisRequestOuterClass.RequestType.Select; import glide.api.commands.GenericCommands; import glide.api.commands.ServerManagementCommands; @@ -54,4 +55,10 @@ public CompletableFuture info() { public CompletableFuture info(@NonNull InfoOptions options) { return commandManager.submitNewCommand(Info, options.toArgs(), this::handleStringResponse); } + + @Override + public CompletableFuture select(long index) { + return commandManager.submitNewCommand( + Select, new String[] {Long.toString(index)}, this::handleStringResponse); + } } diff --git a/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java b/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java index bd7ed56cdc..5dd94f93e9 100644 --- a/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java +++ b/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java @@ -31,4 +31,13 @@ public interface ServerManagementCommands { * sections requested. */ CompletableFuture info(InfoOptions options); + + /** + * Change the currently selected Redis database. + * + * @see redis.io for details. + * @param index The index of the database to select. + * @return A simple OK response. + */ + CompletableFuture select(long index); } diff --git a/java/client/src/main/java/glide/api/models/Transaction.java b/java/client/src/main/java/glide/api/models/Transaction.java index 219ee7e934..9b015796bc 100644 --- a/java/client/src/main/java/glide/api/models/Transaction.java +++ b/java/client/src/main/java/glide/api/models/Transaction.java @@ -1,7 +1,10 @@ /** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api.models; +import static redis_request.RedisRequestOuterClass.RequestType.Select; + import lombok.AllArgsConstructor; +import redis_request.RedisRequestOuterClass; /** * Extends BaseTransaction class for Redis standalone commands. Transactions allow the execution of @@ -27,4 +30,18 @@ public class Transaction extends BaseTransaction { protected Transaction getThis() { return this; } + + /** + * Change the currently selected Redis database. + * + * @see redis.io for details. + * @param index The index of the database to select. + * @return Command Response - A simple OK response. + */ + public Transaction select(long index) { + RedisRequestOuterClass.Command.ArgsArray commandArgs = buildArgs(Long.toString(index)); + + protobufTransaction.addCommands(buildCommand(Select, commandArgs)); + return this; + } } diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index e8e5f9f7b7..85b4ab7d17 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -37,6 +37,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.SCard; import static redis_request.RedisRequestOuterClass.RequestType.SMembers; import static redis_request.RedisRequestOuterClass.RequestType.SRem; +import static redis_request.RedisRequestOuterClass.RequestType.Select; import static redis_request.RedisRequestOuterClass.RequestType.SetString; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; @@ -724,6 +725,28 @@ public void hincrByFloat_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void select_returns_success() { + // setup + CompletableFuture testResponse = mock(CompletableFuture.class); + Long index = 5L; + when(testResponse.get()).thenReturn(OK); + + // match on protobuf request + when(commandManager.submitNewCommand( + eq(Select), eq(new String[] {Long.toString(index)}), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.select(index); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(OK, payload); + } + @SneakyThrows @Test public void sadd_returns_success() { diff --git a/java/client/src/test/java/glide/api/models/StandaloneTransactionTests.java b/java/client/src/test/java/glide/api/models/StandaloneTransactionTests.java new file mode 100644 index 0000000000..2fb78f92a1 --- /dev/null +++ b/java/client/src/test/java/glide/api/models/StandaloneTransactionTests.java @@ -0,0 +1,35 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.models; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static redis_request.RedisRequestOuterClass.RequestType.Select; + +import java.util.LinkedList; +import java.util.List; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.Test; +import redis_request.RedisRequestOuterClass; +import redis_request.RedisRequestOuterClass.Command.ArgsArray; + +public class StandaloneTransactionTests { + @Test + public void standalone_transaction_commands() { + List> + results = new LinkedList<>(); + Transaction transaction = new Transaction(); + + transaction.select(5L); + results.add(Pair.of(Select, ArgsArray.newBuilder().addArgs("5").build())); + + var protobufTransaction = transaction.getProtobufTransaction().build(); + + for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) { + RedisRequestOuterClass.Command protobuf = protobufTransaction.getCommands(idx); + + assertEquals(results.get(idx).getLeft(), protobuf.getRequestType()); + assertEquals( + results.get(idx).getRight().getArgsCount(), protobuf.getArgsArray().getArgsCount()); + assertEquals(results.get(idx).getRight(), protobuf.getArgsArray()); + } + } +} diff --git a/java/integTest/build.gradle b/java/integTest/build.gradle index 4aab6eb87e..1f8481282e 100644 --- a/java/integTest/build.gradle +++ b/java/integTest/build.gradle @@ -10,6 +10,8 @@ dependencies { // client implementation project(':client') + implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.13.0' + // https://github.com/netty/netty/wiki/Native-transports // At the moment, Windows is not supported implementation group: 'io.netty', name: 'netty-transport-native-epoll', version: '4.1.100.Final', classifier: 'linux-x86_64' diff --git a/java/integTest/src/test/java/glide/standalone/CommandTests.java b/java/integTest/src/test/java/glide/standalone/CommandTests.java index c78ab76ec0..c17b08d746 100644 --- a/java/integTest/src/test/java/glide/standalone/CommandTests.java +++ b/java/integTest/src/test/java/glide/standalone/CommandTests.java @@ -3,6 +3,7 @@ import static glide.TestConfiguration.REDIS_VERSION; import static glide.TestConfiguration.STANDALONE_PORTS; +import static glide.api.BaseClient.OK; import static glide.api.models.commands.InfoOptions.Section.CLUSTER; import static glide.api.models.commands.InfoOptions.Section.CPU; import static glide.api.models.commands.InfoOptions.Section.EVERYTHING; @@ -11,12 +12,16 @@ import static glide.cluster.CommandTests.EVERYTHING_INFO_SECTIONS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import glide.api.RedisClient; import glide.api.models.commands.InfoOptions; import glide.api.models.configuration.NodeAddress; import glide.api.models.configuration.RedisClientConfiguration; +import glide.api.models.exceptions.RequestException; +import java.util.UUID; +import java.util.concurrent.ExecutionException; import lombok.SneakyThrows; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -99,4 +104,28 @@ public void info_with_everything_option() { assertTrue(data.contains("# " + section), "Section " + section + " is missing"); } } + + @Test + @SneakyThrows + public void simple_select_test() { + assertEquals(OK, regularClient.select(0).get()); + + String key = UUID.randomUUID().toString(); + String value = UUID.randomUUID().toString(); + assertEquals(OK, regularClient.set(key, value).get()); + + assertEquals(OK, regularClient.select(1).get()); + assertNull(regularClient.get(key).get()); + + assertEquals(OK, regularClient.select(0).get()); + assertEquals(value, regularClient.get(key).get()); + } + + @Test + @SneakyThrows + public void select_test_gives_error() { + ExecutionException e = + assertThrows(ExecutionException.class, () -> regularClient.select(-1).get()); + assertTrue(e.getCause() instanceof RequestException); + } } diff --git a/java/integTest/src/test/java/glide/standalone/TransactionTests.java b/java/integTest/src/test/java/glide/standalone/TransactionTests.java index e4f7c25c84..8db46436e7 100644 --- a/java/integTest/src/test/java/glide/standalone/TransactionTests.java +++ b/java/integTest/src/test/java/glide/standalone/TransactionTests.java @@ -3,6 +3,7 @@ import static glide.TransactionTestUtilities.transactionTest; import static glide.TransactionTestUtilities.transactionTestResult; +import static glide.api.BaseClient.OK; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -14,8 +15,10 @@ import glide.api.models.commands.InfoOptions; import glide.api.models.configuration.NodeAddress; import glide.api.models.configuration.RedisClientConfiguration; +import java.util.UUID; import java.util.concurrent.TimeUnit; import lombok.SneakyThrows; +import org.apache.commons.lang3.ArrayUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -92,6 +95,17 @@ public void test_standalone_transactions() { Transaction transaction = (Transaction) transactionTest(new Transaction()); Object[] expectedResult = transactionTestResult(); + String key = UUID.randomUUID().toString(); + String value = UUID.randomUUID().toString(); + + transaction.select(1); + transaction.set(key, value); + transaction.get(key); + transaction.select(0); + transaction.get(key); + + expectedResult = ArrayUtils.addAll(expectedResult, OK, OK, value, OK, null); + Object[] result = client.exec(transaction).get(10, TimeUnit.SECONDS); assertArrayEquals(expectedResult, result); } From 4aac6616ab401a2edbae8ad8604cc11798cece28 Mon Sep 17 00:00:00 2001 From: Shachar Langbeheim <98546660+shachlanAmazon@users.noreply.github.com> Date: Sun, 25 Feb 2024 12:09:47 +0200 Subject: [PATCH 10/65] Don't run wrappers' CI on core test changes. (#1028) Co-authored-by: nihohit --- .github/workflows/csharp.yml | 2 +- .github/workflows/go.yml | 4 ++-- .github/workflows/java.yml | 4 ++-- .github/workflows/node.yml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index c32120d93a..e1dfcc5c86 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -5,7 +5,7 @@ on: branches: ["main"] paths: - csharp/** - - glide-core/** + - glide-core/src/** - submodules/** - .github/workflows/csharp.yml pull_request: diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index a396e708b8..80246396b4 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -5,13 +5,13 @@ on: push: branches: [ "main" ] paths: - - glide-core/** + - glide-core/src/** - submodules/** - go/** - .github/workflows/go.yml pull_request: paths: - - glide-core/** + - glide-core/src/** - submodules/** - go/** - .github/workflows/go.yml diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml index 4989fae6c3..941ad2b349 100644 --- a/.github/workflows/java.yml +++ b/.github/workflows/java.yml @@ -4,13 +4,13 @@ on: push: branches: ["main"] paths: - - glide-core/** + - glide-core/src/** - submodules/** - "java/**" - ".github/workflows/java.yml" pull_request: paths: - - glide-core/** + - glide-core/src/** - submodules/** - "java/**" - ".github/workflows/java.yml" diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index 612d931878..e2d1558939 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -4,7 +4,7 @@ on: push: branches: ["main"] paths: - - glide-core/** + - glide-core/src/** - submodules/** - node/** - utils/cluster_manager.py From b866e0cb73aab2134fe7aca0d64f5bdc3691e35b Mon Sep 17 00:00:00 2001 From: ort-bot Date: Sun, 25 Feb 2024 00:20:13 +0000 Subject: [PATCH 11/65] Updated attribution files --- glide-core/THIRD_PARTY_LICENSES_RUST | 22 +- node/THIRD_PARTY_LICENSES_NODE | 26 +- python/THIRD_PARTY_LICENSES_PYTHON | 2314 +++++++++++++++++--------- 3 files changed, 1522 insertions(+), 840 deletions(-) diff --git a/glide-core/THIRD_PARTY_LICENSES_RUST b/glide-core/THIRD_PARTY_LICENSES_RUST index d53682245b..f263209929 100644 --- a/glide-core/THIRD_PARTY_LICENSES_RUST +++ b/glide-core/THIRD_PARTY_LICENSES_RUST @@ -4138,7 +4138,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bumpalo:3.15.1 +Package: bumpalo:3.15.3 The following copyrights and licenses were found in the source code of this package: @@ -12482,7 +12482,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: hermit-abi:0.3.6 +Package: hermit-abi:0.3.8 The following copyrights and licenses were found in the source code of this package: @@ -26958,7 +26958,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: socket2:0.5.5 +Package: socket2:0.5.6 The following copyrights and licenses were found in the source code of this package: @@ -34498,7 +34498,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.52.0 +Package: windows-targets:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -34956,7 +34956,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.52.0 +Package: windows_aarch64_gnullvm:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -35414,7 +35414,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.52.0 +Package: windows_aarch64_msvc:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -35872,7 +35872,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.52.0 +Package: windows_i686_gnu:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -36330,7 +36330,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.52.0 +Package: windows_i686_msvc:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -36788,7 +36788,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.52.0 +Package: windows_x86_64_gnu:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -37246,7 +37246,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnullvm:0.52.0 +Package: windows_x86_64_gnullvm:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -37704,7 +37704,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_msvc:0.52.0 +Package: windows_x86_64_msvc:0.52.3 The following copyrights and licenses were found in the source code of this package: diff --git a/node/THIRD_PARTY_LICENSES_NODE b/node/THIRD_PARTY_LICENSES_NODE index aa8d1c940f..d0a094c8ba 100644 --- a/node/THIRD_PARTY_LICENSES_NODE +++ b/node/THIRD_PARTY_LICENSES_NODE @@ -4190,7 +4190,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bumpalo:3.15.1 +Package: bumpalo:3.15.3 The following copyrights and licenses were found in the source code of this package: @@ -13048,7 +13048,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: hermit-abi:0.3.6 +Package: hermit-abi:0.3.8 The following copyrights and licenses were found in the source code of this package: @@ -17098,7 +17098,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: napi:2.15.2 +Package: napi:2.15.4 The following copyrights and licenses were found in the source code of this package: @@ -28583,7 +28583,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: socket2:0.5.5 +Package: socket2:0.5.6 The following copyrights and licenses were found in the source code of this package: @@ -36810,7 +36810,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.52.0 +Package: windows-targets:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -37268,7 +37268,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.52.0 +Package: windows_aarch64_gnullvm:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -37726,7 +37726,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.52.0 +Package: windows_aarch64_msvc:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -38184,7 +38184,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.52.0 +Package: windows_i686_gnu:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -38642,7 +38642,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.52.0 +Package: windows_i686_msvc:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -39100,7 +39100,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.52.0 +Package: windows_x86_64_gnu:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -39558,7 +39558,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnullvm:0.52.0 +Package: windows_x86_64_gnullvm:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -40016,7 +40016,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_msvc:0.52.0 +Package: windows_x86_64_msvc:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -41602,7 +41602,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -Package: @types:node:20.11.19 +Package: @types:node:20.11.20 The following copyrights and licenses were found in the source code of this package: diff --git a/python/THIRD_PARTY_LICENSES_PYTHON b/python/THIRD_PARTY_LICENSES_PYTHON index 452aca3a9a..db63077cc1 100644 --- a/python/THIRD_PARTY_LICENSES_PYTHON +++ b/python/THIRD_PARTY_LICENSES_PYTHON @@ -4138,7 +4138,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: bumpalo:3.15.1 +Package: bumpalo:3.15.3 The following copyrights and licenses were found in the source code of this package: @@ -12919,7 +12919,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: hermit-abi:0.3.6 +Package: hermit-abi:0.3.8 The following copyrights and licenses were found in the source code of this package: @@ -21173,6 +21173,235 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- +Package: portable-atomic:1.6.0 + +The following copyrights and licenses were found in the source code of this package: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + -- + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + Package: powerfmt:0.2.0 The following copyrights and licenses were found in the source code of this package: @@ -21631,7 +21860,744 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro-error:1.0.4 +Package: proc-macro-error:1.0.4 + +The following copyrights and licenses were found in the source code of this package: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + -- + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: proc-macro-error-attr:1.0.4 + +The following copyrights and licenses were found in the source code of this package: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + -- + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: proc-macro2:1.0.78 + +The following copyrights and licenses were found in the source code of this package: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + -- + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: protobuf:3.3.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: protobuf-support:3.3.0 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: pyo3:0.20.3 The following copyrights and licenses were found in the source code of this package: @@ -21860,7 +22826,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: proc-macro-error-attr:1.0.4 +Package: pyo3-asyncio:0.20.0 The following copyrights and licenses were found in the source code of this package: @@ -22066,30 +23032,9 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- -Package: proc-macro2:1.0.78 +Package: pyo3-build-config:0.20.3 The following copyrights and licenses were found in the source code of this package: @@ -22318,34 +23263,213 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: protobuf:3.3.0 +Package: pyo3-ffi:0.20.3 The following copyrights and licenses were found in the source code of this package: -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + 1. Definitions. ----- + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -Package: protobuf-support:3.3.0 + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -The following copyrights and licenses were found in the source code of this package: + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -22368,7 +23492,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pyo3:0.20.2 +Package: pyo3-macros:0.20.3 The following copyrights and licenses were found in the source code of this package: @@ -22597,7 +23721,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pyo3-asyncio:0.20.0 +Package: pyo3-macros-backend:0.20.3 The following copyrights and licenses were found in the source code of this package: @@ -22803,9 +23927,30 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. + -- + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ---- -Package: pyo3-ffi:0.20.2 +Package: quote:1.0.35 The following copyrights and licenses were found in the source code of this package: @@ -23034,7 +24179,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pyo3-macros:0.20.2 +Package: rand:0.8.5 The following copyrights and licenses were found in the source code of this package: @@ -23263,7 +24408,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pyo3-macros-backend:0.20.2 +Package: rand_chacha:0.3.1 The following copyrights and licenses were found in the source code of this package: @@ -23492,7 +24637,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: quote:1.0.35 +Package: rand_core:0.6.4 The following copyrights and licenses were found in the source code of this package: @@ -23721,213 +24866,65 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand:0.8.5 +Package: redis:0.24.0 The following copyrights and licenses were found in the source code of this package: - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. +---- - END OF TERMS AND CONDITIONS +Package: redox_syscall:0.4.1 - APPENDIX: How to apply the Apache License to your work. +The following copyrights and licenses were found in the source code of this package: - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: - Copyright [yyyy] [name of copyright owner] +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - http://www.apache.org/licenses/LICENSE-2.0 +---- - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Package: redox_users:0.4.4 - -- +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -23950,7 +24947,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand_chacha:0.3.1 +Package: rustc-demangle:0.1.23 The following copyrights and licenses were found in the source code of this package: @@ -24179,7 +25176,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rand_core:0.6.4 +Package: rustix:0.37.27 The following copyrights and licenses were found in the source code of this package: @@ -24387,112 +25384,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: redis:0.24.0 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: redox_syscall:0.4.1 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: redox_users:0.4.4 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: rustc-demangle:0.1.23 - -The following copyrights and licenses were found in the source code of this package: - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -24695,6 +25586,22 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + -- Permission is hereby granted, free of charge, to any person obtaining @@ -24718,7 +25625,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustix:0.37.27 +Package: rustls:0.22.2 The following copyrights and licenses were found in the source code of this package: @@ -24926,6 +25833,45 @@ The following copyrights and licenses were found in the source code of this pack -- +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + -- + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: rustls-native-certs:0.7.0 + +The following copyrights and licenses were found in the source code of this package: + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -25128,21 +26074,19 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. ---- LLVM Exceptions to the Apache 2.0 License ---- + -- -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. -- @@ -25167,7 +26111,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls:0.22.2 +Package: rustls-pemfile:2.1.0 The following copyrights and licenses were found in the source code of this package: @@ -25410,7 +26354,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-native-certs:0.7.0 +Package: rustls-pki-types:1.3.0 The following copyrights and licenses were found in the source code of this package: @@ -25618,20 +26562,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - - -- - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -25653,7 +26583,25 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pemfile:2.1.0 +Package: rustls-webpki:0.102.2 + +The following copyrights and licenses were found in the source code of this package: + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +---- + +Package: ryu:1.0.17 The following copyrights and licenses were found in the source code of this package: @@ -25861,19 +26809,35 @@ The following copyrights and licenses were found in the source code of this pack -- -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. +Boost Software License - Version 1.0 - August 17th, 2003 -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: - -- +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +---- + +Package: schannel:0.1.23 + +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -25896,7 +26860,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pki-types:1.3.0 +Package: scopeguard:1.2.0 The following copyrights and licenses were found in the source code of this package: @@ -26125,25 +27089,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-webpki:0.102.2 - -The following copyrights and licenses were found in the source code of this package: - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. - ----- - -Package: ryu:1.0.17 +Package: security-framework:2.9.2 The following copyrights and licenses were found in the source code of this package: @@ -26351,36 +27297,6 @@ The following copyrights and licenses were found in the source code of this pack -- -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - ----- - -Package: schannel:0.1.23 - -The following copyrights and licenses were found in the source code of this package: - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -26402,7 +27318,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: scopeguard:1.2.0 +Package: security-framework-sys:2.9.1 The following copyrights and licenses were found in the source code of this package: @@ -26631,7 +27547,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework:2.9.2 +Package: serde:1.0.197 The following copyrights and licenses were found in the source code of this package: @@ -26860,7 +27776,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: security-framework-sys:2.9.1 +Package: serde_derive:1.0.197 The following copyrights and licenses were found in the source code of this package: @@ -27089,213 +28005,40 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde:1.0.197 +Package: sha1_smol:1.0.0 The following copyrights and licenses were found in the source code of this package: - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: - APPENDIX: How to apply the Apache License to your work. +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. - Copyright [yyyy] [name of copyright owner] +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - http://www.apache.org/licenses/LICENSE-2.0 +---- - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Package: sharded-slab:0.1.7 - -- +The following copyrights and licenses were found in the source code of this package: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -27318,7 +28061,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: serde_derive:1.0.197 +Package: signal-hook:0.3.17 The following copyrights and licenses were found in the source code of this package: @@ -27547,63 +28290,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: sha1_smol:1.0.0 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: sharded-slab:0.1.7 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: signal-hook:0.3.17 +Package: signal-hook-registry:1.4.1 The following copyrights and licenses were found in the source code of this package: @@ -27832,7 +28519,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: signal-hook-registry:1.4.1 +Package: signal-hook-tokio:0.3.1 The following copyrights and licenses were found in the source code of this package: @@ -28061,7 +28748,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: signal-hook-tokio:0.3.1 +Package: slab:0.4.9 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: smallvec:1.13.1 The following copyrights and licenses were found in the source code of this package: @@ -28290,32 +29002,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: slab:0.4.9 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: smallvec:1.13.1 +Package: socket2:0.4.10 The following copyrights and licenses were found in the source code of this package: @@ -28544,7 +29231,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: socket2:0.4.10 +Package: socket2:0.5.6 The following copyrights and licenses were found in the source code of this package: @@ -28773,7 +29460,63 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: socket2:0.5.5 +Package: spin:0.9.8 + +The following copyrights and licenses were found in the source code of this package: + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +Package: subtle:2.5.0 + +The following copyrights and licenses were found in the source code of this package: + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the ORGANIZATION nor the names of its contributors may be +used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + +Package: syn:1.0.109 The following copyrights and licenses were found in the source code of this package: @@ -29002,63 +29745,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: spin:0.9.8 - -The following copyrights and licenses were found in the source code of this package: - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ----- - -Package: subtle:2.5.0 - -The following copyrights and licenses were found in the source code of this package: - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the ORGANIZATION nor the names of its contributors may be -used to endorse or promote products derived from this software without specific -prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - -Package: syn:1.0.109 +Package: syn:2.0.50 The following copyrights and licenses were found in the source code of this package: @@ -29287,7 +29974,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.50 +Package: target-lexicon:0.12.14 The following copyrights and licenses were found in the source code of this package: @@ -29493,26 +30180,21 @@ The following copyrights and licenses were found in the source code of this pack See the License for the specific language governing permissions and limitations under the License. - -- - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +--- LLVM Exceptions to the Apache 2.0 License ---- -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. ---- @@ -36542,7 +37224,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.52.0 +Package: windows-targets:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -37000,7 +37682,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.52.0 +Package: windows_aarch64_gnullvm:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -37458,7 +38140,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.52.0 +Package: windows_aarch64_msvc:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -37916,7 +38598,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.52.0 +Package: windows_i686_gnu:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -38374,7 +39056,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.52.0 +Package: windows_i686_msvc:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -38832,7 +39514,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.52.0 +Package: windows_x86_64_gnu:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -39290,7 +39972,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnullvm:0.52.0 +Package: windows_x86_64_gnullvm:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -39748,7 +40430,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_msvc:0.52.0 +Package: windows_x86_64_msvc:0.52.3 The following copyrights and licenses were found in the source code of this package: @@ -41870,7 +42552,7 @@ The following copyrights and licenses were found in the source code of this pack ---- -Package: google-auth:2.28.0 +Package: google-auth:2.28.1 The following copyrights and licenses were found in the source code of this package: From d0b0b030c3b126703202a82a74bdf5854c19c3dc Mon Sep 17 00:00:00 2001 From: Shachar Langbeheim Date: Sun, 25 Feb 2024 13:40:27 +0200 Subject: [PATCH 12/65] Make socket listener tests more readable. (#1027) * Move socket reading into response assert. This removes reading from socket before asserting on the read value from multiple tests. * Add helper function to assert on values. * Remove unused cursor argument. * Move repeating read logic to function. * Increase setup legibility. 2-3 booleans in a row are hard to read without confusion. Using enums instead. * Move repeated writing logic to write helpers. * Add pointer to value helper function. * Add another writer helper method. * fix comments --- glide-core/tests/test_socket_listener.rs | 524 ++++++++++++++--------- 1 file changed, 323 insertions(+), 201 deletions(-) diff --git a/glide-core/tests/test_socket_listener.rs b/glide-core/tests/test_socket_listener.rs index b7d7e6e6e3..11503c449c 100644 --- a/glide-core/tests/test_socket_listener.rs +++ b/glide-core/tests/test_socket_listener.rs @@ -44,6 +44,28 @@ mod socket_listener { ClosingError = 3, } + enum RedisType { + Cluster, + Standalone, + } + + #[derive(Copy, Clone)] + enum Tls { + NoTls, + UseTls, + } + + impl Tls { + fn to_bool(self) -> bool { + matches!(self, Tls::UseTls) + } + } + + enum TestServer { + Shared, + Unique, + } + struct ServerTestBasics { server: Option, socket: UnixStream, @@ -70,9 +92,13 @@ mod socket_listener { args_pointer: bool, } - fn assert_value(pointer: u64, expected_value: Option) { + fn pointer_to_value(pointer: u64) -> Box { let pointer = pointer as *mut Value; - let received_value = unsafe { Box::from_raw(pointer) }; + unsafe { Box::from_raw(pointer) } + } + + fn assert_value(pointer: u64, expected_value: Option) { + let received_value = pointer_to_value(pointer); assert!(expected_value.is_some()); assert_eq!(*received_value, expected_value.unwrap()); } @@ -90,14 +116,28 @@ mod socket_listener { } } - fn assert_null_response(buffer: &[u8], expected_callback: u32) { - assert_response(buffer, 0, expected_callback, None, ResponseType::Null); + fn get_response(buffer: &mut Vec, socket: Option<&mut UnixStream>) -> Response { + if let Some(socket) = socket { + let _size = read_from_socket(buffer, socket); + } + let (message_length, header_bytes) = parse_header(buffer); + decode_response(buffer, header_bytes, message_length as usize) } - fn assert_ok_response(buffer: &[u8], expected_callback: u32) { + fn assert_null_response(buffer: &mut Vec, socket: &mut UnixStream, expected_callback: u32) { assert_response( buffer, - 0, + Some(socket), + expected_callback, + None, + ResponseType::Null, + ); + } + + fn assert_ok_response(buffer: &mut Vec, socket: &mut UnixStream, expected_callback: u32) { + assert_response( + buffer, + Some(socket), expected_callback, Some(Value::Okay), ResponseType::Value, @@ -105,11 +145,27 @@ mod socket_listener { } fn assert_error_response( - buffer: &[u8], + buffer: &mut Vec, + socket: &mut UnixStream, expected_callback: u32, error_type: ResponseType, ) -> Response { - assert_response(buffer, 0, expected_callback, None, error_type) + assert_response(buffer, Some(socket), expected_callback, None, error_type) + } + + fn assert_value_response( + buffer: &mut Vec, + socket: Option<&mut UnixStream>, + expected_callback: u32, + value: Value, + ) -> Response { + assert_response( + buffer, + socket, + expected_callback, + Some(value), + ResponseType::Value, + ) } fn read_from_socket(buffer: &mut Vec, socket: &mut UnixStream) -> usize { @@ -118,14 +174,13 @@ mod socket_listener { } fn assert_response( - buffer: &[u8], - cursor: usize, + buffer: &mut Vec, + socket: Option<&mut UnixStream>, expected_callback: u32, expected_value: Option, expected_response_type: ResponseType, ) -> Response { - let (message_length, header_bytes) = parse_header(buffer); - let response = decode_response(buffer, cursor + header_bytes, message_length as usize); + let response = get_response(buffer, socket); assert_eq!(response.callback_idx, expected_callback); match response.value { Some(response::Value::RespPointer(pointer)) => { @@ -172,12 +227,11 @@ mod socket_listener { u32::encode_var(length, &mut buffer[new_len - required_space..]); } - fn write_message(buffer: &mut Vec, request: impl Message) -> u32 { + fn write_message(buffer: &mut Vec, request: impl Message) { let message_length = request.compute_size() as u32; write_header(buffer, message_length); let _res = buffer.write_all(&request.write_to_bytes().unwrap()); - message_length } fn get_command(components: CommandComponents) -> Command { @@ -214,23 +268,30 @@ mod socket_listener { request } + fn write_request(buffer: &mut Vec, socket: &mut UnixStream, request: RedisRequest) { + write_message(buffer, request); + socket.write_all(buffer).unwrap(); + } + fn write_command_request( buffer: &mut Vec, + socket: &mut UnixStream, callback_index: u32, args: Vec, request_type: EnumOrUnknown, args_pointer: bool, - ) -> u32 { + ) { let request = get_command_request(callback_index, args, request_type, args_pointer); - write_message(buffer, request) + write_request(buffer, socket, request); } fn write_transaction_request( buffer: &mut Vec, + socket: &mut UnixStream, callback_index: u32, commands_components: Vec, - ) -> u32 { + ) { let mut request = RedisRequest::new(); request.callback_idx = callback_index; let mut transaction = Transaction::new(); @@ -244,12 +305,19 @@ mod socket_listener { transaction, )); - write_message(buffer, request) + write_request(buffer, socket, request); } - fn write_get(buffer: &mut Vec, callback_index: u32, key: &str, args_pointer: bool) -> u32 { + fn write_get( + buffer: &mut Vec, + socket: &mut UnixStream, + callback_index: u32, + key: &str, + args_pointer: bool, + ) { write_command_request( buffer, + socket, callback_index, vec![key.to_string()], RequestType::GetString.into(), @@ -259,13 +327,15 @@ mod socket_listener { fn write_set( buffer: &mut Vec, + socket: &mut UnixStream, callback_index: u32, key: &str, value: String, args_pointer: bool, - ) -> u32 { + ) { write_command_request( buffer, + socket, callback_index, vec![key.to_string(), value], RequestType::SetString.into(), @@ -280,7 +350,7 @@ mod socket_listener { fn connect_to_redis( addresses: &[ConnectionAddr], socket: &UnixStream, - use_tls: bool, + use_tls: Tls, cluster_mode: ClusterMode, ) { // Send the server address @@ -288,7 +358,7 @@ mod socket_listener { let connection_request = create_connection_request( addresses, &TestConfiguration { - use_tls, + use_tls: use_tls.to_bool(), cluster_mode, request_timeout: Some(10000), ..Default::default() @@ -300,12 +370,11 @@ mod socket_listener { write_message(&mut buffer, connection_request); let mut socket = socket.try_clone().unwrap(); socket.write_all(&buffer).unwrap(); - let _size = read_from_socket(&mut buffer, &mut socket); - assert_ok_response(&buffer, CALLBACK_INDEX); + assert_ok_response(&mut buffer, &mut socket, CALLBACK_INDEX); } fn setup_socket( - use_tls: bool, + use_tls: Tls, socket_path: Option, addresses: &[ConnectionAddr], cluster_mode: ClusterMode, @@ -341,7 +410,7 @@ mod socket_listener { let server_mock = ServerMock::new(responses); let addresses = server_mock.get_addresses(); let socket = setup_socket( - false, + Tls::NoTls, socket_path, addresses.as_slice(), ClusterMode::Disabled, @@ -353,56 +422,63 @@ mod socket_listener { } fn setup_server_test_basics_with_server_and_socket_path( - use_tls: bool, + use_tls: Tls, socket_path: Option, server: Option, ) -> ServerTestBasics { let address = server .as_ref() .map(|server| server.get_client_addr()) - .unwrap_or(get_shared_server_address(use_tls)); + .unwrap_or(get_shared_server_address(use_tls.to_bool())); let socket = setup_socket(use_tls, socket_path, &[address], ClusterMode::Disabled); ServerTestBasics { server, socket } } fn setup_test_basics_with_socket_path( - use_tls: bool, + use_tls: Tls, socket_path: Option, - shared_server: bool, + shared_server: TestServer, ) -> ServerTestBasics { - let server = if !shared_server { - Some(RedisServer::new(ServerType::Tcp { tls: use_tls })) - } else { - None + let server = match shared_server { + TestServer::Shared => None, + TestServer::Unique => Some(RedisServer::new(ServerType::Tcp { + tls: use_tls.to_bool(), + })), }; setup_server_test_basics_with_server_and_socket_path(use_tls, socket_path, server) } - fn setup_server_test_basics(use_tls: bool, shared_server: bool) -> ServerTestBasics { + fn setup_server_test_basics(use_tls: Tls, shared_server: TestServer) -> ServerTestBasics { setup_test_basics_with_socket_path(use_tls, None, shared_server) } - fn setup_test_basics(use_tls: bool, shared_server: bool, use_cluster: bool) -> TestBasics { - if use_cluster { - let cluster = setup_cluster_test_basics(use_tls, shared_server); - TestBasics { - server: BackingServer::Cluster(cluster._cluster), - socket: cluster.socket, + fn setup_test_basics( + use_tls: Tls, + shared_server: TestServer, + redis_type: RedisType, + ) -> TestBasics { + match redis_type { + RedisType::Cluster => { + let cluster = setup_cluster_test_basics(use_tls, shared_server); + TestBasics { + server: BackingServer::Cluster(cluster._cluster), + socket: cluster.socket, + } } - } else { - let server = setup_server_test_basics(use_tls, shared_server); - TestBasics { - server: BackingServer::Standalone(server.server), - socket: server.socket, + RedisType::Standalone => { + let server = setup_server_test_basics(use_tls, shared_server); + TestBasics { + server: BackingServer::Standalone(server.server), + socket: server.socket, + } } } } - fn setup_cluster_test_basics(use_tls: bool, shared_cluster: bool) -> ClusterTestBasics { - let cluster = if !shared_cluster { - Some(RedisCluster::new(use_tls, &None, None, None)) - } else { - None + fn setup_cluster_test_basics(use_tls: Tls, shared_cluster: TestServer) -> ClusterTestBasics { + let cluster = match shared_cluster { + TestServer::Shared => None, + TestServer::Unique => Some(RedisCluster::new(use_tls.to_bool(), &None, None, None)), }; let socket = setup_socket( use_tls, @@ -410,7 +486,7 @@ mod socket_listener { &cluster .as_ref() .map(|cluster| cluster.get_server_addresses()) - .unwrap_or(get_shared_cluster_addresses(use_tls)), + .unwrap_or(get_shared_cluster_addresses(use_tls.to_bool())), ClusterMode::Enabled, ); ClusterTestBasics { @@ -435,16 +511,23 @@ mod socket_listener { }); const CALLBACK_INDEX: u32 = 99; - let mut test_basics = - setup_test_basics_with_socket_path(false, Some(socket_path.clone()), true); + let mut test_basics = setup_test_basics_with_socket_path( + Tls::NoTls, + Some(socket_path.clone()), + TestServer::Shared, + ); let key = generate_random_string(KEY_LENGTH); let approx_message_length = key.len() + APPROX_RESP_HEADER_LEN; let mut buffer = Vec::with_capacity(approx_message_length); - write_get(&mut buffer, CALLBACK_INDEX, key.as_str(), false); - test_basics.socket.write_all(&buffer).unwrap(); + write_get( + &mut buffer, + &mut test_basics.socket, + CALLBACK_INDEX, + key.as_str(), + false, + ); - let _size = read_from_socket(&mut buffer, &mut test_basics.socket); - assert_null_response(&buffer, CALLBACK_INDEX); + assert_null_response(&mut buffer, &mut test_basics.socket, CALLBACK_INDEX); close_socket(&socket_path); } @@ -465,7 +548,7 @@ mod socket_listener { const CALLBACK_INDEX: u32 = 99; let address = server.get_client_addr(); let mut socket = setup_socket( - false, + Tls::NoTls, Some(socket_path.clone()), &[address], ClusterMode::Disabled, @@ -473,11 +556,15 @@ mod socket_listener { let key = generate_random_string(KEY_LENGTH); let approx_message_length = key.len() + APPROX_RESP_HEADER_LEN; let mut buffer = Vec::with_capacity(approx_message_length); - write_get(&mut buffer, CALLBACK_INDEX, key.as_str(), false); - socket.write_all(&buffer).unwrap(); + write_get( + &mut buffer, + &mut socket, + CALLBACK_INDEX, + key.as_str(), + false, + ); - let _size = read_from_socket(&mut buffer, &mut socket); - assert_null_response(&buffer, CALLBACK_INDEX); + assert_null_response(&mut buffer, &mut socket, CALLBACK_INDEX); }) .unwrap(); } @@ -488,15 +575,13 @@ mod socket_listener { #[rstest] #[timeout(SHORT_CLUSTER_TEST_TIMEOUT)] fn test_socket_set_and_get( - #[values((false, false), (true, false), (false,true,))] use_arg_pointer_and_tls: ( - bool, - bool, - ), - #[values(true, false)] use_cluster: bool, + #[values((false, Tls::NoTls), (true, Tls::NoTls), (false, Tls::UseTls))] + use_arg_pointer_and_tls: (bool, Tls), + #[values(RedisType::Cluster, RedisType::Standalone)] use_cluster: RedisType, ) { let args_pointer = use_arg_pointer_and_tls.0; let use_tls = use_arg_pointer_and_tls.1; - let mut test_basics = setup_test_basics(use_tls, true, use_cluster); + let mut test_basics = setup_test_basics(use_tls, TestServer::Shared, use_cluster); const CALLBACK1_INDEX: u32 = 100; const CALLBACK2_INDEX: u32 = 101; @@ -508,27 +593,28 @@ mod socket_listener { let mut buffer = Vec::with_capacity(approx_message_length); write_set( &mut buffer, + &mut test_basics.socket, CALLBACK1_INDEX, key.as_str(), value.clone(), args_pointer, ); - test_basics.socket.write_all(&buffer).unwrap(); - let _size = read_from_socket(&mut buffer, &mut test_basics.socket); - assert_ok_response(&buffer, CALLBACK1_INDEX); + assert_ok_response(&mut buffer, &mut test_basics.socket, CALLBACK1_INDEX); buffer.clear(); - write_get(&mut buffer, CALLBACK2_INDEX, key.as_str(), args_pointer); - test_basics.socket.write_all(&buffer).unwrap(); - - let _size = read_from_socket(&mut buffer, &mut test_basics.socket); - assert_response( - &buffer, - 0, + write_get( + &mut buffer, + &mut test_basics.socket, CALLBACK2_INDEX, - Some(Value::BulkString(value.into_bytes())), - ResponseType::Value, + key.as_str(), + args_pointer, + ); + assert_value_response( + &mut buffer, + Some(&mut test_basics.socket), + CALLBACK2_INDEX, + Value::BulkString(value.into_bytes()), ); } @@ -536,9 +622,9 @@ mod socket_listener { #[timeout(SHORT_CLUSTER_TEST_TIMEOUT)] fn test_socket_pass_custom_command( #[values(false, true)] args_pointer: bool, - #[values(true, false)] use_cluster: bool, + #[values(RedisType::Cluster, RedisType::Standalone)] use_cluster: RedisType, ) { - let mut test_basics = setup_test_basics(false, true, use_cluster); + let mut test_basics = setup_test_basics(Tls::NoTls, TestServer::Shared, use_cluster); const CALLBACK1_INDEX: u32 = 100; const CALLBACK2_INDEX: u32 = 101; @@ -550,40 +636,37 @@ mod socket_listener { let mut buffer = Vec::with_capacity(approx_message_length); write_command_request( &mut buffer, + &mut test_basics.socket, CALLBACK1_INDEX, vec!["SET".to_string(), key.to_string(), value.clone()], RequestType::CustomCommand.into(), args_pointer, ); - test_basics.socket.write_all(&buffer).unwrap(); - let _size = read_from_socket(&mut buffer, &mut test_basics.socket); - assert_ok_response(&buffer, CALLBACK1_INDEX); + assert_ok_response(&mut buffer, &mut test_basics.socket, CALLBACK1_INDEX); buffer.clear(); write_command_request( &mut buffer, + &mut test_basics.socket, CALLBACK2_INDEX, vec!["GET".to_string(), key], RequestType::CustomCommand.into(), args_pointer, ); - test_basics.socket.write_all(&buffer).unwrap(); - let _size = read_from_socket(&mut buffer, &mut test_basics.socket); - assert_response( - &buffer, - 0, + assert_value_response( + &mut buffer, + Some(&mut test_basics.socket), CALLBACK2_INDEX, - Some(Value::BulkString(value.into_bytes())), - ResponseType::Value, + Value::BulkString(value.into_bytes()), ); } #[rstest] #[timeout(SHORT_CLUSTER_TEST_TIMEOUT)] fn test_socket_pass_manual_route_to_all_primaries() { - let mut test_basics = setup_cluster_test_basics(false, true); + let mut test_basics = setup_cluster_test_basics(Tls::NoTls, TestServer::Shared); const CALLBACK1_INDEX: u32 = 100; let approx_message_length = 4 + APPROX_RESP_HEADER_LEN; @@ -597,19 +680,15 @@ mod socket_listener { let mut routes = redis_request::Routes::default(); routes.set_simple_routes(redis_request::SimpleRoutes::AllPrimaries); request.route = Some(routes).into(); - write_message(&mut buffer, request); - test_basics.socket.write_all(&buffer).unwrap(); + write_request(&mut buffer, &mut test_basics.socket, request); - let _size = read_from_socket(&mut buffer, &mut test_basics.socket); - let (message_length, header_bytes) = parse_header(&buffer); - let response = decode_response(&buffer, header_bytes, message_length as usize); + let response = get_response(&mut buffer, Some(&mut test_basics.socket)); assert_eq!(response.callback_idx, CALLBACK1_INDEX); let Some(response::Value::RespPointer(pointer)) = response.value else { panic!("Unexpected response {:?}", response.value); }; - let pointer = pointer as *mut Value; - let received_value = unsafe { Box::from_raw(pointer) }; + let received_value = pointer_to_value(pointer); let Value::Map(values) = *received_value else { panic!("Unexpected value {:?}", received_value); }; @@ -624,7 +703,7 @@ mod socket_listener { fn test_socket_pass_manual_route_by_address() { // We send a request to a random node, get that node's hostname & port, and then // route the same request to the hostname & port, and verify that we've received the same value. - let mut test_basics = setup_cluster_test_basics(false, true); + let mut test_basics = setup_cluster_test_basics(Tls::NoTls, TestServer::Shared); const CALLBACK_INDEX: u32 = 100; let approx_message_length = 4 + APPROX_RESP_HEADER_LEN; @@ -638,19 +717,15 @@ mod socket_listener { let mut routes = redis_request::Routes::default(); routes.set_simple_routes(redis_request::SimpleRoutes::Random); request.route = Some(routes).into(); - write_message(&mut buffer, request.clone()); - test_basics.socket.write_all(&buffer).unwrap(); + write_request(&mut buffer, &mut test_basics.socket, request.clone()); - let _size = read_from_socket(&mut buffer, &mut test_basics.socket); - let (message_length, header_bytes) = parse_header(&buffer); - let response = decode_response(&buffer, header_bytes, message_length as usize); + let response = get_response(&mut buffer, Some(&mut test_basics.socket)); assert_eq!(response.callback_idx, CALLBACK_INDEX); let Some(response::Value::RespPointer(pointer)) = response.value else { panic!("Unexpected response {:?}", response.value); }; - let pointer = pointer as *mut Value; - let received_value = unsafe { Box::from_raw(pointer) }; + let received_value = pointer_to_value(pointer); let first_value = String::from_redis_value(&received_value).unwrap(); let (host, port) = first_value .split('\n') @@ -670,19 +745,15 @@ mod socket_listener { }; routes.set_by_address_route(by_address_route); request.route = Some(routes).into(); - write_message(&mut buffer, request); - test_basics.socket.write_all(&buffer).unwrap(); + write_request(&mut buffer, &mut test_basics.socket, request); - let _size = read_from_socket(&mut buffer, &mut test_basics.socket); - let (message_length, header_bytes) = parse_header(&buffer); - let response = decode_response(&buffer, header_bytes, message_length as usize); + let response = get_response(&mut buffer, Some(&mut test_basics.socket)); assert_eq!(response.callback_idx, CALLBACK_INDEX); let Some(response::Value::RespPointer(pointer)) = response.value else { panic!("Unexpected response {:?}", response.value); }; - let pointer = pointer as *mut Value; - let received_value = unsafe { Box::from_raw(pointer) }; + let received_value = pointer_to_value(pointer); let second_value = String::from_redis_value(&received_value).unwrap(); assert_eq!(first_value, second_value); @@ -700,11 +771,15 @@ mod socket_listener { .server_mock .add_response(&expected_command, "*-1\r\n".to_string()); let mut buffer = Vec::with_capacity(key.len() * 2); - write_get(&mut buffer, CALLBACK_INDEX, key.as_str(), use_arg_pointer); - test_basics.socket.write_all(&buffer).unwrap(); + write_get( + &mut buffer, + &mut test_basics.socket, + CALLBACK_INDEX, + key.as_str(), + use_arg_pointer, + ); - let _size = read_from_socket(&mut buffer, &mut test_basics.socket); - assert_null_response(&buffer, CALLBACK_INDEX); + assert_null_response(&mut buffer, &mut test_basics.socket, CALLBACK_INDEX); } #[rstest] @@ -720,15 +795,19 @@ mod socket_listener { let mut buffer = Vec::with_capacity(approx_message_length); write_command_request( &mut buffer, + &mut test_basics.socket, CALLBACK_INDEX, vec![key], EnumOrUnknown::from_i32(request_type), false, ); - test_basics.socket.write_all(&buffer).unwrap(); - let _size = read_from_socket(&mut buffer, &mut test_basics.socket); - let response = assert_error_response(&buffer, CALLBACK_INDEX, ResponseType::ClosingError); + let response = assert_error_response( + &mut buffer, + &mut test_basics.socket, + CALLBACK_INDEX, + ResponseType::ClosingError, + ); assert_eq!( response.closing_error(), format!("Received invalid request type: {request_type}") @@ -739,15 +818,13 @@ mod socket_listener { #[rstest] #[timeout(SHORT_CLUSTER_TEST_TIMEOUT)] fn test_socket_send_and_receive_long_values( - #[values((false, false), (true, false), (false,true))] use_arg_pointer_and_tls: ( - bool, - bool, - ), - #[values(true, false)] use_cluster: bool, + #[values((false, Tls::NoTls), (true, Tls::NoTls), (false, Tls::UseTls))] + use_arg_pointer_and_tls: (bool, Tls), + #[values(RedisType::Cluster, RedisType::Standalone)] use_cluster: RedisType, ) { let args_pointer = use_arg_pointer_and_tls.0; let use_tls = use_arg_pointer_and_tls.1; - let mut test_basics = setup_test_basics(use_tls, true, use_cluster); + let mut test_basics = setup_test_basics(use_tls, TestServer::Shared, use_cluster); const CALLBACK1_INDEX: u32 = 100; const CALLBACK2_INDEX: u32 = 101; @@ -762,19 +839,23 @@ mod socket_listener { let mut buffer = Vec::with_capacity(approx_message_length); write_set( &mut buffer, + &mut test_basics.socket, CALLBACK1_INDEX, key.as_str(), value.clone(), args_pointer, ); - test_basics.socket.write_all(&buffer).unwrap(); - let _size = read_from_socket(&mut buffer, &mut test_basics.socket); - assert_ok_response(&buffer, CALLBACK1_INDEX); + assert_ok_response(&mut buffer, &mut test_basics.socket, CALLBACK1_INDEX); buffer.clear(); - write_get(&mut buffer, CALLBACK2_INDEX, key.as_str(), args_pointer); - test_basics.socket.write_all(&buffer).unwrap(); + write_get( + &mut buffer, + &mut test_basics.socket, + CALLBACK2_INDEX, + key.as_str(), + args_pointer, + ); let response_header_length = u32::required_space(size_of::() as u32); let expected_length = size_of::() + response_header_length + 2; // 2 bytes for callbackIdx and value type @@ -787,12 +868,11 @@ mod socket_listener { assert_ne!(0, next_read); size += next_read; } - assert_response( - &buffer, - 0, + assert_value_response( + &mut buffer, + None, CALLBACK2_INDEX, - Some(Value::BulkString(value.into_bytes())), - ResponseType::Value, + Value::BulkString(value.into_bytes()), ); } @@ -801,11 +881,9 @@ mod socket_listener { #[rstest] #[timeout(SHORT_CLUSTER_TEST_TIMEOUT)] fn test_socket_send_and_receive_multiple_long_inputs( - #[values((false, false), (true, false), (false,true))] use_arg_pointer_and_tls: ( - bool, - bool, - ), - #[values(true, false)] use_cluster: bool, + #[values((false, Tls::NoTls), (true, Tls::NoTls), (false, Tls::UseTls))] + use_arg_pointer_and_tls: (bool, Tls), + #[values(RedisType::Cluster, RedisType::Standalone)] use_cluster: RedisType, ) { #[derive(Clone, PartialEq, Eq, Debug)] enum State { @@ -815,7 +893,7 @@ mod socket_listener { } let args_pointer = use_arg_pointer_and_tls.0; let use_tls = use_arg_pointer_and_tls.1; - let test_basics = setup_test_basics(use_tls, true, use_cluster); + let test_basics = setup_test_basics(use_tls, TestServer::Shared, use_cluster); const VALUE_LENGTH: usize = 1000000; const NUMBER_OF_THREADS: usize = 10; let values = Arc::new(Mutex::new(vec![Vec::::new(); NUMBER_OF_THREADS])); @@ -901,18 +979,29 @@ mod socket_listener { // Send a set request let approx_message_length = VALUE_LENGTH + key.len() + APPROX_RESP_HEADER_LEN; let mut buffer = Vec::with_capacity(approx_message_length); - write_set(&mut buffer, index as u32, &key, value, args_pointer); { let _guard = cloned_lock.lock().unwrap(); - write_socket.write_all(&buffer).unwrap(); + write_set( + &mut buffer, + &mut write_socket, + index as u32, + &key, + value, + args_pointer, + ); } buffer.clear(); // Send a get request - write_get(&mut buffer, index as u32, &key, args_pointer); { let _guard = cloned_lock.lock().unwrap(); - write_socket.write_all(&buffer).unwrap(); + write_get( + &mut buffer, + &mut write_socket, + index as u32, + &key, + args_pointer, + ); } }); } @@ -927,7 +1016,8 @@ mod socket_listener { #[rstest] #[timeout(SHORT_STANDALONE_TEST_TIMEOUT)] fn test_does_not_close_when_server_closes() { - let mut test_basics = setup_test_basics(false, false, false); + let mut test_basics = + setup_test_basics(Tls::NoTls, TestServer::Unique, RedisType::Standalone); let server = test_basics.server; drop(server); @@ -935,17 +1025,26 @@ mod socket_listener { const CALLBACK_INDEX: u32 = 0; let key = generate_random_string(KEY_LENGTH); let mut buffer = Vec::with_capacity(100); - write_get(&mut buffer, CALLBACK_INDEX, key.as_str(), false); - test_basics.socket.write_all(&buffer).unwrap(); + write_get( + &mut buffer, + &mut test_basics.socket, + CALLBACK_INDEX, + key.as_str(), + false, + ); - let _size = read_from_socket(&mut buffer, &mut test_basics.socket); - assert_error_response(&buffer, CALLBACK_INDEX, ResponseType::RequestError); + assert_error_response( + &mut buffer, + &mut test_basics.socket, + CALLBACK_INDEX, + ResponseType::RequestError, + ); } #[rstest] #[timeout(SHORT_STANDALONE_TEST_TIMEOUT)] fn test_reconnect_after_temporary_disconnect() { - let test_basics = setup_server_test_basics(false, false); + let test_basics = setup_server_test_basics(Tls::NoTls, TestServer::Unique); let mut socket = test_basics.socket.try_clone().unwrap(); let address = test_basics.server.as_ref().unwrap().get_client_addr(); drop(test_basics); @@ -959,24 +1058,37 @@ mod socket_listener { let key = generate_random_string(KEY_LENGTH); // TODO - this part should be replaced with a sleep once we implement heartbeat let mut buffer = Vec::with_capacity(100); - write_get(&mut buffer, CALLBACK_INDEX, key.as_str(), false); - socket.write_all(&buffer).unwrap(); + write_get( + &mut buffer, + &mut socket, + CALLBACK_INDEX, + key.as_str(), + false, + ); - let _size = read_from_socket(&mut buffer, &mut socket); - assert_error_response(&buffer, CALLBACK_INDEX, ResponseType::RequestError); + assert_error_response( + &mut buffer, + &mut socket, + CALLBACK_INDEX, + ResponseType::RequestError, + ); let mut buffer = Vec::with_capacity(100); - write_get(&mut buffer, CALLBACK_INDEX, key.as_str(), false); - socket.write_all(&buffer).unwrap(); + write_get( + &mut buffer, + &mut socket, + CALLBACK_INDEX, + key.as_str(), + false, + ); - let _size = read_from_socket(&mut buffer, &mut socket); - assert_null_response(&buffer, CALLBACK_INDEX); + assert_null_response(&mut buffer, &mut socket, CALLBACK_INDEX); } #[rstest] #[timeout(SHORT_STANDALONE_TEST_TIMEOUT)] fn test_handle_request_after_reporting_disconnet() { - let test_basics = setup_server_test_basics(false, false); + let test_basics = setup_server_test_basics(Tls::NoTls, TestServer::Unique); let mut socket = test_basics.socket.try_clone().unwrap(); let address = test_basics.server.as_ref().unwrap().get_client_addr(); drop(test_basics); @@ -984,11 +1096,20 @@ mod socket_listener { const CALLBACK_INDEX: u32 = 0; let key = generate_random_string(KEY_LENGTH); let mut buffer = Vec::with_capacity(100); - write_get(&mut buffer, CALLBACK_INDEX, key.as_str(), false); - socket.write_all(&buffer).unwrap(); + write_get( + &mut buffer, + &mut socket, + CALLBACK_INDEX, + key.as_str(), + false, + ); - let _size = read_from_socket(&mut buffer, &mut socket); - assert_error_response(&buffer, CALLBACK_INDEX, ResponseType::RequestError); + assert_error_response( + &mut buffer, + &mut socket, + CALLBACK_INDEX, + ResponseType::RequestError, + ); let new_server = RedisServer::new_with_addr_and_modules(address, &[]); block_on_all(wait_for_server_to_become_ready( @@ -996,17 +1117,23 @@ mod socket_listener { )); let mut buffer = Vec::with_capacity(100); - write_get(&mut buffer, CALLBACK_INDEX, key.as_str(), false); - socket.write_all(&buffer).unwrap(); + write_get( + &mut buffer, + &mut socket, + CALLBACK_INDEX, + key.as_str(), + false, + ); - let _size = read_from_socket(&mut buffer, &mut socket); - assert_null_response(&buffer, CALLBACK_INDEX); + assert_null_response(&mut buffer, &mut socket, CALLBACK_INDEX); } #[rstest] #[timeout(SHORT_CLUSTER_TEST_TIMEOUT)] - fn test_send_transaction_and_get_array_of_results(#[values(true, false)] use_cluster: bool) { - let test_basics = setup_test_basics(false, true, use_cluster); + fn test_send_transaction_and_get_array_of_results( + #[values(RedisType::Cluster, RedisType::Standalone)] use_cluster: RedisType, + ) { + let test_basics = setup_test_basics(Tls::NoTls, TestServer::Shared, use_cluster); let mut socket = test_basics.socket; const CALLBACK_INDEX: u32 = 0; @@ -1034,29 +1161,27 @@ mod socket_listener { }, ]; let mut buffer = Vec::with_capacity(200); - write_transaction_request(&mut buffer, CALLBACK_INDEX, commands); - socket.write_all(&buffer).unwrap(); + write_transaction_request(&mut buffer, &mut socket, CALLBACK_INDEX, commands); - let _size = read_from_socket(&mut buffer, &mut socket); - assert_response( - buffer.as_slice(), - 0, + assert_value_response( + &mut buffer, + Some(&mut socket), CALLBACK_INDEX, - Some(Value::Array(vec![ + Value::Array(vec![ Value::Okay, Value::BulkString(vec![b'b', b'a', b'r']), Value::Okay, Value::Nil, - ])), - ResponseType::Value, + ]), ); } #[rstest] #[timeout(SHORT_CLUSTER_TEST_TIMEOUT)] - fn test_send_script(#[values(true, false)] use_cluster: bool) { - let mut test_basics = setup_test_basics(false, true, use_cluster); - let socket = &mut test_basics.socket; + fn test_send_script( + #[values(RedisType::Cluster, RedisType::Standalone)] use_cluster: RedisType, + ) { + let mut test_basics = setup_test_basics(Tls::NoTls, TestServer::Shared, use_cluster); const CALLBACK_INDEX: u32 = 100; const VALUE_LENGTH: usize = 10; let key = generate_random_string(KEY_LENGTH); @@ -1078,40 +1203,37 @@ mod socket_listener { }, )); - write_header(&mut buffer, request.compute_size() as u32); - let _res = buffer.write_all(&request.write_to_bytes().unwrap()); + write_request(&mut buffer, &mut test_basics.socket, request); - socket.write_all(&buffer).unwrap(); - - let _size = read_from_socket(&mut buffer, socket); - assert_response( - &buffer, - 0, + assert_value_response( + &mut buffer, + Some(&mut test_basics.socket), CALLBACK_INDEX, - Some(Value::BulkString(value.into_bytes())), - ResponseType::Value, + Value::BulkString(value.into_bytes()), ); } #[rstest] #[timeout(SHORT_CLUSTER_TEST_TIMEOUT)] - fn test_send_empty_custom_command_is_an_error(#[values(true, false)] use_cluster: bool) { - let mut test_basics = setup_test_basics(false, true, use_cluster); + fn test_send_empty_custom_command_is_an_error( + #[values(RedisType::Cluster, RedisType::Standalone)] use_cluster: RedisType, + ) { + let mut test_basics = setup_test_basics(Tls::NoTls, TestServer::Shared, use_cluster); const CALLBACK_INDEX: u32 = 42; let mut buffer = Vec::with_capacity(APPROX_RESP_HEADER_LEN); write_command_request( &mut buffer, + &mut test_basics.socket, CALLBACK_INDEX, vec![], RequestType::CustomCommand.into(), false, ); - test_basics.socket.write_all(&buffer).unwrap(); - let _size = read_from_socket(&mut buffer, &mut test_basics.socket); assert_error_response( - buffer.as_slice(), + &mut buffer, + &mut test_basics.socket, CALLBACK_INDEX, ResponseType::RequestError, ); From d449819d203d7b6e94b4998ab4de61d0647279bb Mon Sep 17 00:00:00 2001 From: Shoham Elias <116083498+shohamazon@users.noreply.github.com> Date: Sun, 25 Feb 2024 13:44:51 +0200 Subject: [PATCH 13/65] Python: remove duplicated line from info test --- python/python/tests/test_async_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 723fe63968..33c13e2d71 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -1415,7 +1415,6 @@ async def test_info_random_route(self, redis_client: RedisClusterClient): info = await redis_client.info([InfoSection.SERVER], RandomNode()) assert isinstance(info, str) assert "# Server" in info - assert "# Server" in info @pytest.mark.asyncio From ea5cbb9b0fe390dcc02b1d1ca3a9ebb1844c34d3 Mon Sep 17 00:00:00 2001 From: barshaul Date: Thu, 15 Feb 2024 12:57:10 +0000 Subject: [PATCH 14/65] Fixed updating the library version in the CD workflow --- .github/workflows/npm-cd.yml | 18 ++++++++++--- .github/workflows/pypi-cd.yml | 20 +++++++++++--- .../workflows/update-glide-version/action.yml | 26 +++++++++++++++++++ benchmarks/rust/.cargo/config.toml | 2 +- csharp/lib/.cargo/config.toml | 2 +- glide-core/.cargo/config.toml | 2 +- java/.cargo/config.toml | 2 +- node/rust-client/.cargo/config.toml | 2 +- node/tests/SharedTests.ts | 2 +- python/.cargo/config.toml | 2 +- python/python/tests/test_async_client.py | 2 +- utils/get_licenses_from_ort.py | 8 +++--- 12 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/update-glide-version/action.yml diff --git a/.github/workflows/npm-cd.yml b/.github/workflows/npm-cd.yml index 22f55a0e1b..69d78aa85e 100644 --- a/.github/workflows/npm-cd.yml +++ b/.github/workflows/npm-cd.yml @@ -3,6 +3,10 @@ name: Continuous Deployment on: + pull_request: + paths: + - .github/workflows/npm-cd.yml + - .github/workflows/build-node-wrapper/action.yml push: tags: - "v*.*" @@ -56,7 +60,8 @@ jobs: - name: Set the release version shell: bash run: | - echo "RELEASE_VERSION=${GITHUB_REF:11}" >> $GITHUB_ENV + export version=`if ${{ github.event_name == 'pull_request' }}; then echo '255.255.255'; else echo ${GITHUB_REF:11}; fi` + echo "RELEASE_VERSION=${version}" >> $GITHUB_ENV - name: Setup node if: matrix.build.NPM_PUBLISH == true @@ -68,7 +73,13 @@ jobs: scope: "${{ vars.NPM_SCOPE }}" always-auth: true token: ${{ secrets.NPM_AUTH_TOKEN }} - + + - name: Update package version in config.toml + uses: ./.github/workflows/update-glide-version + with: + folder_path: "${{ github.workspace }}/node/rust-client/.cargo" + named_os: ${{ matrix.build.NAMED_OS }} + - name: Build Node wrapper if: matrix.build.NPM_PUBLISH == true uses: ./.github/workflows/build-node-wrapper @@ -82,7 +93,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - name: Publish to NPM - if: matrix.build.NPM_PUBLISH == true + if: matrix.build.NPM_PUBLISH == true && github.event_name != 'pull_request' shell: bash working-directory: ./node run: | @@ -190,6 +201,7 @@ jobs: done - name: Publish the base package + if: github.event_name != 'pull_request' shell: bash working-directory: ./node/npm/glide run: | diff --git a/.github/workflows/pypi-cd.yml b/.github/workflows/pypi-cd.yml index 56e407b89d..cc273a3ede 100644 --- a/.github/workflows/pypi-cd.yml +++ b/.github/workflows/pypi-cd.yml @@ -3,6 +3,10 @@ name: Continuous Deployment on: + pull_request: + paths: + - .github/workflows/pypi-cd.yml + - .github/workflows/build-python-wrapper/action.yml push: tags: - "v*.*" @@ -56,7 +60,8 @@ jobs: - name: Set the release version shell: bash run: | - echo "RELEASE_VERSION=${GITHUB_REF:11}" >> $GITHUB_ENV + export version=`if ${{ github.event_name == 'pull_request' }}; then echo '255.255.255'; else echo ${GITHUB_REF:11}; fi` + echo "RELEASE_VERSION=${version}" >> $GITHUB_ENV - name: Set the package version for Python if: matrix.build.PYPI_PUBLISH == true @@ -85,6 +90,12 @@ jobs: sudo apt upgrade -y sudo apt install python3 python3-venv python3-pip -y + - name: Update package version in config.toml + uses: ./.github/workflows/update-glide-version + with: + folder_path: "${{ github.workspace }}/python/.cargo" + named_os: ${{ matrix.build.NAMED_OS }} + - name: Build Python wrapper if: matrix.build.PYPI_PUBLISH == true uses: ./.github/workflows/build-python-wrapper @@ -104,7 +115,7 @@ jobs: cat .gitignore - name: Build Python wheels (linux) - if: matrix.build.PYPI_PUBLISH == true && startsWith(matrix.build.NAMED_OS, 'linux') + if: matrix.build.PYPI_PUBLISH == true && startsWith(matrix.build.NAMED_OS, 'linux') && github.event_name != 'pull_request' uses: PyO3/maturin-action@v1 with: working-directory: ./python @@ -125,7 +136,7 @@ jobs: fi - name: Build Python wheels (macos) - if: matrix.build.PYPI_PUBLISH == true && startsWith(matrix.build.NAMED_OS, 'darwin') + if: matrix.build.PYPI_PUBLISH == true && startsWith(matrix.build.NAMED_OS, 'darwin') && github.event_name != 'pull_request' uses: PyO3/maturin-action@v1 with: working-directory: ./python @@ -133,7 +144,7 @@ jobs: args: --release --strip --out wheels -i python3.8 python3.9 python3.10 python3.11 python3.12 - name: Upload Python wheels - if: matrix.build.PYPI_PUBLISH == true + if: matrix.build.PYPI_PUBLISH == true && github.event_name != 'pull_request' uses: actions/upload-artifact@v3 with: name: wheels @@ -141,6 +152,7 @@ jobs: if-no-files-found: error publish-to-pypi: + if: github.event_name != 'pull_request' name: Publish the base PyPi package runs-on: ubuntu-latest needs: publish-binaries diff --git a/.github/workflows/update-glide-version/action.yml b/.github/workflows/update-glide-version/action.yml new file mode 100644 index 0000000000..77d60f4bb4 --- /dev/null +++ b/.github/workflows/update-glide-version/action.yml @@ -0,0 +1,26 @@ +name: Update GLIDE version in the config.toml file + +inputs: + folder_path: + description: "The folder path of the config.toml file" + required: true + type: string + named_os: + description: "The name of the current operating system" + required: false + default: "linux" + type: string + options: + - linux + - darwin +runs: + using: "composite" + steps: + - name: Update package version in config.toml + working-directory: ${{ inputs.folder_path }} + shell: bash + run: | + SED_FOR_MACOS=`if ${{ inputs.named_os == 'darwin' }}; then echo "''"; fi` + sed -i $SED_FOR_MACOS "s|unknown|${{ env.RELEASE_VERSION }}|g" ./config.toml + # log the config.toml file + cat ./config.toml diff --git a/benchmarks/rust/.cargo/config.toml b/benchmarks/rust/.cargo/config.toml index d7336d3306..cc73b700a2 100644 --- a/benchmarks/rust/.cargo/config.toml +++ b/benchmarks/rust/.cargo/config.toml @@ -1,3 +1,3 @@ [env] GLIDE_NAME = "Glide" # This should be overwritten by each wrapper library. -GLIDE_VERSION = "0.1.0" +GLIDE_VERSION = "unknown" diff --git a/csharp/lib/.cargo/config.toml b/csharp/lib/.cargo/config.toml index 45873d24a5..51f56c6ec2 100644 --- a/csharp/lib/.cargo/config.toml +++ b/csharp/lib/.cargo/config.toml @@ -1,3 +1,3 @@ [env] GLIDE_NAME = { value = "GlideC#", force = true } -GLIDE_VERSION = "0.1.0" +GLIDE_VERSION = "unknown" diff --git a/glide-core/.cargo/config.toml b/glide-core/.cargo/config.toml index d7336d3306..cc73b700a2 100644 --- a/glide-core/.cargo/config.toml +++ b/glide-core/.cargo/config.toml @@ -1,3 +1,3 @@ [env] GLIDE_NAME = "Glide" # This should be overwritten by each wrapper library. -GLIDE_VERSION = "0.1.0" +GLIDE_VERSION = "unknown" diff --git a/java/.cargo/config.toml b/java/.cargo/config.toml index 25c9e01f33..44b6861735 100644 --- a/java/.cargo/config.toml +++ b/java/.cargo/config.toml @@ -1,3 +1,3 @@ [env] GLIDE_NAME = { value = "GlideJava", force = true } -GLIDE_VERSION = "0.1.0" +GLIDE_VERSION = "unknown" diff --git a/node/rust-client/.cargo/config.toml b/node/rust-client/.cargo/config.toml index 56be8c8010..551cab4c9a 100644 --- a/node/rust-client/.cargo/config.toml +++ b/node/rust-client/.cargo/config.toml @@ -1,3 +1,3 @@ [env] GLIDE_NAME = { value = "GlideJS", force = true } -GLIDE_VERSION = "0.1.0" +GLIDE_VERSION = "unknown" diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 2ed87bedbb..ef6ae73aa8 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -85,7 +85,7 @@ export function runBaseTests(config: { const result = await client.customCommand(["CLIENT", "INFO"]); expect(result).toContain("lib-name=GlideJS"); - expect(result).toContain("lib-ver=0.1.0"); + expect(result).toContain("lib-ver=unknown"); }, protocol); }, config.timeout diff --git a/python/.cargo/config.toml b/python/.cargo/config.toml index 3188c03f8a..605cb60f60 100644 --- a/python/.cargo/config.toml +++ b/python/.cargo/config.toml @@ -1,3 +1,3 @@ [env] GLIDE_NAME = { value = "GlidePy", force = true } -GLIDE_VERSION = "0.1.0" +GLIDE_VERSION = "unknown" diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 33c13e2d71..5bc7d59894 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -116,7 +116,7 @@ async def test_register_client_name_and_version(self, redis_client: TRedisClient info = await redis_client.custom_command(["CLIENT", "INFO"]) assert type(info) is str assert "lib-name=GlidePy" in info - assert "lib-ver=0.1.0" in info + assert "lib-ver=unknown" in info @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) diff --git a/utils/get_licenses_from_ort.py b/utils/get_licenses_from_ort.py index 4ecf3c6ef6..877b4d7787 100644 --- a/utils/get_licenses_from_ort.py +++ b/utils/get_licenses_from_ort.py @@ -67,7 +67,7 @@ def __str__(self): ] all_licenses_set: Set = set() -unkown_licenses: List[PackageLicense] = [] +unknown_licenses: List[PackageLicense] = [] for ort_result in ort_results_per_lang: with open(ort_result.analyzer_result_file, "r") as ort_results, open( @@ -93,7 +93,7 @@ def __str__(self): final_licenses = [license] for license in final_licenses: if license not in APPROVED_LICENSES: - unkown_licenses.append( + unknown_licenses.append( PackageLicense(package["id"], ort_result.name, license) ) all_licenses_set.add(license) @@ -108,6 +108,6 @@ def __str__(self): for license in all_licenses_set: print(f"{license}") -print("\n\n#### Unkown / Not Pre-Approved Licenses #####\n") -for package in unkown_licenses: +print("\n\n#### unknown / Not Pre-Approved Licenses #####\n") +for package in unknown_licenses: print(str(package)) From d651379b5503abbd7f72b25943cecd4ea4e9bee5 Mon Sep 17 00:00:00 2001 From: barshaul Date: Sun, 18 Feb 2024 11:14:43 +0000 Subject: [PATCH 15/65] Removed unnecessary flags from the CD. --- .github/workflows/npm-cd.yml | 16 +++++----------- .github/workflows/pypi-cd.yml | 24 +++++++++--------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/.github/workflows/npm-cd.yml b/.github/workflows/npm-cd.yml index 69d78aa85e..68dd3b3b82 100644 --- a/.github/workflows/npm-cd.yml +++ b/.github/workflows/npm-cd.yml @@ -12,6 +12,7 @@ on: - "v*.*" jobs: publish-binaries: + if: github.repository_owner == 'aws' name: Publish packages to NPM runs-on: ${{ matrix.build.RUNNER }} strategy: @@ -24,7 +25,6 @@ jobs: RUNNER: ubuntu-latest, ARCH: x64, TARGET: x86_64-unknown-linux-gnu, - NPM_PUBLISH: true, } - { OS: ubuntu-latest, @@ -32,7 +32,6 @@ jobs: RUNNER: [self-hosted, Linux, ARM64], ARCH: arm64, TARGET: aarch64-unknown-linux-gnu, - NPM_PUBLISH: true, CONTAINER: "2_28", } - { @@ -41,7 +40,6 @@ jobs: RUNNER: macos-latest, ARCH: x64, TARGET: x86_64-apple-darwin, - NPM_PUBLISH: true, } - { OS: macos-latest, @@ -49,7 +47,6 @@ jobs: RUNNER: macos-13-xlarge, arch: arm64, TARGET: aarch64-apple-darwin, - NPM_PUBLISH: true, } steps: - name: Checkout @@ -60,11 +57,10 @@ jobs: - name: Set the release version shell: bash run: | - export version=`if ${{ github.event_name == 'pull_request' }}; then echo '255.255.255'; else echo ${GITHUB_REF:11}; fi` - echo "RELEASE_VERSION=${version}" >> $GITHUB_ENV + export version=`if ${{ github.event_name == 'pull_request' }}; then echo '255.255.255'; else echo ${GITHUB_REF:11}; fi` + echo "RELEASE_VERSION=${version}" >> $GITHUB_ENV - name: Setup node - if: matrix.build.NPM_PUBLISH == true uses: actions/setup-node@v3 with: node-version: "16" @@ -81,7 +77,6 @@ jobs: named_os: ${{ matrix.build.NAMED_OS }} - name: Build Node wrapper - if: matrix.build.NPM_PUBLISH == true uses: ./.github/workflows/build-node-wrapper with: os: ${{ matrix.build.OS }} @@ -93,7 +88,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - name: Publish to NPM - if: matrix.build.NPM_PUBLISH == true && github.event_name != 'pull_request' + if: github.event_name != 'pull_request' shell: bash working-directory: ./node run: | @@ -112,7 +107,6 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} - name: Pack the Node package - if: matrix.build.NPM_PUBLISH == true shell: bash working-directory: ./node run: | @@ -126,7 +120,7 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} - name: Upload the Node package - if: matrix.build.NPM_PUBLISH == true + if: github.event_name != 'pull_request' uses: actions/upload-artifact@v3 with: name: ${{ matrix.build.TARGET }} diff --git a/.github/workflows/pypi-cd.yml b/.github/workflows/pypi-cd.yml index cc273a3ede..003f2093b4 100644 --- a/.github/workflows/pypi-cd.yml +++ b/.github/workflows/pypi-cd.yml @@ -12,6 +12,7 @@ on: - "v*.*" jobs: publish-binaries: + if: github.repository_owner == 'aws' name: Publish packages to PyPi runs-on: ${{ matrix.build.RUNNER }} strategy: @@ -24,7 +25,6 @@ jobs: RUNNER: ubuntu-latest, ARCH: x64, TARGET: x86_64-unknown-linux-gnu, - PYPI_PUBLISH: true, } - { OS: ubuntu-latest, @@ -32,7 +32,6 @@ jobs: RUNNER: [self-hosted, Linux, ARM64], ARCH: arm64, TARGET: aarch64-unknown-linux-gnu, - PYPI_PUBLISH: true, CONTAINER: "2_28", } - { @@ -41,7 +40,6 @@ jobs: RUNNER: macos-latest, ARCH: x64, TARGET: x86_64-apple-darwin, - PYPI_PUBLISH: true, } - { OS: macos-latest, @@ -49,7 +47,6 @@ jobs: RUNNER: macos-13-xlarge, arch: arm64, TARGET: aarch64-apple-darwin, - PYPI_PUBLISH: true, } steps: - name: Checkout @@ -64,7 +61,6 @@ jobs: echo "RELEASE_VERSION=${version}" >> $GITHUB_ENV - name: Set the package version for Python - if: matrix.build.PYPI_PUBLISH == true working-directory: ./python run: | SED_FOR_MACOS=`if [[ "${{ matrix.build.OS }}" =~ .*"macos".* ]]; then echo "''"; fi` @@ -73,18 +69,18 @@ jobs: cat Cargo.toml - name: Set up Python - if: matrix.build.PYPI_PUBLISH == true && !contains(matrix.build.RUNNER, 'self-hosted') + if: ${{ !contains(matrix.build.RUNNER, 'self-hosted') }} uses: actions/setup-python@v4 with: python-version: "3.10" - name: Set up Python older versions for MacOS - if: matrix.build.PYPI_PUBLISH == true && startsWith(matrix.build.NAMED_OS, 'darwin') + if: startsWith(matrix.build.NAMED_OS, 'darwin') run: | brew install python@3.8 python@3.9 - name: Setup Python for self-hosted Ubuntu runners - if: matrix.build.PYPI_PUBLISH == true && contains(matrix.build.OS, 'ubuntu') && contains(matrix.build.RUNNER, 'self-hosted') + if: contains(matrix.build.OS, 'ubuntu') && contains(matrix.build.RUNNER, 'self-hosted') run: | sudo apt update -y sudo apt upgrade -y @@ -97,7 +93,6 @@ jobs: named_os: ${{ matrix.build.NAMED_OS }} - name: Build Python wrapper - if: matrix.build.PYPI_PUBLISH == true uses: ./.github/workflows/build-python-wrapper with: os: ${{ matrix.build.OS }} @@ -107,7 +102,6 @@ jobs: - name: Include protobuf files in the package working-directory: ./python - if: matrix.build.PYPI_PUBLISH == true run: | SED_FOR_MACOS=`if [[ "${{ matrix.build.OS }}" =~ .*"macos".* ]]; then echo "''"; fi` sed -i $SED_FOR_MACOS '/pb2.py/d' .gitignore @@ -115,12 +109,12 @@ jobs: cat .gitignore - name: Build Python wheels (linux) - if: matrix.build.PYPI_PUBLISH == true && startsWith(matrix.build.NAMED_OS, 'linux') && github.event_name != 'pull_request' + if: startsWith(matrix.build.NAMED_OS, 'linux') uses: PyO3/maturin-action@v1 with: working-directory: ./python target: ${{ matrix.build.TARGET }} - args: --release --strip --out wheels -i python3.8 python3.9 python3.10 python3.11 python3.12 + args: --release --strip --out wheels -i ${{ github.event_name != 'pull_request' && 'python3.8 python3.9 python3.10 python3.11 python3.12' || 'python3.10' }} manylinux: auto container: ${{ matrix.build.CONTAINER != '' && matrix.build.CONTAINER || '2014' }} before-script-linux: | @@ -136,15 +130,15 @@ jobs: fi - name: Build Python wheels (macos) - if: matrix.build.PYPI_PUBLISH == true && startsWith(matrix.build.NAMED_OS, 'darwin') && github.event_name != 'pull_request' + if: startsWith(matrix.build.NAMED_OS, 'darwin') uses: PyO3/maturin-action@v1 with: working-directory: ./python target: ${{ matrix.build.TARGET }} - args: --release --strip --out wheels -i python3.8 python3.9 python3.10 python3.11 python3.12 + args: --release --strip --out wheels -i ${{ github.event_name != 'pull_request' && 'python3.8 python3.9 python3.10 python3.11 python3.12' || 'python3.10' }} - name: Upload Python wheels - if: matrix.build.PYPI_PUBLISH == true && github.event_name != 'pull_request' + if: github.event_name != 'pull_request' uses: actions/upload-artifact@v3 with: name: wheels From d8c056e0ba14755a5475e080de6994b0ad9b2b8f Mon Sep 17 00:00:00 2001 From: ort-bot Date: Mon, 26 Feb 2024 00:19:05 +0000 Subject: [PATCH 16/65] Updated attribution files --- python/THIRD_PARTY_LICENSES_PYTHON | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/THIRD_PARTY_LICENSES_PYTHON b/python/THIRD_PARTY_LICENSES_PYTHON index db63077cc1..c78134feed 100644 --- a/python/THIRD_PARTY_LICENSES_PYTHON +++ b/python/THIRD_PARTY_LICENSES_PYTHON @@ -45751,7 +45751,7 @@ PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 ---- -Package: typing-extensions:4.9.0 +Package: typing-extensions:4.10.0 The following copyrights and licenses were found in the source code of this package: From ade0377c7daa3e7af1adfa08f12499a14fd1a59a Mon Sep 17 00:00:00 2001 From: Shachar Langbeheim <98546660+shachlanAmazon@users.noreply.github.com> Date: Mon, 26 Feb 2024 10:49:57 +0200 Subject: [PATCH 17/65] Node: enable routing by node address. (#1021) Co-authored-by: nihohit --- CHANGELOG.md | 4 ++ node/src/RedisClusterClient.ts | 37 +++++++++++++- node/tests/RedisClusterClient.test.ts | 69 +++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94f678dbe9..1b2a4b4025 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +#### Changes + +- Node: Allow routing Cluster requests by address. ([#1021](https://github.com/aws/glide-for-redis/pull/1021)) + ## 0.2.0 (2024-02-11) #### Changes diff --git a/node/src/RedisClusterClient.ts b/node/src/RedisClusterClient.ts index b9a1fdb90f..f35b2bfd85 100644 --- a/node/src/RedisClusterClient.ts +++ b/node/src/RedisClusterClient.ts @@ -52,6 +52,19 @@ export type SlotKeyTypes = { key: string; }; +/// Route command to specific node. +export type RouteByAddress = { + type: "routeByAddress"; + /** + * DNS name of the host. + */ + host: string; + /** + * The port to access on the node. If port is not provided, `host` is assumed to be in the format `{hostname}:{port}`. + */ + port?: number; +}; + export type Routes = | SingleNodeRoute /** @@ -75,7 +88,8 @@ export type SingleNodeRoute = /** * Route request to the node that contains the slot that the given key matches. */ - | SlotKeyTypes; + | SlotKeyTypes + | RouteByAddress; function toProtobufRoute( route: Routes | undefined @@ -124,6 +138,27 @@ function toProtobufRoute( slotId: route.id, }), }); + } else if (route.type === "routeByAddress") { + let port = route.port; + let host = route.host; + + if (port === undefined) { + const split = host.split(":"); + + if (split.length !== 2) { + throw new Error( + "No port provided, expected host to be formatted as `{hostname}:{port}`. Received " + + host + ); + } + + host = split[0]; + port = Number(split[1]); + } + + return redis_request.Routes.create({ + byAddressRoute: { host, port }, + }); } } diff --git a/node/tests/RedisClusterClient.test.ts b/node/tests/RedisClusterClient.test.ts index 1ea93d589a..43533434b9 100644 --- a/node/tests/RedisClusterClient.test.ts +++ b/node/tests/RedisClusterClient.test.ts @@ -135,6 +135,75 @@ describe("RedisClusterClient", () => { TIMEOUT ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `route by address reaches correct node_%p`, + async (protocol) => { + const client = await RedisClusterClient.createClient( + getOptions(cluster.ports(), protocol) + ); + const result = (await client.customCommand( + ["cluster", "nodes"], + "randomNode" + )) as string; + + // check that routing without explicit port works + const host = + result + .split("\n") + .find((line) => line.includes("myself")) + ?.split(" ")[1] + .split("@")[0] ?? ""; + + if (!host) { + throw new Error("No host could be parsed"); + } + + const secondResult = (await client.customCommand( + ["cluster", "nodes"], + { + type: "routeByAddress", + host, + } + )) as string; + + expect(result).toEqual(secondResult); + + const [host2, port] = host.split(":"); + + // check that routing with explicit port works + const thirdResult = (await client.customCommand( + ["cluster", "nodes"], + { + type: "routeByAddress", + host: host2, + port: Number(port), + } + )) as string; + + expect(result).toEqual(thirdResult); + + client.close(); + }, + TIMEOUT + ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `fail routing by address if no port is provided_%p`, + async (protocol) => { + const client = await RedisClusterClient.createClient( + getOptions(cluster.ports(), protocol) + ); + expect(() => + client.info(undefined, { + type: "routeByAddress", + host: "foo", + }) + ).toThrowError(); + client.close(); + }, + TIMEOUT + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `config get and config set transactions test_%p`, async (protocol) => { From 17961c9f4297a9c62d3226e3bdfffe39c5117d10 Mon Sep 17 00:00:00 2001 From: Shachar Langbeheim <98546660+shachlanAmazon@users.noreply.github.com> Date: Mon, 26 Feb 2024 18:00:22 +0200 Subject: [PATCH 18/65] Fix lint warnings (#1039) found with `flake8 . --count --exit-zero --max-complexity=12 --max-line-length=127 --statistics --exclude=python/glide/protobuf,.env/* --extend-ignore=E230` 1 E203 whitespace before ':' 4 E711 comparison to None should be 'if cond is None:' 5 E712 comparison to True should be 'if cond is True:' or 'if cond:' 1 F401 'typing.Set' imported but unused 1 F841 local variable 'e' is assigned to but never used Co-authored-by: nihohit --- python/python/glide/redis_client.py | 2 +- python/python/tests/test_async_client.py | 49 +++++++++--------------- 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/python/python/glide/redis_client.py b/python/python/glide/redis_client.py index 989cb6b495..163f0aae1c 100644 --- a/python/python/glide/redis_client.py +++ b/python/python/glide/redis_client.py @@ -2,7 +2,7 @@ import asyncio import threading -from typing import List, Optional, Set, Tuple, Union, cast +from typing import List, Optional, Tuple, Union, cast import async_timeout from glide.async_commands.cluster_commands import ClusterCommands diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 5bc7d59894..029e73e510 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -713,9 +713,9 @@ async def test_hexist(self, redis_client: TRedisClient): field_value_map = {field: "value", field2: "value2"} assert await redis_client.hset(key, field_value_map) == 2 - assert await redis_client.hexists(key, field) == True - assert await redis_client.hexists(key, "nonExistingField") == False - assert await redis_client.hexists("nonExistingKey", field2) == False + assert await redis_client.hexists(key, field) + assert not await redis_client.hexists(key, "nonExistingField") + assert not await redis_client.hexists("nonExistingKey", field2) @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) @@ -938,21 +938,15 @@ async def test_expire_pexpire_ttl_with_positive_timeout( # set command clears the timeout. assert await redis_client.set(key, "bar") == OK if await check_if_server_version_lt(redis_client, "7.0.0"): - assert await redis_client.pexpire(key, 10000) == True + assert await redis_client.pexpire(key, 10000) else: - assert ( - await redis_client.pexpire(key, 10000, ExpireOptions.HasNoExpiry) - == True - ) + assert await redis_client.pexpire(key, 10000, ExpireOptions.HasNoExpiry) assert await redis_client.ttl(key) in range(11) if await check_if_server_version_lt(redis_client, "7.0.0"): - assert await redis_client.expire(key, 15) == True + assert await redis_client.expire(key, 15) else: - assert ( - await redis_client.expire(key, 15, ExpireOptions.HasExistingExpiry) - == True - ) + assert await redis_client.expire(key, 15, ExpireOptions.HasExistingExpiry) assert await redis_client.ttl(key) in range(16) @pytest.mark.parametrize("cluster_mode", [True, False]) @@ -981,11 +975,8 @@ async def test_expireat_pexpireat_ttl_with_positive_timeout( assert await redis_client.set(key, "bar") == OK current_time_ms = int(time.time() * 1000) if not await check_if_server_version_lt(redis_client, "7.0.0"): - assert ( - await redis_client.pexpireat( - key, current_time_ms + 50000, ExpireOptions.HasExistingExpiry - ) - == False + assert not await redis_client.pexpireat( + key, current_time_ms + 50000, ExpireOptions.HasExistingExpiry ) @pytest.mark.parametrize("cluster_mode", [True, False]) @@ -1001,7 +992,7 @@ async def test_expire_pexpire_expireat_pexpireat_past_or_negative_timeout( assert await redis_client.ttl(key) == -2 assert await redis_client.set(key, "foo") == OK - assert await redis_client.pexpire(key, -10000) == True + assert await redis_client.pexpire(key, -10000) assert await redis_client.ttl(key) == -2 assert await redis_client.set(key, "foo") == OK @@ -1009,9 +1000,7 @@ async def test_expire_pexpire_expireat_pexpireat_past_or_negative_timeout( assert await redis_client.ttl(key) == -2 assert await redis_client.set(key, "foo") == OK - assert ( - await redis_client.pexpireat(key, int(time.time() * 1000) - 50000) == True - ) + assert await redis_client.pexpireat(key, int(time.time() * 1000) - 50000) assert await redis_client.ttl(key) == -2 @pytest.mark.parametrize("cluster_mode", [True, False]) @@ -1022,11 +1011,9 @@ async def test_expire_pexpire_expireAt_pexpireAt_ttl_non_existing_key( key = get_random_string(10) assert await redis_client.expire(key, 10) == 0 - assert await redis_client.pexpire(key, 10000) == False + assert not await redis_client.pexpire(key, 10000) assert await redis_client.expireat(key, int(time.time()) + 50) == 0 - assert ( - await redis_client.pexpireat(key, int(time.time() * 1000) + 50000) == False - ) + assert not await redis_client.pexpireat(key, int(time.time() * 1000) + 50000) assert await redis_client.ttl(key) == -2 @pytest.mark.parametrize("cluster_mode", [True, False]) @@ -1066,7 +1053,7 @@ async def test_zadd_nx_xx(self, redis_client: TRedisClient): increment=5.0, existing_options=ConditionalChange.ONLY_IF_DOES_NOT_EXIST, ) - == None + is None ) assert ( @@ -1123,7 +1110,7 @@ async def test_zadd_gt_lt(self, redis_client: TRedisClient): increment=-3.0, update_condition=UpdateOptions.GREATER_THAN, ) - == None + is None ) @pytest.mark.parametrize("cluster_mode", [True, False]) @@ -1187,9 +1174,9 @@ async def test_zscore(self, redis_client: TRedisClient): assert await redis_client.zadd(key, members_scores=members_scores) == 3 assert await redis_client.zscore(key, "one") == 1.0 - assert await redis_client.zscore(key, "non_existing_member") == None + assert await redis_client.zscore(key, "non_existing_member") is None assert ( - await redis_client.zscore("non_existing_key", "non_existing_member") == None + await redis_client.zscore("non_existing_key", "non_existing_member") is None ) @pytest.mark.parametrize("cluster_mode", [True, False]) @@ -1423,7 +1410,7 @@ class TestExceptions: @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_timeout_exception_with_blpop(self, redis_client: TRedisClient): key = get_random_string(10) - with pytest.raises(TimeoutError) as e: + with pytest.raises(TimeoutError): await redis_client.custom_command(["BLPOP", key, "1"]) From ef6e73ed52e4590fc50b6855097789ae84cc58e1 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Mon, 26 Feb 2024 11:51:35 -0800 Subject: [PATCH 19/65] Java: Move ping() commands out of baseclient (#1029) * Java: Move ping() commands out of baseclient (#110) Signed-off-by: Andrew Carbonetto Co-authored-by: SanHalacogluImproving --- .../src/main/java/glide/api/BaseClient.java | 19 +------- .../src/main/java/glide/api/RedisClient.java | 16 ++++++- .../java/glide/api/RedisClusterClient.java | 15 ++++++- .../ConnectionManagementClusterCommands.java | 26 ++++++++--- .../ConnectionManagementCommands.java | 9 ++-- .../glide/api/RedisClusterClientTest.java | 44 ++++++++++++++++++- .../test/java/glide/SharedCommandTests.java | 16 ------- .../test/java/glide/cluster/CommandTests.java | 14 ++++++ .../java/glide/standalone/CommandTests.java | 14 ++++++ 9 files changed, 125 insertions(+), 48 deletions(-) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index b24ac3ab96..67057a95d5 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -22,7 +22,6 @@ import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; -import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; import static redis_request.RedisRequestOuterClass.RequestType.SCard; import static redis_request.RedisRequestOuterClass.RequestType.SMembers; @@ -30,7 +29,6 @@ import static redis_request.RedisRequestOuterClass.RequestType.SetString; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; -import glide.api.commands.ConnectionManagementCommands; import glide.api.commands.GenericBaseCommands; import glide.api.commands.HashCommands; import glide.api.commands.SetCommands; @@ -61,12 +59,7 @@ /** Base Client class for Redis */ @AllArgsConstructor public abstract class BaseClient - implements AutoCloseable, - GenericBaseCommands, - ConnectionManagementCommands, - StringCommands, - HashCommands, - SetCommands { + implements AutoCloseable, GenericBaseCommands, StringCommands, HashCommands, SetCommands { /** Redis simple string response with "OK" */ public static final String OK = ConstantResponse.OK.toString(); @@ -214,16 +207,6 @@ protected Set handleSetResponse(Response response) throws RedisException return handleRedisResponse(Set.class, false, response); } - @Override - public CompletableFuture ping() { - return commandManager.submitNewCommand(Ping, new String[0], this::handleStringResponse); - } - - @Override - public CompletableFuture ping(@NonNull String str) { - return commandManager.submitNewCommand(Ping, new String[] {str}, this::handleStringResponse); - } - @Override public CompletableFuture del(@NonNull String[] keys) { return commandManager.submitNewCommand(Del, keys, this::handleLongResponse); diff --git a/java/client/src/main/java/glide/api/RedisClient.java b/java/client/src/main/java/glide/api/RedisClient.java index 76389b1d9b..5b4d28ab4a 100644 --- a/java/client/src/main/java/glide/api/RedisClient.java +++ b/java/client/src/main/java/glide/api/RedisClient.java @@ -3,8 +3,10 @@ import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand; import static redis_request.RedisRequestOuterClass.RequestType.Info; +import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.Select; +import glide.api.commands.ConnectionManagementCommands; import glide.api.commands.GenericCommands; import glide.api.commands.ServerManagementCommands; import glide.api.models.Transaction; @@ -19,7 +21,8 @@ * Async (non-blocking) client for Redis in Standalone mode. Use {@link #CreateClient} to request a * client to Redis. */ -public class RedisClient extends BaseClient implements GenericCommands, ServerManagementCommands { +public class RedisClient extends BaseClient + implements GenericCommands, ServerManagementCommands, ConnectionManagementCommands { protected RedisClient(ConnectionManager connectionManager, CommandManager commandManager) { super(connectionManager, commandManager); @@ -46,6 +49,17 @@ public CompletableFuture exec(Transaction transaction) { return commandManager.submitNewCommand(transaction, this::handleArrayOrNullResponse); } + @Override + public CompletableFuture ping() { + return commandManager.submitNewCommand(Ping, new String[0], this::handleStringResponse); + } + + @Override + public CompletableFuture ping(@NonNull String message) { + return commandManager.submitNewCommand( + Ping, new String[] {message}, this::handleStringResponse); + } + @Override public CompletableFuture info() { return commandManager.submitNewCommand(Info, new String[0], this::handleStringResponse); diff --git a/java/client/src/main/java/glide/api/RedisClusterClient.java b/java/client/src/main/java/glide/api/RedisClusterClient.java index b288d7b70f..719fd07451 100644 --- a/java/client/src/main/java/glide/api/RedisClusterClient.java +++ b/java/client/src/main/java/glide/api/RedisClusterClient.java @@ -86,15 +86,26 @@ public CompletableFuture[]> exec( .>toArray(ClusterValue[]::new)); } + @Override + public CompletableFuture ping() { + return commandManager.submitNewCommand(Ping, new String[0], this::handleStringResponse); + } + + @Override + public CompletableFuture ping(@NonNull String message) { + return commandManager.submitNewCommand( + Ping, new String[] {message}, this::handleStringResponse); + } + @Override public CompletableFuture ping(@NonNull Route route) { return commandManager.submitNewCommand(Ping, new String[0], route, this::handleStringResponse); } @Override - public CompletableFuture ping(@NonNull String str, @NonNull Route route) { + public CompletableFuture ping(@NonNull String message, @NonNull Route route) { return commandManager.submitNewCommand( - Ping, new String[] {str}, route, this::handleStringResponse); + Ping, new String[] {message}, route, this::handleStringResponse); } @Override diff --git a/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java b/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java index a3d5f03e6b..5620052dfe 100644 --- a/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java +++ b/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java @@ -11,13 +11,30 @@ */ public interface ConnectionManagementClusterCommands { + /** + * Ping the Redis server. The command will be routed to all primaries. + * + * @see redis.io for details. + * @return String with "PONG". + */ + CompletableFuture ping(); + + /** + * Ping the Redis server. The command will be routed to all primaries. + * + * @see redis.io for details. + * @param message The server will respond with a copy of the message. + * @return String with a copy of the argument message. + */ + CompletableFuture ping(String message); + /** * Ping the Redis server. * * @see redis.io for details. * @param route Routing configuration for the command. Client will route the command to the nodes * defined. - * @return Response from Redis containing a String with "PONG". + * @return String with "PONG". */ CompletableFuture ping(Route route); @@ -25,11 +42,10 @@ public interface ConnectionManagementClusterCommands { * Ping the Redis server. * * @see redis.io for details. - * @param str The ping argument that will be returned. + * @param message The ping argument that will be returned. * @param route Routing configuration for the command. Client will route the command to the nodes * defined. - * @return Response from Redis containing a String with a copy of the argument - * str. + * @return String with a copy of the argument message. */ - CompletableFuture ping(String str, Route route); + CompletableFuture ping(String message, Route route); } diff --git a/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java b/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java index 5f4027bdd1..a38cff926e 100644 --- a/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java +++ b/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java @@ -14,7 +14,7 @@ public interface ConnectionManagementCommands { * Ping the Redis server. * * @see redis.io for details. - * @return Response from Redis containing a String with "PONG". + * @return String with "PONG". */ CompletableFuture ping(); @@ -22,9 +22,8 @@ public interface ConnectionManagementCommands { * Ping the Redis server. * * @see redis.io for details. - * @param str The ping argument that will be returned. - * @return Response from Redis containing a String with a copy of the argument - * str. + * @param message The server will respond with a copy of the message. + * @return String with a copy of the argument message. */ - CompletableFuture ping(String str); + CompletableFuture ping(String message); } diff --git a/java/client/src/test/java/glide/api/RedisClusterClientTest.java b/java/client/src/test/java/glide/api/RedisClusterClientTest.java index d79150daed..341cd40025 100644 --- a/java/client/src/test/java/glide/api/RedisClusterClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClusterClientTest.java @@ -148,6 +148,48 @@ public void ping_returns_success() { CompletableFuture testResponse = mock(CompletableFuture.class); when(testResponse.get()).thenReturn("PONG"); + // match on protobuf request + when(commandManager.submitNewCommand(eq(Ping), eq(new String[0]), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.ping(); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals("PONG", payload); + } + + @SneakyThrows + @Test + public void ping_with_message_returns_success() { + // setup + String message = "RETURN OF THE PONG"; + String[] arguments = new String[] {message}; + CompletableFuture testResponse = new CompletableFuture(); + testResponse.complete(message); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Ping), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.ping(message); + String pong = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(message, pong); + } + + @SneakyThrows + @Test + public void ping_with_route_returns_success() { + // setup + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn("PONG"); + Route route = ALL_NODES; // match on protobuf request @@ -165,7 +207,7 @@ public void ping_returns_success() { @SneakyThrows @Test - public void ping_with_message_returns_success() { + public void ping_with_message_with_route_returns_success() { // setup String message = "RETURN OF THE PONG"; String[] arguments = new String[] {message}; diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index e13e6bee89..5010a27261 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -77,22 +77,6 @@ public static void teardown() { clusterClient.close(); } - @SneakyThrows - @ParameterizedTest - @MethodSource("getClients") - public void ping(BaseClient client) { - String data = client.ping().get(); - assertEquals("PONG", data); - } - - @SneakyThrows - @ParameterizedTest - @MethodSource("getClients") - public void ping_with_message(BaseClient client) { - String data = client.ping("H3LL0").get(); - assertEquals("H3LL0", data); - } - @SneakyThrows @ParameterizedTest @MethodSource("getClients") diff --git a/java/integTest/src/test/java/glide/cluster/CommandTests.java b/java/integTest/src/test/java/glide/cluster/CommandTests.java index 38973aa581..408cabb8a6 100644 --- a/java/integTest/src/test/java/glide/cluster/CommandTests.java +++ b/java/integTest/src/test/java/glide/cluster/CommandTests.java @@ -130,6 +130,20 @@ public void custom_command_del_returns_a_number() { assertNull(data); } + @Test + @SneakyThrows + public void ping() { + String data = clusterClient.ping().get(); + assertEquals("PONG", data); + } + + @Test + @SneakyThrows + public void ping_with_message() { + String data = clusterClient.ping("H3LL0").get(); + assertEquals("H3LL0", data); + } + @Test @SneakyThrows public void ping_with_route() { diff --git a/java/integTest/src/test/java/glide/standalone/CommandTests.java b/java/integTest/src/test/java/glide/standalone/CommandTests.java index c17b08d746..747c6e152d 100644 --- a/java/integTest/src/test/java/glide/standalone/CommandTests.java +++ b/java/integTest/src/test/java/glide/standalone/CommandTests.java @@ -70,6 +70,20 @@ public void custom_command_del_returns_a_number() { assertNull(data); } + @Test + @SneakyThrows + public void ping() { + String data = regularClient.ping().get(); + assertEquals("PONG", data); + } + + @Test + @SneakyThrows + public void ping_with_message() { + String data = regularClient.ping("H3LL0").get(); + assertEquals("H3LL0", data); + } + @Test @SneakyThrows public void info_without_options() { From 10725c0428d1b5f2b407f03ab05f22f04ef0889e Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Mon, 26 Feb 2024 13:25:46 -0800 Subject: [PATCH 20/65] Added Lpush, Lpop and LpopCount Commands. (List Commands) (#1031) * Added Lpush, Lpop and LpopCount Commands. (List Commands) --- .../src/main/java/glide/api/BaseClient.java | 30 ++++++- .../glide/api/commands/ListBaseCommands.java | 68 ++++++++++++++ .../glide/api/models/BaseTransaction.java | 54 +++++++++++ .../java/glide/utils/ArrayTransformUtils.java | 6 +- .../test/java/glide/api/RedisClientTest.java | 90 +++++++++++++++++-- .../glide/api/models/TransactionTests.java | 14 +++ .../test/java/glide/SharedCommandTests.java | 33 +++++++ .../java/glide/TransactionTestUtilities.java | 18 +++- 8 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 java/client/src/main/java/glide/api/commands/ListBaseCommands.java diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 67057a95d5..ea802cba10 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -20,6 +20,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; +import static redis_request.RedisRequestOuterClass.RequestType.LPop; +import static redis_request.RedisRequestOuterClass.RequestType.LPush; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; @@ -31,6 +33,7 @@ import glide.api.commands.GenericBaseCommands; import glide.api.commands.HashCommands; +import glide.api.commands.ListBaseCommands; import glide.api.commands.SetCommands; import glide.api.commands.StringCommands; import glide.api.models.commands.SetOptions; @@ -59,7 +62,12 @@ /** Base Client class for Redis */ @AllArgsConstructor public abstract class BaseClient - implements AutoCloseable, GenericBaseCommands, StringCommands, HashCommands, SetCommands { + implements AutoCloseable, + GenericBaseCommands, + StringCommands, + HashCommands, + ListBaseCommands, + SetCommands { /** Redis simple string response with "OK" */ public static final String OK = ConstantResponse.OK.toString(); @@ -323,6 +331,26 @@ public CompletableFuture hincrByFloat( this::handleDoubleResponse); } + @Override + public CompletableFuture lpush(@NonNull String key, @NonNull String[] elements) { + String[] arguments = ArrayUtils.addFirst(elements, key); + return commandManager.submitNewCommand(LPush, arguments, this::handleLongResponse); + } + + @Override + public CompletableFuture lpop(@NonNull String key) { + return commandManager.submitNewCommand( + LPop, new String[] {key}, this::handleStringOrNullResponse); + } + + @Override + public CompletableFuture lpopCount(@NonNull String key, long count) { + return commandManager.submitNewCommand( + LPop, + new String[] {key, Long.toString(count)}, + response -> castArray(handleArrayResponse(response), String.class)); + } + @Override public CompletableFuture sadd(String key, String[] members) { String[] arguments = ArrayUtils.addFirst(members, key); diff --git a/java/client/src/main/java/glide/api/commands/ListBaseCommands.java b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java new file mode 100644 index 0000000000..bae0a03b5a --- /dev/null +++ b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java @@ -0,0 +1,68 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.commands; + +import java.util.concurrent.CompletableFuture; + +/** + * List Commands interface for both standalone and cluster clients. + * + * @see List Commands + */ +public interface ListBaseCommands { + /** + * Inserts all the specified values at the head of the list stored at key. + * elements are inserted one after the other to the head of the list, from the leftmost + * element to the rightmost element. If key does not exist, it is created as an empty + * list before performing the push operations. + * + * @see redis.io for details. + * @param key The key of the list. + * @param elements The elements to insert at the head of the list stored at key. + * @return The length of the list after the push operations. + * @example + *
+     * Long pushCount1 = client.lpush("my_list", new String[] {"value1", "value2"}).get()
+     * assert pushCount1 == 2L
+     * Long pushCount2 = client.lpush("nonexistent_list", new String[] {"new_value"}).get()
+     * assert pushCount2 == 1L
+     * 
+ */ + CompletableFuture lpush(String key, String[] elements); + + /** + * Removes and returns the first elements of the list stored at key. The command pops + * a single element from the beginning of the list. + * + * @see redis.io for details. + * @param key The key of the list. + * @return The value of the first element.
+ * If key does not exist, null will be returned.
+ * @example + *
+     * String value1 = client.lpop("my_list").get()
+     * assert value1.equals("value1")
+     * String value2 = client.lpop("non_exiting_key").get()
+     * assert value2.equals(null)
+     * 
+ */ + CompletableFuture lpop(String key); + + /** + * Removes and returns up to count elements of the list stored at key, + * depending on the list's length. + * + * @see redis.io for details. + * @param key The key of the list. + * @param count The count of the elements to pop from the list. + * @return An array of the popped elements will be returned depending on the list's length.
+ * If key does not exist, null will be returned.
+ * @example + *
+     * String[] values1 = client.lpopCount("my_list", 2).get()
+     * assert values1.equals(new String[] {"value1", "value2"})
+     * String[] values2 = client.lpopCount("non_exiting_key" , 7).get()
+     * assert values2.equals(null)
+     * 
+ */ + CompletableFuture lpopCount(String key, long count); +} 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 096000d458..5e666cf8e2 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -20,6 +20,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.Info; +import static redis_request.RedisRequestOuterClass.RequestType.LPop; +import static redis_request.RedisRequestOuterClass.RequestType.LPush; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.Ping; @@ -465,6 +467,58 @@ public T hincrByFloat(@NonNull String key, @NonNull String field, double amount) return getThis(); } + /** + * Inserts all the specified values at the head of the list stored at key. + * elements are inserted one after the other to the head of the list, from the leftmost + * element to the rightmost element. If key does not exist, it is created as an empty + * list before performing the push operations. + * + * @see redis.io for details. + * @param key The key of the list. + * @param elements The elements to insert at the head of the list stored at key. + * @return Command Response - The length of the list after the push operations. + */ + public T lpush(@NonNull String key, @NonNull String[] elements) { + ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(elements, key)); + + protobufTransaction.addCommands(buildCommand(LPush, commandArgs)); + return getThis(); + } + + /** + * Removes and returns the first elements of the list stored at key. The command pops + * a single element from the beginning of the list. + * + * @see redis.io for details. + * @param key The key of the list. + * @return Command Response - The value of the first element.
+ * If key does not exist, null will be returned.
+ */ + public T lpop(@NonNull String key) { + ArgsArray commandArgs = buildArgs(key); + + protobufTransaction.addCommands(buildCommand(LPop, commandArgs)); + return getThis(); + } + + /** + * Removes and returns up to count elements of the list stored at key, + * depending on the list's length. + * + * @see redis.io for details. + * @param key The key of the list. + * @param count The count of the elements to pop from the list. + * @return Command Response - An array of the popped elements will be returned depending on the + * list's length.
+ * If key does not exist, null will be returned.
+ */ + public T lpopCount(@NonNull String key, long count) { + ArgsArray commandArgs = buildArgs(key, Long.toString(count)); + + protobufTransaction.addCommands(buildCommand(LPop, commandArgs)); + return getThis(); + } + /** * Add specified members to the set stored at key. Specified members that are already * a member of this set are ignored. diff --git a/java/client/src/main/java/glide/utils/ArrayTransformUtils.java b/java/client/src/main/java/glide/utils/ArrayTransformUtils.java index 091ee13a9b..81e812d5b8 100644 --- a/java/client/src/main/java/glide/utils/ArrayTransformUtils.java +++ b/java/client/src/main/java/glide/utils/ArrayTransformUtils.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.stream.Stream; +/** Utility methods for data conversion. */ public class ArrayTransformUtils { /** @@ -25,8 +26,9 @@ public static String[] convertMapToArgArray(Map args) { * * @param objectArr Array of objects to cast. * @param clazz The class of the array elements to cast to. - * @return An array of type T, containing the elements from the input array. - * @param The type to which the elements are cast. + * @return An array of type U, containing the elements from the input array. + * @param The base type from which the elements are being cast. + * @param The subtype of T to which the elements are cast. */ @SuppressWarnings("unchecked") public static U[] castArray(T[] objectArr, Class clazz) { diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 85b4ab7d17..c732babcc0 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -30,6 +30,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.Info; +import static redis_request.RedisRequestOuterClass.RequestType.LPop; +import static redis_request.RedisRequestOuterClass.RequestType.LPush; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.Ping; @@ -136,6 +138,28 @@ public void ping_with_message_returns_success() { assertEquals(message, pong); } + @SneakyThrows + @Test + public void select_returns_success() { + // setup + CompletableFuture testResponse = mock(CompletableFuture.class); + Long index = 5L; + when(testResponse.get()).thenReturn(OK); + + // match on protobuf request + when(commandManager.submitNewCommand( + eq(Select), eq(new String[] {Long.toString(index)}), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.select(index); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(OK, payload); + } + @SneakyThrows @Test public void del_returns_long_success() { @@ -727,24 +751,76 @@ public void hincrByFloat_returns_success() { @SneakyThrows @Test - public void select_returns_success() { + public void lpush_returns_success() { + // setup + String key = "testKey"; + String[] elements = new String[] {"value1", "value2"}; + String[] args = new String[] {key, "value1", "value2"}; + Long value = 2L; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(LPush), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.lpush(key, elements); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void lpop_returns_success() { // setup + String key = "testKey"; + String[] args = new String[] {key}; + String value = "value"; + CompletableFuture testResponse = mock(CompletableFuture.class); - Long index = 5L; - when(testResponse.get()).thenReturn(OK); + when(testResponse.get()).thenReturn(value); // match on protobuf request - when(commandManager.submitNewCommand( - eq(Select), eq(new String[] {Long.toString(index)}), any())) + when(commandManager.submitNewCommand(eq(LPop), eq(args), any())) .thenReturn(testResponse); // exercise - CompletableFuture response = service.select(index); + CompletableFuture response = service.lpop(key); String payload = response.get(); // verify assertEquals(testResponse, response); - assertEquals(OK, payload); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void lpopCount_returns_success() { + // setup + String key = "testKey"; + long count = 2L; + String[] args = new String[] {key, Long.toString(count)}; + String[] value = new String[] {"value1", "value2"}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(LPop), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.lpopCount(key, count); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); } @SneakyThrows 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 db21eef29c..42c7d1165a 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -20,6 +20,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.Info; +import static redis_request.RedisRequestOuterClass.RequestType.LPop; +import static redis_request.RedisRequestOuterClass.RequestType.LPush; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.Ping; @@ -149,6 +151,18 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) HashIncrByFloat, ArgsArray.newBuilder().addArgs("key").addArgs("field").addArgs("1.5").build())); + transaction.lpush("key", new String[] {"element1", "element2"}); + results.add( + Pair.of( + LPush, + ArgsArray.newBuilder().addArgs("key").addArgs("element1").addArgs("element2").build())); + + transaction.lpop("key"); + results.add(Pair.of(LPop, ArgsArray.newBuilder().addArgs("key").build())); + + transaction.lpopCount("key", 2); + results.add(Pair.of(LPop, ArgsArray.newBuilder().addArgs("key").addArgs("2").build())); + transaction.sadd("key", new String[] {"value"}); results.add(Pair.of(SAdd, ArgsArray.newBuilder().addArgs("key").addArgs("value").build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 5010a27261..549d7d8b40 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -539,6 +539,39 @@ public void hincrBy_hincrByFloat_type_error(BaseClient client) { assertTrue(hincrByFloatException.getCause() instanceof RequestException); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void lpush_lpop_existing_non_existing_key(BaseClient client) { + String key = UUID.randomUUID().toString(); + String[] valueArray = new String[] {"value4", "value3", "value2", "value1"}; + + assertEquals(4, client.lpush(key, valueArray).get()); + assertEquals("value1", client.lpop(key).get()); + assertArrayEquals(new String[] {"value2", "value3"}, client.lpopCount(key, 2).get()); + assertNull(client.lpop("non_existing_key").get()); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void lpush_lpop_type_error(BaseClient client) { + String key = UUID.randomUUID().toString(); + + assertEquals(OK, client.set(key, "foo").get()); + + Exception lpushException = + assertThrows(ExecutionException.class, () -> client.lpush(key, new String[] {"foo"}).get()); + assertTrue(lpushException.getCause() instanceof RequestException); + + Exception lpopException = assertThrows(ExecutionException.class, () -> client.lpop(key).get()); + assertTrue(lpopException.getCause() instanceof RequestException); + + Exception lpopCountException = + assertThrows(ExecutionException.class, () -> client.lpopCount(key, 2).get()); + assertTrue(lpopCountException.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 9785cfb162..48483fc9fc 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -13,8 +13,11 @@ public class TransactionTestUtilities { private static final String key3 = "{key}" + UUID.randomUUID(); private static final String key4 = "{key}" + UUID.randomUUID(); private static final String key5 = "{key}" + UUID.randomUUID(); + private static final String key6 = "{key}" + UUID.randomUUID(); + private static final String value1 = UUID.randomUUID().toString(); private static final String value2 = UUID.randomUUID().toString(); + private static final String value3 = UUID.randomUUID().toString(); private static final String field1 = UUID.randomUUID().toString(); private static final String field2 = UUID.randomUUID().toString(); private static final String field3 = UUID.randomUUID().toString(); @@ -58,10 +61,14 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.hincrBy(key4, field3, 5); baseTransaction.hincrByFloat(key4, field3, 5.5); - baseTransaction.sadd(key5, new String[] {"baz", "foo"}); - baseTransaction.srem(key5, new String[] {"foo"}); - baseTransaction.scard(key5); - baseTransaction.smembers(key5); + baseTransaction.lpush(key5, new String[] {value1, value2, value3}); + baseTransaction.lpop(key5); + baseTransaction.lpopCount(key5, 2); + + baseTransaction.sadd(key6, new String[] {"baz", "foo"}); + baseTransaction.srem(key6, new String[] {"foo"}); + baseTransaction.scard(key6); + baseTransaction.smembers(key6); return baseTransaction; } @@ -93,6 +100,9 @@ public static Object[] transactionTestResult() { 1L, 5L, 10.5, + 3L, + value3, + new String[] {value2, value1}, 2L, 1L, 1L, From 38626f41cf79dc0ffd0acc322297529793849dc5 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:14:19 -0800 Subject: [PATCH 21/65] Added Rpush, Rpop and RpopCount Commands. (List Commands) (#107) (#1034) * Added Rpush, Rpop and RpopCount Commands. (List Commands) (#107) --- .../src/main/java/glide/api/BaseClient.java | 22 ++++++ .../glide/api/commands/ListBaseCommands.java | 57 ++++++++++++++ .../glide/api/models/BaseTransaction.java | 53 +++++++++++++ .../test/java/glide/api/RedisClientTest.java | 76 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 11 +++ .../test/java/glide/SharedCommandTests.java | 30 ++++++++ .../java/glide/TransactionTestUtilities.java | 17 +++-- 7 files changed, 261 insertions(+), 5 deletions(-) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index ea802cba10..6d56840502 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -24,6 +24,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.LPush; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; +import static redis_request.RedisRequestOuterClass.RequestType.RPop; +import static redis_request.RedisRequestOuterClass.RequestType.RPush; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; import static redis_request.RedisRequestOuterClass.RequestType.SCard; import static redis_request.RedisRequestOuterClass.RequestType.SMembers; @@ -351,6 +353,26 @@ public CompletableFuture lpopCount(@NonNull String key, long count) { response -> castArray(handleArrayResponse(response), String.class)); } + @Override + public CompletableFuture rpush(@NonNull String key, @NonNull String[] elements) { + String[] arguments = ArrayUtils.addFirst(elements, key); + return commandManager.submitNewCommand(RPush, arguments, this::handleLongResponse); + } + + @Override + public CompletableFuture rpop(@NonNull String key) { + return commandManager.submitNewCommand( + RPop, new String[] {key}, this::handleStringOrNullResponse); + } + + @Override + public CompletableFuture rpopCount(@NonNull String key, long count) { + return commandManager.submitNewCommand( + RPop, + new String[] {key, Long.toString(count)}, + response -> castArray(handleArrayOrNullResponse(response), String.class)); + } + @Override public CompletableFuture sadd(String key, String[] members) { String[] arguments = ArrayUtils.addFirst(members, key); diff --git a/java/client/src/main/java/glide/api/commands/ListBaseCommands.java b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java index bae0a03b5a..989ec73785 100644 --- a/java/client/src/main/java/glide/api/commands/ListBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java @@ -9,6 +9,7 @@ * @see List Commands */ public interface ListBaseCommands { + /** * Inserts all the specified values at the head of the list stored at key. * elements are inserted one after the other to the head of the list, from the leftmost @@ -65,4 +66,60 @@ public interface ListBaseCommands { * */ CompletableFuture lpopCount(String key, long count); + + /** + * Inserts all the specified values at the tail of the list stored at key.
+ * elements are inserted one after the other to the tail of the list, from the + * leftmost element to the rightmost element. If key does not exist, it is created as + * an empty list before performing the push operations. + * + * @see redis.io for details. + * @param key The key of the list. + * @param elements The elements to insert at the tail of the list stored at key. + * @return The length of the list after the push operations. + * @example + *
+     * Long pushCount1 = client.rpush("my_list", new String[] {"value1", "value2"}).get()
+     * assert pushCount1 == 2L
+     * Long pushCount2 = client.rpush("nonexistent_list", new String[] {"new_value"}).get()
+     * assert pushCount2 == 1L
+     * 
+ */ + CompletableFuture rpush(String key, String[] elements); + + /** + * Removes and returns the last elements of the list stored at key.
+ * The command pops a single element from the end of the list. + * + * @see redis.io for details. + * @param key The key of the list. + * @return The value of the last element.
+ * If key does not exist, null will be returned.
+ * @example + *
+     * String value1 = client.rpop("my_list").get()
+     * assert value1.equals("value1")
+     * String value2 = client.rpop("non_exiting_key").get()
+     * assert value2.equals(null)
+     * 
+ */ + CompletableFuture rpop(String key); + + /** + * Removes and returns up to count elements from the list stored at key, + * depending on the list's length. + * + * @see redis.io for details. + * @param count The count of the elements to pop from the list. + * @returns An array of popped elements will be returned depending on the list's length.
+ * If key does not exist, null will be returned.
+ * @example + *
+     * String[] values1 = client.rpopCount("my_list", 2).get()
+     * assert values1.equals(new String[] {"value1", "value2"})
+     * String[] values2 = client.rpopCount("non_exiting_key" , 7).get()
+     * assert values2.equals(null)
+     * 
+ */ + CompletableFuture rpopCount(String key, long count); } 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 5e666cf8e2..7c18c3dec3 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -25,6 +25,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.Ping; +import static redis_request.RedisRequestOuterClass.RequestType.RPop; +import static redis_request.RedisRequestOuterClass.RequestType.RPush; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; import static redis_request.RedisRequestOuterClass.RequestType.SCard; import static redis_request.RedisRequestOuterClass.RequestType.SMembers; @@ -370,6 +372,57 @@ public T hdel(@NonNull String key, @NonNull String[] fields) { return getThis(); } + /** + * Inserts all the specified values at the tail of the list stored at key.
+ * elements are inserted one after the other to the tail of the list, from the + * leftmost element to the rightmost element. If key does not exist, it is created as + * an empty list before performing the push operations. + * + * @see redis.io for details. + * @param key The key of the list. + * @param elements The elements to insert at the tail of the list stored at key. + * @return Command Response - The length of the list after the push operations. + */ + public T rpush(@NonNull String key, @NonNull String[] elements) { + ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(elements, key)); + + protobufTransaction.addCommands(buildCommand(RPush, commandArgs)); + return getThis(); + } + + /** + * Removes and returns the last elements of the list stored at key.
+ * The command pops a single element from the end of the list. + * + * @see redis.io for details. + * @param key The key of the list. + * @return Command Response - The value of the last element.
+ * If key does not exist, null will be returned.
+ */ + public T rpop(@NonNull String key) { + ArgsArray commandArgs = buildArgs(key); + + protobufTransaction.addCommands(buildCommand(RPop, commandArgs)); + return getThis(); + } + + /** + * Removes and returns up to count elements from the list stored at key, + * depending on the list's length. + * + * @see redis.io for details. + * @param count The count of the elements to pop from the list. + * @returns Command Response - An array of popped elements will be returned depending on the + * list's length.
+ * If key does not exist, null will be returned.
+ */ + public T rpopCount(@NonNull String key, long count) { + ArgsArray commandArgs = buildArgs(key, Long.toString(count)); + + protobufTransaction.addCommands(buildCommand(RPop, commandArgs)); + return getThis(); + } + /** * Returns the values associated with the specified fields in the hash stored at key. * diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index c732babcc0..06d0a145e9 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -35,6 +35,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.Ping; +import static redis_request.RedisRequestOuterClass.RequestType.RPop; +import static redis_request.RedisRequestOuterClass.RequestType.RPush; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; import static redis_request.RedisRequestOuterClass.RequestType.SCard; import static redis_request.RedisRequestOuterClass.RequestType.SMembers; @@ -823,6 +825,80 @@ public void lpopCount_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void rpush_returns_success() { + // setup + String key = "testKey"; + String[] elements = new String[] {"value1", "value2"}; + String[] args = new String[] {key, "value1", "value2"}; + Long value = 2L; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(RPush), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.rpush(key, elements); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void rpop_returns_success() { + // setup + String key = "testKey"; + String value = "value"; + String[] args = new String[] {key}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(RPop), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.rpop(key); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void rpopCount_returns_success() { + // setup + String key = "testKey"; + long count = 2L; + String[] args = new String[] {key, Long.toString(count)}; + String[] value = new String[] {"value1", "value2"}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(RPop), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.rpopCount(key, count); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void sadd_returns_success() { diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index 42c7d1165a..c43a224016 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -25,6 +25,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.Ping; +import static redis_request.RedisRequestOuterClass.RequestType.RPop; +import static redis_request.RedisRequestOuterClass.RequestType.RPush; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; import static redis_request.RedisRequestOuterClass.RequestType.SCard; import static redis_request.RedisRequestOuterClass.RequestType.SMembers; @@ -163,6 +165,15 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.lpopCount("key", 2); results.add(Pair.of(LPop, ArgsArray.newBuilder().addArgs("key").addArgs("2").build())); + transaction.rpush("key", new String[] {"element"}); + results.add(Pair.of(RPush, ArgsArray.newBuilder().addArgs("key").addArgs("element").build())); + + transaction.rpop("key"); + results.add(Pair.of(RPop, ArgsArray.newBuilder().addArgs("key").build())); + + transaction.rpopCount("key", 2); + results.add(Pair.of(RPop, ArgsArray.newBuilder().addArgs("key").addArgs("2").build())); + transaction.sadd("key", new String[] {"value"}); results.add(Pair.of(SAdd, ArgsArray.newBuilder().addArgs("key").addArgs("value").build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 549d7d8b40..a2f13dc324 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -572,6 +572,36 @@ public void lpush_lpop_type_error(BaseClient client) { assertTrue(lpopCountException.getCause() instanceof RequestException); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void rpush_rpop_existing_non_existing_key(BaseClient client) { + String key = UUID.randomUUID().toString(); + String[] valueArray = new String[] {"value1", "value2", "value3", "value4"}; + + assertEquals(4, client.rpush(key, valueArray).get()); + assertEquals("value4", client.rpop(key).get()); + + assertArrayEquals(new String[] {"value3", "value2"}, client.rpopCount(key, 2).get()); + assertNull(client.rpop("non_existing_key").get()); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void rpush_rpop_type_error(BaseClient client) { + String key = UUID.randomUUID().toString(); + + assertEquals(OK, client.set(key, "foo").get()); + + Exception rpushException = + assertThrows(ExecutionException.class, () -> client.rpush(key, new String[] {"foo"}).get()); + assertTrue(rpushException.getCause() instanceof RequestException); + + Exception rpopException = assertThrows(ExecutionException.class, () -> client.rpop(key).get()); + assertTrue(rpopException.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 48483fc9fc..bcdbb8af78 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -14,7 +14,7 @@ public class TransactionTestUtilities { private static final String key4 = "{key}" + UUID.randomUUID(); private static final String key5 = "{key}" + UUID.randomUUID(); private static final String key6 = "{key}" + UUID.randomUUID(); - + private static final String key7 = "{key}" + UUID.randomUUID(); private static final String value1 = UUID.randomUUID().toString(); private static final String value2 = UUID.randomUUID().toString(); private static final String value3 = UUID.randomUUID().toString(); @@ -65,10 +65,14 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.lpop(key5); baseTransaction.lpopCount(key5, 2); - baseTransaction.sadd(key6, new String[] {"baz", "foo"}); - baseTransaction.srem(key6, new String[] {"foo"}); - baseTransaction.scard(key6); - baseTransaction.smembers(key6); + baseTransaction.rpush(key6, new String[] {value1, value2, value2}); + baseTransaction.rpop(key6); + baseTransaction.rpopCount(key6, 2); + + baseTransaction.sadd(key7, new String[] {"baz", "foo"}); + baseTransaction.srem(key7, new String[] {"foo"}); + baseTransaction.scard(key7); + baseTransaction.smembers(key7); return baseTransaction; } @@ -103,6 +107,9 @@ public static Object[] transactionTestResult() { 3L, value3, new String[] {value2, value1}, + 3L, + value2, + new String[] {value2, value1}, 2L, 1L, 1L, From 3fb2ebd4d15a0a96f1f7f017695d20fca8218110 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Mon, 26 Feb 2024 15:32:41 -0800 Subject: [PATCH 22/65] Update jedis sync client with thread pooling (#978) * Update jedis sync client with thread pooling (#86) * Add jedis threadpool for standalone client Signed-off-by: Andrew Carbonetto --------- Signed-off-by: Andrew Carbonetto * Java-Jedis: Only set the pool for standalone Signed-off-by: Andrew Carbonetto * Spotless Signed-off-by: Andrew Carbonetto * Rename field in jedis client Signed-off-by: Andrew Carbonetto --------- Signed-off-by: Andrew Carbonetto --- .../benchmarks/clients/jedis/JedisClient.java | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/java/benchmarks/src/main/java/glide/benchmarks/clients/jedis/JedisClient.java b/java/benchmarks/src/main/java/glide/benchmarks/clients/jedis/JedisClient.java index a79b757a2c..a2fb669334 100644 --- a/java/benchmarks/src/main/java/glide/benchmarks/clients/jedis/JedisClient.java +++ b/java/benchmarks/src/main/java/glide/benchmarks/clients/jedis/JedisClient.java @@ -6,18 +6,24 @@ import java.util.Set; import redis.clients.jedis.DefaultJedisClientConfig; import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisPool; -import redis.clients.jedis.commands.JedisCommands; /** A Jedis client with sync capabilities. See: https://github.com/redis/jedis */ public class JedisClient implements SyncClient { - - private JedisCommands jedis; + boolean isClusterMode; + private JedisPool jedisStandalonePool; + private JedisCluster jedisCluster; @Override public void closeConnection() { - // nothing to do + if (jedisCluster != null) { + jedisCluster.close(); + } + if (jedisStandalonePool != null) { + jedisStandalonePool.close(); + } } @Override @@ -27,27 +33,38 @@ public String getName() { @Override public void connectToRedis(ConnectionSettings connectionSettings) { - if (connectionSettings.clusterMode) { - jedis = + isClusterMode = connectionSettings.clusterMode; + if (isClusterMode) { + jedisCluster = new JedisCluster( Set.of(new HostAndPort(connectionSettings.host, connectionSettings.port)), DefaultJedisClientConfig.builder().ssl(connectionSettings.useSsl).build()); } else { - try (JedisPool pool = + jedisStandalonePool = new JedisPool( - connectionSettings.host, connectionSettings.port, connectionSettings.useSsl)) { - jedis = pool.getResource(); - } + connectionSettings.host, connectionSettings.port, connectionSettings.useSsl); } } @Override public void set(String key, String value) { - jedis.set(key, value); + if (isClusterMode) { + jedisCluster.set(key, value); + } else { + try (Jedis jedis = jedisStandalonePool.getResource()) { + jedis.set(key, value); + } + } } @Override public String get(String key) { - return jedis.get(key); + if (isClusterMode) { + return jedisCluster.get(key); + } else { + try (Jedis jedis = jedisStandalonePool.getResource()) { + return jedis.get(key); + } + } } } From 5d5a36cefcc667b0bd07efd6e3378732657409ce Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Tue, 27 Feb 2024 10:16:12 -0800 Subject: [PATCH 23/65] Java: Add UDS read/write error handling and tests with glide core mock (#949) Add UDS read/write error handling and tests with glide core mock. (#76) * Add UDS read/write error handling and tests with glide core mock. Signed-off-by: Yury-Fridlyand --- .../connectors/handlers/ChannelHandler.java | 36 +++- .../connectors/handlers/ReadHandler.java | 6 +- .../ConnectionWithGlideMockTests.java | 196 ++++++++++++++++++ .../glide/utils/RustCoreLibMockTestBase.java | 49 +++++ .../test/java/glide/utils/RustCoreMock.java | 189 +++++++++++++++++ 5 files changed, 469 insertions(+), 7 deletions(-) create mode 100644 java/client/src/test/java/glide/connection/ConnectionWithGlideMockTests.java create mode 100644 java/client/src/test/java/glide/utils/RustCoreLibMockTestBase.java create mode 100644 java/client/src/test/java/glide/utils/RustCoreMock.java diff --git a/java/client/src/main/java/glide/connectors/handlers/ChannelHandler.java b/java/client/src/main/java/glide/connectors/handlers/ChannelHandler.java index 2874fc13b8..32637219d1 100644 --- a/java/client/src/main/java/glide/connectors/handlers/ChannelHandler.java +++ b/java/client/src/main/java/glide/connectors/handlers/ChannelHandler.java @@ -6,8 +6,11 @@ import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; import io.netty.channel.unix.DomainSocketAddress; import java.util.concurrent.CompletableFuture; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; import redis_request.RedisRequestOuterClass.RedisRequest; import response.ResponseOuterClass.Response; @@ -17,8 +20,6 @@ */ public class ChannelHandler { - private static final String THREAD_POOL_NAME = "glide-channel"; - protected final Channel channel; protected final CallbackDispatcher callbackDispatcher; @@ -41,6 +42,8 @@ public ChannelHandler( .channel(threadPoolResource.getDomainSocketChannelClass()) .handler(new ProtobufSocketChannelInitializer(callbackDispatcher)) .connect(new DomainSocketAddress(socketPath)) + // TODO .addListener(new NettyFutureErrorHandler()) + // we need to use connection promise here for that ^ .sync() .channel(); this.callbackDispatcher = callbackDispatcher; @@ -58,9 +61,11 @@ public CompletableFuture write(RedisRequest.Builder request, boolean f request.setCallbackIdx(commandId.getKey()); if (flush) { - channel.writeAndFlush(request.build()); + channel + .writeAndFlush(request.build()) + .addListener(new NettyFutureErrorHandler(commandId.getValue())); } else { - channel.write(request.build()); + channel.write(request.build()).addListener(new NettyFutureErrorHandler(commandId.getValue())); } return commandId.getValue(); } @@ -73,7 +78,7 @@ public CompletableFuture write(RedisRequest.Builder request, boolean f */ public CompletableFuture connect(ConnectionRequest request) { var future = callbackDispatcher.registerConnection(); - channel.writeAndFlush(request); + channel.writeAndFlush(request).addListener(new NettyFutureErrorHandler(future)); return future; } @@ -82,4 +87,25 @@ public ChannelFuture close() { callbackDispatcher.shutdownGracefully(); return channel.close(); } + + /** + * Propagate an error from Netty's {@link ChannelFuture} and complete the {@link + * CompletableFuture} promise. + */ + @RequiredArgsConstructor + private static class NettyFutureErrorHandler implements ChannelFutureListener { + + private final CompletableFuture promise; + + @Override + public void operationComplete(@NonNull ChannelFuture channelFuture) throws Exception { + if (channelFuture.isCancelled()) { + promise.cancel(false); + } + var cause = channelFuture.cause(); + if (cause != null) { + promise.completeExceptionally(cause); + } + } + } } diff --git a/java/client/src/main/java/glide/connectors/handlers/ReadHandler.java b/java/client/src/main/java/glide/connectors/handlers/ReadHandler.java index 44e8d75a1c..29b7f4c01b 100644 --- a/java/client/src/main/java/glide/connectors/handlers/ReadHandler.java +++ b/java/client/src/main/java/glide/connectors/handlers/ReadHandler.java @@ -29,8 +29,10 @@ public void channelRead(@NonNull ChannelHandlerContext ctx, @NonNull Object msg) /** Handles uncaught exceptions from {@link #channelRead(ChannelHandlerContext, Object)}. */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + // TODO: log thru logger System.out.printf("=== exceptionCaught %s %s %n", ctx, cause); - cause.printStackTrace(System.err); - super.exceptionCaught(ctx, cause); + + callbackDispatcher.distributeClosingException( + "An unhandled error while reading from UDS channel: " + cause); } } diff --git a/java/client/src/test/java/glide/connection/ConnectionWithGlideMockTests.java b/java/client/src/test/java/glide/connection/ConnectionWithGlideMockTests.java new file mode 100644 index 0000000000..52be39bacc --- /dev/null +++ b/java/client/src/test/java/glide/connection/ConnectionWithGlideMockTests.java @@ -0,0 +1,196 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.connection; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import connection_request.ConnectionRequestOuterClass.ConnectionRequest; +import connection_request.ConnectionRequestOuterClass.NodeAddress; +import glide.api.RedisClient; +import glide.api.models.exceptions.ClosingException; +import glide.connectors.handlers.CallbackDispatcher; +import glide.connectors.handlers.ChannelHandler; +import glide.connectors.resources.Platform; +import glide.managers.CommandManager; +import glide.managers.ConnectionManager; +import glide.utils.RustCoreLibMockTestBase; +import glide.utils.RustCoreMock; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeoutException; +import lombok.SneakyThrows; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import redis_request.RedisRequestOuterClass.RedisRequest; +import response.ResponseOuterClass.Response; + +public class ConnectionWithGlideMockTests extends RustCoreLibMockTestBase { + + private ChannelHandler channelHandler = null; + + @BeforeEach + @SneakyThrows + public void createTestClient() { + channelHandler = + new ChannelHandler( + new CallbackDispatcher(), socketPath, Platform.getThreadPoolResourceSupplier().get()); + } + + @AfterEach + public void closeTestClient() { + channelHandler.close(); + } + + private Future testConnection() { + return channelHandler.connect(createConnectionRequest()); + } + + private static ConnectionRequest createConnectionRequest() { + return ConnectionRequest.newBuilder() + .addAddresses(NodeAddress.newBuilder().setHost("dummyhost").setPort(42).build()) + .build(); + } + + @BeforeAll + public static void init() { + startRustCoreLibMock(null); + } + + @Test + @SneakyThrows + // as of #710 https://github.com/aws/babushka/pull/710 - connection response is empty + public void can_connect_with_empty_response() { + RustCoreMock.updateGlideMock( + new RustCoreMock.GlideMockProtobuf() { + @Override + public Response connection(ConnectionRequest request) { + return Response.newBuilder().build(); + } + + @Override + public Response.Builder redisRequest(RedisRequest request) { + return null; + } + }); + + var connectionResponse = testConnection().get(); + assertAll( + () -> assertFalse(connectionResponse.hasClosingError()), + () -> assertFalse(connectionResponse.hasRequestError()), + () -> assertFalse(connectionResponse.hasRespPointer())); + } + + @Test + @SneakyThrows + public void can_connect_with_ok_response() { + RustCoreMock.updateGlideMock( + new RustCoreMock.GlideMockProtobuf() { + @Override + public Response connection(ConnectionRequest request) { + return OK().build(); + } + + @Override + public Response.Builder redisRequest(RedisRequest request) { + return null; + } + }); + + var connectionResponse = testConnection().get(); + assertAll( + () -> assertTrue(connectionResponse.hasConstantResponse()), + () -> assertFalse(connectionResponse.hasClosingError()), + () -> assertFalse(connectionResponse.hasRequestError()), + () -> assertFalse(connectionResponse.hasRespPointer())); + } + + @Test + public void cant_connect_when_no_response() { + RustCoreMock.updateGlideMock( + new RustCoreMock.GlideMockProtobuf() { + @Override + public Response connection(ConnectionRequest request) { + return null; + } + + @Override + public Response.Builder redisRequest(RedisRequest request) { + return null; + } + }); + + assertThrows(TimeoutException.class, () -> testConnection().get(1, SECONDS)); + } + + @Test + @SneakyThrows + public void cant_connect_when_negative_response() { + RustCoreMock.updateGlideMock( + new RustCoreMock.GlideMockProtobuf() { + @Override + public Response connection(ConnectionRequest request) { + return Response.newBuilder().setClosingError("You shall not pass!").build(); + } + + @Override + public Response.Builder redisRequest(RedisRequest request) { + return null; + } + }); + + var exception = assertThrows(ExecutionException.class, () -> testConnection().get(1, SECONDS)); + assertAll( + () -> assertTrue(exception.getCause() instanceof ClosingException), + () -> assertEquals("You shall not pass!", exception.getCause().getMessage())); + } + + @Test + @SneakyThrows + public void rethrow_error_on_read_when_malformed_packet_received() { + RustCoreMock.updateGlideMock(request -> new byte[] {-1}); + + var exception = assertThrows(ExecutionException.class, () -> testConnection().get(1, SECONDS)); + assertAll( + () -> assertTrue(exception.getCause() instanceof ClosingException), + () -> + assertTrue( + exception + .getCause() + .getMessage() + .contains("An unhandled error while reading from UDS channel"))); + } + + @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(1, SECONDS)); + assertTrue(exception.getCause() instanceof RuntimeException); + + // Not a public class, can't import + assertEquals( + "io.netty.channel.StacklessClosedChannelException", + exception.getCause().getCause().getClass().getName()); + } finally { + // restart mock to let other tests pass if this one failed + startRustCoreLibMock(null); + } + } + + private static class TestClient extends RedisClient { + + public TestClient(ChannelHandler channelHandler) { + super(new ConnectionManager(channelHandler), new CommandManager(channelHandler)); + } + } +} diff --git a/java/client/src/test/java/glide/utils/RustCoreLibMockTestBase.java b/java/client/src/test/java/glide/utils/RustCoreLibMockTestBase.java new file mode 100644 index 0000000000..ecf59e4a17 --- /dev/null +++ b/java/client/src/test/java/glide/utils/RustCoreLibMockTestBase.java @@ -0,0 +1,49 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.utils; + +import glide.connectors.handlers.ChannelHandler; +import glide.ffi.resolvers.SocketListenerResolver; +import lombok.SneakyThrows; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +public class RustCoreLibMockTestBase { + + /** + * Pass this socket path to {@link ChannelHandler} or mock {@link + * SocketListenerResolver#getSocket()} to return it. + */ + protected static String socketPath = null; + + @SneakyThrows + public static void startRustCoreLibMock(RustCoreMock.GlideMock rustCoreLibMock) { + assert socketPath == null + : "Previous `RustCoreMock` wasn't stopped. Ensure that your test class inherits" + + " `RustCoreLibMockTestBase`."; + + socketPath = RustCoreMock.start(rustCoreLibMock); + } + + @BeforeEach + public void preTestCheck() { + assert socketPath != null + : "You missed to call `startRustCoreLibMock` in a `@BeforeAll` method of your test class" + + " inherited from `RustCoreLibMockTestBase`."; + } + + @AfterEach + public void afterTestCheck() { + assert !RustCoreMock.failed() : "Error occurred in `RustCoreMock`"; + } + + @AfterAll + @SneakyThrows + public static void stopRustCoreLibMock() { + assert socketPath != null + : "You missed to call `startRustCoreLibMock` in a `@AfterAll` method of your test class" + + " inherited from `RustCoreLibMockTestBase`."; + RustCoreMock.stop(); + socketPath = null; + } +} diff --git a/java/client/src/test/java/glide/utils/RustCoreMock.java b/java/client/src/test/java/glide/utils/RustCoreMock.java new file mode 100644 index 0000000000..8ef787948e --- /dev/null +++ b/java/client/src/test/java/glide/utils/RustCoreMock.java @@ -0,0 +1,189 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.utils; + +import connection_request.ConnectionRequestOuterClass.ConnectionRequest; +import glide.connectors.resources.Platform; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.EpollServerDomainSocketChannel; +import io.netty.channel.kqueue.KQueueServerDomainSocketChannel; +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.channel.unix.DomainSocketChannel; +import io.netty.handler.codec.protobuf.ProtobufEncoder; +import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; +import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; +import java.nio.file.Files; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import redis_request.RedisRequestOuterClass.RedisRequest; +import response.ResponseOuterClass.ConstantResponse; +import response.ResponseOuterClass.Response; + +public class RustCoreMock { + + @FunctionalInterface + public interface GlideMock { + default boolean isRaw() { + return true; + } + + byte[] handle(byte[] request); + } + + public abstract static class GlideMockProtobuf implements GlideMock { + @Override + public boolean isRaw() { + return false; + } + + @Override + public byte[] handle(byte[] request) { + return new byte[0]; + } + + /** Return `null` to do not reply. */ + public abstract Response connection(ConnectionRequest request); + + /** Return `null` to do not reply. */ + public abstract Response.Builder redisRequest(RedisRequest request); + + public Response redisRequestWithCallbackId(RedisRequest request) { + var responseDraft = redisRequest(request); + return responseDraft == null + ? null + : responseDraft.setCallbackIdx(request.getCallbackIdx()).build(); + } + + public static Response.Builder OK() { + return Response.newBuilder().setConstantResponse(ConstantResponse.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; + + private final Channel channel; + + private final String socketPath; + + private static RustCoreMock instance; + + private GlideMock messageProcessor; + + /** Update {@link GlideMock} into a running {@link RustCoreMock}. */ + public static void updateGlideMock(GlideMock newMock) { + instance.messageProcessor = newMock; + } + + private final AtomicBoolean failed = new AtomicBoolean(false); + + /** Get and clear failure status. */ + public static boolean failed() { + return instance.failed.compareAndSet(true, false); + } + + @SneakyThrows + private RustCoreMock() { + var threadPoolResource = Platform.getThreadPoolResourceSupplier().get(); + socketPath = Files.createTempFile("GlideCoreMock", null).toString(); + group = threadPoolResource.getEventLoopGroup(); + channel = + new ServerBootstrap() + .group(group) + .channel( + Platform.getCapabilities().isEPollAvailable() + ? EpollServerDomainSocketChannel.class + : KQueueServerDomainSocketChannel.class) + .childHandler( + new ChannelInitializer() { + + @Override + protected void initChannel(DomainSocketChannel ch) throws Exception { + ch.pipeline() + // https://netty.io/4.1/api/io/netty/handler/codec/protobuf/ProtobufEncoder.html + .addLast("frameDecoder", new ProtobufVarint32FrameDecoder()) + .addLast("frameEncoder", new ProtobufVarint32LengthFieldPrepender()) + .addLast("protobufEncoder", new ProtobufEncoder()) + .addLast(new UdsServer(ch)); + } + }) + .bind(new DomainSocketAddress(socketPath)) + .syncUninterruptibly() + .channel(); + } + + public static String start(GlideMock messageProcessor) { + if (instance != null) { + stop(); + } + instance = new RustCoreMock(); + instance.messageProcessor = messageProcessor; + return instance.socketPath; + } + + @SneakyThrows + public static void stop() { + if (instance != null) { + instance.channel.close().syncUninterruptibly(); + instance.group.shutdownGracefully().get(5, TimeUnit.SECONDS); + instance = null; + } + } + + @RequiredArgsConstructor + private class UdsServer extends ChannelInboundHandlerAdapter { + + private final Channel ch; + + // This works with only one connected client. + // TODO Rework with `channelActive` override. + private final AtomicBoolean anybodyConnected = new AtomicBoolean(false); + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + var buf = (ByteBuf) msg; + var bytes = new byte[buf.readableBytes()]; + buf.readBytes(bytes); + buf.release(); + if (messageProcessor.isRaw()) { + ch.writeAndFlush(Unpooled.copiedBuffer(messageProcessor.handle(bytes))); + return; + } + var handler = (GlideMockProtobuf) messageProcessor; + Response response = null; + if (!anybodyConnected.get()) { + var connection = ConnectionRequest.parseFrom(bytes); + response = handler.connection(connection); + anybodyConnected.setPlain(true); + } else { + var request = RedisRequest.parseFrom(bytes); + response = handler.redisRequestWithCallbackId(request); + } + if (response != null) { + ctx.writeAndFlush(response); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + cause.printStackTrace(); + ctx.close(); + failed.setPlain(true); + } + } +} From f6184808424fb1aff961cb3cfd3bac68d1e923ac Mon Sep 17 00:00:00 2001 From: Shoham Elias <116083498+shohamazon@users.noreply.github.com> Date: Wed, 28 Feb 2024 16:07:46 +0200 Subject: [PATCH 24/65] Node: allow standalone tests to use cluster_manager.py script (#1025) --- node/tests/RedisClient.test.ts | 43 ++++++++++++--------------- node/tests/RedisClusterClient.test.ts | 2 +- node/tests/RedisModules.test.ts | 7 ++++- node/tests/SharedTests.ts | 2 +- node/tests/TestUtilities.ts | 11 +++++-- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/node/tests/RedisClient.test.ts b/node/tests/RedisClient.test.ts index 5c83c18917..451ab214a9 100644 --- a/node/tests/RedisClient.test.ts +++ b/node/tests/RedisClient.test.ts @@ -11,7 +11,6 @@ import { it, } from "@jest/globals"; import { BufferReader, BufferWriter } from "protobufjs"; -import RedisServer from "redis-server"; import { v4 as uuidv4 } from "uuid"; import { BaseClientConfiguration, @@ -21,41 +20,32 @@ import { } from ".."; import { redis_request } from "../src/ProtobufMessage"; import { runBaseTests } from "./SharedTests"; -import { flushallOnPort, transactionTest } from "./TestUtilities"; +import { RedisCluster, flushallOnPort, transactionTest } from "./TestUtilities"; /* eslint-disable @typescript-eslint/no-var-requires */ -const FreePort = require("find-free-port"); type Context = { client: RedisClient; }; -const PORT_NUMBER = 3000; +const TIMEOUT = 10000; describe("RedisClient", () => { - let server: RedisServer; + let testsFailed = 0; + let cluster: RedisCluster; let port: number; beforeAll(async () => { - port = await FreePort(PORT_NUMBER).then( - ([free_port]: number[]) => free_port - ); - server = await new Promise((resolve, reject) => { - const server = new RedisServer(port); - server.open(async (err: Error | null) => { - if (err) { - reject(err); - } - - resolve(server); - }); - }); - }); + cluster = await RedisCluster.createCluster(false, 1, 1); + port = cluster.ports()[0]; + }, 20000); afterEach(async () => { - await flushallOnPort(port); + await flushallOnPort(cluster.ports()[0]); }); - afterAll(() => { - server.close(); + afterAll(async () => { + if (testsFailed === 0) { + await cluster.close(); + } }); const getAddress = (port: number) => { @@ -204,12 +194,17 @@ describe("RedisClient", () => { const options = getOptions(port, protocol); options.protocol = protocol; options.clientName = clientName; + testsFailed += 1; const client = await RedisClient.createClient(options); - return { client, context: { client } }; }, - close: async (context: Context) => { + close: (context: Context, testSucceeded: boolean) => { + if (testSucceeded) { + testsFailed -= 1; + } + context.client.close(); }, + timeout: TIMEOUT, }); }); diff --git a/node/tests/RedisClusterClient.test.ts b/node/tests/RedisClusterClient.test.ts index 43533434b9..51aca01470 100644 --- a/node/tests/RedisClusterClient.test.ts +++ b/node/tests/RedisClusterClient.test.ts @@ -35,7 +35,7 @@ describe("RedisClusterClient", () => { let testsFailed = 0; let cluster: RedisCluster; beforeAll(async () => { - cluster = await RedisCluster.createCluster(3, 0); + cluster = await RedisCluster.createCluster(true, 3, 0); }, 20000); afterEach(async () => { diff --git a/node/tests/RedisModules.test.ts b/node/tests/RedisModules.test.ts index e6c1d33378..506c572c61 100644 --- a/node/tests/RedisModules.test.ts +++ b/node/tests/RedisModules.test.ts @@ -34,7 +34,12 @@ describe("RedisModules", () => { arg.startsWith("--load-module=") ); const loadModuleValues = loadModuleArgs.map((arg) => arg.split("=")[1]); - cluster = await RedisCluster.createCluster(3, 0, loadModuleValues); + cluster = await RedisCluster.createCluster( + true, + 3, + 0, + loadModuleValues + ); }, 20000); afterEach(async () => { diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index ef6ae73aa8..c4e5049df0 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1481,7 +1481,7 @@ export function runBaseTests(config: { "positiveInfinity" ) ).toEqual(0); - + expect(await client.set(key2, "foo")).toEqual("OK"); await expect( client.zcount(key2, "negativeInfinity", "positiveInfinity") diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index fadf6e5fde..70a0489a2d 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -144,9 +144,9 @@ export function transactionTest( baseTransaction.zcount(key8, { bound: 2 }, "positiveInfinity"); args.push(2); baseTransaction.zpopmin(key8); - args.push({"member2": 3.0}); + args.push({ member2: 3.0 }); baseTransaction.zpopmax(key8); - args.push({"member3": 3.5}); + args.push({ member3: 3.5 }); return args; } @@ -185,12 +185,17 @@ export class RedisCluster { } public static createCluster( + cluster_mode: boolean, shardCount: number, replicaCount: number, loadModule?: string[] ): Promise { return new Promise((resolve, reject) => { - let command = `python3 ../utils/cluster_manager.py start --cluster-mode -r ${replicaCount} -n ${shardCount}`; + let command = `python3 ../utils/cluster_manager.py start -r ${replicaCount} -n ${shardCount}`; + + if (cluster_mode) { + command += " --cluster-mode"; + } if (loadModule) { if (loadModule.length === 0) { From ae492c6609ecf912d2978de282f83887b90d283d Mon Sep 17 00:00:00 2001 From: Shachar Langbeheim <98546660+shachlanAmazon@users.noreply.github.com> Date: Wed, 28 Feb 2024 16:14:16 +0200 Subject: [PATCH 25/65] Remove unused dependencies. (#1047) Co-authored-by: nihohit --- csharp/lib/Cargo.toml | 3 --- glide-core/Cargo.toml | 1 - python/Cargo.toml | 3 --- 3 files changed, 7 deletions(-) diff --git a/csharp/lib/Cargo.toml b/csharp/lib/Cargo.toml index f0f9f0889c..6bf4183914 100644 --- a/csharp/lib/Cargo.toml +++ b/csharp/lib/Cargo.toml @@ -15,10 +15,7 @@ crate-type = ["cdylib"] redis = { path = "../../submodules/redis-rs/redis", features = ["aio", "tokio-comp","tokio-native-tls-comp"] } glide-core = { path = "../../glide-core" } tokio = { version = "^1", features = ["rt", "macros", "rt-multi-thread", "time"] } -num-derive = "0.4.0" -num-traits = "0.2.15" logger_core = {path = "../../logger_core"} -tracing-subscriber = "0.3.16" [profile.release] lto = true diff --git a/glide-core/Cargo.toml b/glide-core/Cargo.toml index 5f4a1fe097..55a0e82fee 100644 --- a/glide-core/Cargo.toml +++ b/glide-core/Cargo.toml @@ -10,7 +10,6 @@ authors = ["Amazon Web Services"] [dependencies] bytes = "^1.3" futures = "^0.3" -num-traits = "^0.2" redis = { path = "../submodules/redis-rs/redis", features = ["aio", "tokio-comp", "tokio-rustls-comp", "connection-manager","cluster", "cluster-async"] } signal-hook = "^0.3" signal-hook-tokio = {version = "^0.3", features = ["futures-v0_3"] } diff --git a/python/Cargo.toml b/python/Cargo.toml index 24c14fbbc9..d8a0e3c6a8 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -12,12 +12,9 @@ crate-type = ["cdylib"] [dependencies] pyo3 = { version = "^0.20", features = ["extension-module", "num-bigint"] } -pyo3-asyncio = { version = "^0.20", features = ["tokio-runtime"] } redis = { path = "../submodules/redis-rs/redis", features = ["aio", "tokio-comp", "connection-manager","tokio-rustls-comp"] } glide-core = { path = "../glide-core" } -tokio = { version = "^1", features = ["rt", "macros", "rt-multi-thread", "time"] } logger_core = {path = "../logger_core"} -tracing-subscriber = "0.3.16" [package.metadata.maturin] python-source = "python" From 9aab042a5e8ed883c827c282f771fd1ef7604f92 Mon Sep 17 00:00:00 2001 From: ort-bot Date: Wed, 28 Feb 2024 02:43:07 +0000 Subject: [PATCH 26/65] Updated attribution files --- glide-core/THIRD_PARTY_LICENSES_RUST | 10 +++++----- node/THIRD_PARTY_LICENSES_NODE | 14 +++++++------- python/THIRD_PARTY_LICENSES_PYTHON | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/glide-core/THIRD_PARTY_LICENSES_RUST b/glide-core/THIRD_PARTY_LICENSES_RUST index f263209929..38a1491785 100644 --- a/glide-core/THIRD_PARTY_LICENSES_RUST +++ b/glide-core/THIRD_PARTY_LICENSES_RUST @@ -683,7 +683,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ahash:0.8.9 +Package: ahash:0.8.10 The following copyrights and licenses were found in the source code of this package: @@ -21627,7 +21627,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: protobuf:3.3.0 +Package: protobuf:3.4.0 The following copyrights and licenses were found in the source code of this package: @@ -21652,7 +21652,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: protobuf-support:3.3.0 +Package: protobuf-support:3.4.0 The following copyrights and licenses were found in the source code of this package: @@ -24081,7 +24081,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pki-types:1.3.0 +Package: rustls-pki-types:1.3.1 The following copyrights and licenses were found in the source code of this package: @@ -27472,7 +27472,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.50 +Package: syn:2.0.51 The following copyrights and licenses were found in the source code of this package: diff --git a/node/THIRD_PARTY_LICENSES_NODE b/node/THIRD_PARTY_LICENSES_NODE index d0a094c8ba..43f5ee6da1 100644 --- a/node/THIRD_PARTY_LICENSES_NODE +++ b/node/THIRD_PARTY_LICENSES_NODE @@ -683,7 +683,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ahash:0.8.9 +Package: ahash:0.8.10 The following copyrights and licenses were found in the source code of this package: @@ -6403,7 +6403,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ctor:0.2.6 +Package: ctor:0.2.7 The following copyrights and licenses were found in the source code of this package: @@ -22336,7 +22336,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: protobuf:3.3.0 +Package: protobuf:3.4.0 The following copyrights and licenses were found in the source code of this package: @@ -22361,7 +22361,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: protobuf-support:3.3.0 +Package: protobuf-support:3.4.0 The following copyrights and licenses were found in the source code of this package: @@ -25477,7 +25477,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pki-types:1.3.0 +Package: rustls-pki-types:1.3.1 The following copyrights and licenses were found in the source code of this package: @@ -29097,7 +29097,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.50 +Package: syn:2.0.51 The following copyrights and licenses were found in the source code of this package: @@ -41602,7 +41602,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -Package: @types:node:20.11.20 +Package: @types:node:20.11.21 The following copyrights and licenses were found in the source code of this package: diff --git a/python/THIRD_PARTY_LICENSES_PYTHON b/python/THIRD_PARTY_LICENSES_PYTHON index c78134feed..bd7f92a2d7 100644 --- a/python/THIRD_PARTY_LICENSES_PYTHON +++ b/python/THIRD_PARTY_LICENSES_PYTHON @@ -683,7 +683,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ahash:0.8.9 +Package: ahash:0.8.10 The following copyrights and licenses were found in the source code of this package: @@ -22547,7 +22547,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: protobuf:3.3.0 +Package: protobuf:3.4.0 The following copyrights and licenses were found in the source code of this package: @@ -22572,7 +22572,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: protobuf-support:3.3.0 +Package: protobuf-support:3.4.0 The following copyrights and licenses were found in the source code of this package: @@ -26354,7 +26354,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pki-types:1.3.0 +Package: rustls-pki-types:1.3.1 The following copyrights and licenses were found in the source code of this package: @@ -29745,7 +29745,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.50 +Package: syn:2.0.51 The following copyrights and licenses were found in the source code of this package: @@ -41650,7 +41650,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: cachetools:5.3.2 +Package: cachetools:5.3.3 The following copyrights and licenses were found in the source code of this package: From 87faffa17d090dc80890736ac3f034298de8ba71 Mon Sep 17 00:00:00 2001 From: ort-bot Date: Thu, 29 Feb 2024 00:18:26 +0000 Subject: [PATCH 27/65] Updated attribution files --- glide-core/THIRD_PARTY_LICENSES_RUST | 24 +-- node/THIRD_PARTY_LICENSES_NODE | 32 ++-- python/THIRD_PARTY_LICENSES_PYTHON | 232 ++------------------------- 3 files changed, 40 insertions(+), 248 deletions(-) diff --git a/glide-core/THIRD_PARTY_LICENSES_RUST b/glide-core/THIRD_PARTY_LICENSES_RUST index 38a1491785..a1f0658144 100644 --- a/glide-core/THIRD_PARTY_LICENSES_RUST +++ b/glide-core/THIRD_PARTY_LICENSES_RUST @@ -5816,7 +5816,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crossbeam-channel:0.5.11 +Package: crossbeam-channel:0.5.12 The following copyrights and licenses were found in the source code of this package: @@ -12482,7 +12482,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: hermit-abi:0.3.8 +Package: hermit-abi:0.3.9 The following copyrights and licenses were found in the source code of this package: @@ -15751,7 +15751,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: log:0.4.20 +Package: log:0.4.21 The following copyrights and licenses were found in the source code of this package: @@ -27472,7 +27472,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.51 +Package: syn:2.0.52 The following copyrights and licenses were found in the source code of this package: @@ -34498,7 +34498,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.52.3 +Package: windows-targets:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -34956,7 +34956,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.52.3 +Package: windows_aarch64_gnullvm:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -35414,7 +35414,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.52.3 +Package: windows_aarch64_msvc:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -35872,7 +35872,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.52.3 +Package: windows_i686_gnu:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -36330,7 +36330,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.52.3 +Package: windows_i686_msvc:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -36788,7 +36788,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.52.3 +Package: windows_x86_64_gnu:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -37246,7 +37246,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnullvm:0.52.3 +Package: windows_x86_64_gnullvm:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -37704,7 +37704,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_msvc:0.52.3 +Package: windows_x86_64_msvc:0.52.4 The following copyrights and licenses were found in the source code of this package: diff --git a/node/THIRD_PARTY_LICENSES_NODE b/node/THIRD_PARTY_LICENSES_NODE index 43f5ee6da1..81ea45dbfb 100644 --- a/node/THIRD_PARTY_LICENSES_NODE +++ b/node/THIRD_PARTY_LICENSES_NODE @@ -5945,7 +5945,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crossbeam-channel:0.5.11 +Package: crossbeam-channel:0.5.12 The following copyrights and licenses were found in the source code of this package: @@ -13048,7 +13048,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: hermit-abi:0.3.8 +Package: hermit-abi:0.3.9 The following copyrights and licenses were found in the source code of this package: @@ -16335,7 +16335,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: log:0.4.20 +Package: log:0.4.21 The following copyrights and licenses were found in the source code of this package: @@ -17098,7 +17098,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: napi:2.15.4 +Package: napi:2.16.0 The following copyrights and licenses were found in the source code of this package: @@ -17148,7 +17148,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: napi-derive:2.15.3 +Package: napi-derive:2.16.0 The following copyrights and licenses were found in the source code of this package: @@ -17173,7 +17173,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: napi-derive-backend:1.0.61 +Package: napi-derive-backend:1.0.62 The following copyrights and licenses were found in the source code of this package: @@ -29097,7 +29097,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.51 +Package: syn:2.0.52 The following copyrights and licenses were found in the source code of this package: @@ -36810,7 +36810,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.52.3 +Package: windows-targets:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -37268,7 +37268,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.52.3 +Package: windows_aarch64_gnullvm:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -37726,7 +37726,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.52.3 +Package: windows_aarch64_msvc:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -38184,7 +38184,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.52.3 +Package: windows_i686_gnu:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -38642,7 +38642,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.52.3 +Package: windows_i686_msvc:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -39100,7 +39100,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.52.3 +Package: windows_x86_64_gnu:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -39558,7 +39558,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnullvm:0.52.3 +Package: windows_x86_64_gnullvm:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -40016,7 +40016,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_msvc:0.52.3 +Package: windows_x86_64_msvc:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -41602,7 +41602,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -Package: @types:node:20.11.21 +Package: @types:node:20.11.22 The following copyrights and licenses were found in the source code of this package: diff --git a/python/THIRD_PARTY_LICENSES_PYTHON b/python/THIRD_PARTY_LICENSES_PYTHON index bd7f92a2d7..dd4b8d6c88 100644 --- a/python/THIRD_PARTY_LICENSES_PYTHON +++ b/python/THIRD_PARTY_LICENSES_PYTHON @@ -5816,7 +5816,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: crossbeam-channel:0.5.11 +Package: crossbeam-channel:0.5.12 The following copyrights and licenses were found in the source code of this package: @@ -12919,7 +12919,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: hermit-abi:0.3.8 +Package: hermit-abi:0.3.9 The following copyrights and licenses were found in the source code of this package: @@ -16417,7 +16417,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: log:0.4.20 +Package: log:0.4.21 The following copyrights and licenses were found in the source code of this package: @@ -22826,214 +22826,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pyo3-asyncio:0.20.0 - -The following copyrights and licenses were found in the source code of this package: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ----- - Package: pyo3-build-config:0.20.3 The following copyrights and licenses were found in the source code of this package: @@ -29745,7 +29537,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: syn:2.0.51 +Package: syn:2.0.52 The following copyrights and licenses were found in the source code of this package: @@ -37224,7 +37016,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows-targets:0.52.3 +Package: windows-targets:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -37682,7 +37474,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_gnullvm:0.52.3 +Package: windows_aarch64_gnullvm:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -38140,7 +37932,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_aarch64_msvc:0.52.3 +Package: windows_aarch64_msvc:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -38598,7 +38390,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_gnu:0.52.3 +Package: windows_i686_gnu:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -39056,7 +38848,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_i686_msvc:0.52.3 +Package: windows_i686_msvc:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -39514,7 +39306,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnu:0.52.3 +Package: windows_x86_64_gnu:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -39972,7 +39764,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_gnullvm:0.52.3 +Package: windows_x86_64_gnullvm:0.52.4 The following copyrights and licenses were found in the source code of this package: @@ -40430,7 +40222,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: windows_x86_64_msvc:0.52.3 +Package: windows_x86_64_msvc:0.52.4 The following copyrights and licenses were found in the source code of this package: From c9fa80dbedbbcc2b43c01852bb1aa19c0820a0f8 Mon Sep 17 00:00:00 2001 From: Shoham Elias Date: Tue, 13 Feb 2024 17:06:18 +0000 Subject: [PATCH 28/65] Python: adds HSETNX command --- CHANGELOG.md | 1 + glide-core/src/client/value_conversion.rs | 2 +- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/socket_listener.rs | 1 + python/python/glide/async_commands/core.py | 37 +++++++++++++++++-- .../glide/async_commands/transaction.py | 22 +++++++++++ python/python/tests/test_async_client.py | 14 +++++++ python/python/tests/test_transaction.py | 3 +- 8 files changed, 76 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b2a4b4025..7cc795c97f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ #### Changes - Node: Allow routing Cluster requests by address. ([#1021](https://github.com/aws/glide-for-redis/pull/1021)) +* Python: Added HSETNX command. ([#954](https://github.com/aws/glide-for-redis/pull/954)) ## 0.2.0 (2024-02-11) diff --git a/glide-core/src/client/value_conversion.rs b/glide-core/src/client/value_conversion.rs index c4864310d0..2aca949d34 100644 --- a/glide-core/src/client/value_conversion.rs +++ b/glide-core/src/client/value_conversion.rs @@ -201,7 +201,7 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option { Some(ExpectedReturnType::Map) } b"INCRBYFLOAT" | b"HINCRBYFLOAT" => Some(ExpectedReturnType::Double), - b"HEXISTS" | b"EXPIRE" | b"EXPIREAT" | b"PEXPIRE" | b"PEXPIREAT" => { + b"HEXISTS" | b"HSETNX" | b"EXPIRE" | b"EXPIREAT" | b"PEXPIRE" | b"PEXPIREAT" => { Some(ExpectedReturnType::Boolean) } b"SMEMBERS" => Some(ExpectedReturnType::Set), diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 0389ce82d3..7b413654d4 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -123,6 +123,7 @@ enum RequestType { XTrim = 79; XGroupCreate = 80; XGroupDestroy = 81; + HSetNX = 82; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index d925c80172..de9ce806dc 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -360,6 +360,7 @@ fn get_command(request: &Command) -> Option { RequestType::XGroupCreate => Some(get_two_word_command("XGROUP", "CREATE")), RequestType::XGroupDestroy => Some(get_two_word_command("XGROUP", "DESTROY")), RequestType::XTrim => Some(cmd("XTRIM")), + RequestType::HSetNX => Some(cmd("HSETNX")), } } diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index a0d4c71e25..7ddfededa3 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -419,7 +419,7 @@ async def hset(self, key: str, field_value_map: Mapping[str, str]) -> int: int: The number of fields that were added to the hash. Example: - >>> hset("my_hash", {"field": "value", "field2": "value2"}) + >>> await client.hset("my_hash", {"field": "value", "field2": "value2"}) 2 """ field_value_list: List[str] = [key] @@ -444,9 +444,9 @@ async def hget(self, key: str, field: str) -> Optional[str]: Returns None if `field` is not presented in the hash or `key` does not exist. Examples: - >>> hget("my_hash", "field") + >>> await client.hget("my_hash", "field") "value" - >>> hget("my_hash", "nonexistent_field") + >>> await client.hget("my_hash", "nonexistent_field") None """ return cast( @@ -454,6 +454,37 @@ async def hget(self, key: str, field: str) -> Optional[str]: await self._execute_command(RequestType.HashGet, [key, field]), ) + async def hsetnx( + self, + key: str, + field: str, + value: str, + ) -> bool: + """ + Sets `field` in the hash stored at `key` to `value`, only if `field` does not yet exist. + If `key` does not exist, a new key holding a hash is created. + If `field` already exists, this operation has no effect. + See https://redis.io/commands/hsetnx/ for more details. + + Args: + key (str): The key of the hash. + field (str): The field to set the value for. + value (str): The value to set. + + Returns: + bool: True if the field was set, False if the field already existed and was not set. + + Examples: + >>> await client.hsetnx("my_hash", "field", "value") + True # Indicates that the field "field" was set successfully in the hash "my_hash". + >>> await client.hsetnx("my_hash", "field", "new_value") + False # Indicates that the field "field" already existed in the hash "my_hash" and was not set again. + """ + return cast( + bool, + await self._execute_command(RequestType.HSetNX, [key, field, value]), + ) + async def hincrby(self, key: str, field: str, amount: int) -> int: """ Increment or decrement the value of a `field` in the hash stored at `key` by the specified amount. diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 89550c5bee..ee52e5cb94 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -373,6 +373,28 @@ def hget(self: TTransaction, key: str, field: str) -> TTransaction: """ return self.append_command(RequestType.HashGet, [key, field]) + def hsetnx( + self: TTransaction, + key: str, + field: str, + value: str, + ) -> TTransaction: + """ + Sets `field` in the hash stored at `key` to `value`, only if `field` does not yet exist. + If `key` does not exist, a new key holding a hash is created. + If `field` already exists, this operation has no effect. + See https://redis.io/commands/hsetnx/ for more details. + + Args: + key (str): The key of the hash. + field (str): The field to set the value for. + value (str): The value to set. + + Commands response: + bool: True if the field was set, False if the field already existed and was not set. + """ + return self.append_command(RequestType.HSetNX, [key, field, value]) + def hincrby(self: TTransaction, key: str, field: str, amount: int) -> TTransaction: """ Increment or decrement the value of a `field` in the hash stored at `key` by the specified amount. diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 029e73e510..c17f1fde94 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -634,6 +634,20 @@ async def test_hdel(self, redis_client: TRedisClient): assert await redis_client.hdel(key, ["nonExistingField"]) == 0 assert await redis_client.hdel("nonExistingKey", [field3]) == 0 + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_hsetnx(self, redis_client: TRedisClient): + key = get_random_string(10) + field = get_random_string(5) + + assert await redis_client.hsetnx(key, field, "value") == True + assert await redis_client.hsetnx(key, field, "new value") == False + assert await redis_client.hget(key, field) == "value" + key = get_random_string(5) + assert await redis_client.set(key, "value") == OK + with pytest.raises(RequestError): + await redis_client.hsetnx(key, field, "value") + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_hmget(self, redis_client: TRedisClient): diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 2e0bef1f74..ee242962dc 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -86,7 +86,8 @@ def transaction_test( args.append(value2) transaction.hlen(key4) args.append(2) - + transaction.hsetnx(key4, key, value) + args.append(False) transaction.hincrby(key4, key3, 5) args.append(5) transaction.hincrbyfloat(key4, key3, 5.5) From 3809a28530c474aba1256cf71f1e0681cce07ece Mon Sep 17 00:00:00 2001 From: Shoham Elias <116083498+shohamazon@users.noreply.github.com> Date: Thu, 29 Feb 2024 15:23:55 +0200 Subject: [PATCH 29/65] Python: adds SISMEMBER command (#972) --- CHANGELOG.md | 3 +- glide-core/src/client/value_conversion.rs | 5 ++-- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/socket_listener.rs | 1 + python/python/glide/async_commands/core.py | 29 +++++++++++++++++++ .../glide/async_commands/transaction.py | 20 +++++++++++++ python/python/tests/test_async_client.py | 10 +++++++ python/python/tests/test_transaction.py | 2 ++ 8 files changed, 67 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cc795c97f..9bf984c3b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Node: Allow routing Cluster requests by address. ([#1021](https://github.com/aws/glide-for-redis/pull/1021)) * Python: Added HSETNX command. ([#954](https://github.com/aws/glide-for-redis/pull/954)) +* Python: Added SISMEMBER command ([#971](https://github.com/aws/glide-for-redis/pull/971)) ## 0.2.0 (2024-02-11) @@ -16,7 +17,7 @@ * Python, Node: When recieving LPOP/RPOP with count, convert result to Array. ([#811](https://github.com/aws/glide-for-redis/pull/811)) * Python, Node: Added TYPE command ([#945](https://github.com/aws/glide-for-redis/pull/945), [#980](https://github.com/aws/glide-for-redis/pull/980)) * Python, Node: Added HLEN command ([#944](https://github.com/aws/glide-for-redis/pull/944), [#981](https://github.com/aws/glide-for-redis/pull/981)) -* Node: Added ZCOUNT command ([#909](https://github.com/aws/glide-for-redis/pull/909)) +* Python, Node: Added ZCOUNT command ([#878](https://github.com/aws/glide-for-redis/pull/878)) ([#909](https://github.com/aws/glide-for-redis/pull/909)) * Python: Added ECHO command ([#953](https://github.com/aws/glide-for-redis/pull/953)) * Python, Node: Added ZPOPMIN command ([#975](https://github.com/aws/glide-for-redis/pull/975), [#1008](https://github.com/aws/glide-for-redis/pull/1008)) * Node: Added STRLEN command ([#993](https://github.com/aws/glide-for-redis/pull/993)) diff --git a/glide-core/src/client/value_conversion.rs b/glide-core/src/client/value_conversion.rs index 2aca949d34..73a886e994 100644 --- a/glide-core/src/client/value_conversion.rs +++ b/glide-core/src/client/value_conversion.rs @@ -201,9 +201,8 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option { Some(ExpectedReturnType::Map) } b"INCRBYFLOAT" | b"HINCRBYFLOAT" => Some(ExpectedReturnType::Double), - b"HEXISTS" | b"HSETNX" | b"EXPIRE" | b"EXPIREAT" | b"PEXPIRE" | b"PEXPIREAT" => { - Some(ExpectedReturnType::Boolean) - } + b"HEXISTS" | b"HSETNX" | b"EXPIRE" | b"EXPIREAT" | b"PEXPIRE" | b"PEXPIREAT" + | b"SISMEMBER" => Some(ExpectedReturnType::Boolean), b"SMEMBERS" => Some(ExpectedReturnType::Set), b"ZSCORE" => Some(ExpectedReturnType::DoubleOrNull), b"ZPOPMIN" | b"ZPOPMAX" => Some(ExpectedReturnType::MapOfStringToDouble), diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 7b413654d4..bfaf1bbe36 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -124,6 +124,7 @@ enum RequestType { XGroupCreate = 80; XGroupDestroy = 81; HSetNX = 82; + SIsMember = 83; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index de9ce806dc..ba24a93a44 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -361,6 +361,7 @@ fn get_command(request: &Command) -> Option { RequestType::XGroupDestroy => Some(get_two_word_command("XGROUP", "DESTROY")), RequestType::XTrim => Some(cmd("XTRIM")), RequestType::HSetNX => Some(cmd("HSETNX")), + RequestType::SIsMember => Some(cmd("SISMEMBER")), } } diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 7ddfededa3..fa51137e66 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -902,6 +902,35 @@ async def scard(self, key: str) -> int: """ return cast(int, await self._execute_command(RequestType.SCard, [key])) + async def sismember( + self, + key: str, + member: str, + ) -> bool: + """ + Returns if `member` is a member of the set stored at `key`. + + See https://redis.io/commands/sismember/ for more details. + + Args: + key (str): The key of the set. + member (str): The member to check for existence in the set. + + Returns: + bool: True if the member exists in the set, False otherwise. + If `key` doesn't exist, it is treated as an empty set and the command returns False. + + Examples: + >>> await client.sismember("my_set", "member1") + True # Indicates that "member1" exists in the set "my_set". + >>> await client.sismember("my_set", "non_existing_member") + False # Indicates that "non_existing_member" does not exist in the set "my_set". + """ + return cast( + bool, + await self._execute_command(RequestType.SIsMember, [key, member]), + ) + async def ltrim(self, key: str, start: int, end: int) -> TOK: """ Trim an existing list so that it will contain only the specified range of elements specified. diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index ee52e5cb94..ed3ba10172 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -695,6 +695,26 @@ def scard(self: TTransaction, key: str) -> TTransaction: """ return self.append_command(RequestType.SCard, [key]) + def sismember( + self: TTransaction, + key: str, + member: str, + ) -> TTransaction: + """ + Returns if `member` is a member of the set stored at `key`. + + See https://redis.io/commands/sismember/ for more details. + + Args: + key (str): The key of the set. + member (str): The member to check for existence in the set. + + Commands response: + bool: True if the member exists in the set, False otherwise. + If `key` doesn't exist, it is treated as an empty set and the command returns False. + """ + return self.append_command(RequestType.SIsMember, [key, member]) + def ltrim(self: TTransaction, key: str, start: int, end: int) -> TTransaction: """ Trim an existing list so that it will contain only the specified range of elements specified. diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index c17f1fde94..5fff8c965e 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -857,6 +857,16 @@ async def test_sadd_srem_smembers_scard_wrong_type_raise_error( await redis_client.smembers(key) assert "Operation against a key holding the wrong kind of value" in str(e) + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_sismember(self, redis_client: TRedisClient): + key = get_random_string(10) + member = get_random_string(5) + assert await redis_client.sadd(key, [member]) == 1 + assert await redis_client.sismember(key, member) + assert not await redis_client.sismember(key, get_random_string(5)) + assert not await redis_client.sismember("non_existing_key", member) + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_ltrim(self, redis_client: TRedisClient): diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index ee242962dc..621e2b1751 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -135,6 +135,8 @@ def transaction_test( args.append({"bar"}) transaction.scard(key7) args.append(1) + transaction.sismember(key7, "bar") + args.append(True) transaction.zadd(key8, {"one": 1, "two": 2, "three": 3}) args.append(3) From 5c8bc7a4007b7ae28beb4264ada9017f2cb28092 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Thu, 29 Feb 2024 10:16:50 -0800 Subject: [PATCH 30/65] C#: rework CI to run tests and minimal benchmark (#108) (#1044) * Update CI. --------- Signed-off-by: Yury-Fridlyand --- .github/workflows/csharp.yml | 48 ++++++++++++++----- .../install-shared-dependencies/action.yml | 2 +- csharp/.gitignore | 1 + 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index e1dfcc5c86..eeae290b73 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -8,12 +8,20 @@ on: - glide-core/src/** - submodules/** - .github/workflows/csharp.yml + - .github/workflows/install-shared-dependencies/action.yml + - .github/workflows/install-redis/action.yml + - .github/workflows/test-benchmark/action.yml + - .github/workflows/lint-rust/action.yml pull_request: paths: - csharp/** - glide-core/src/** - submodules/** - .github/workflows/csharp.yml + - .github/workflows/install-shared-dependencies/action.yml + - .github/workflows/install-redis/action.yml + - .github/workflows/test-benchmark/action.yml + - .github/workflows/lint-rust/action.yml permissions: contents: read @@ -21,7 +29,6 @@ permissions: jobs: run-tests: timeout-minutes: 15 - runs-on: ubuntu-latest strategy: fail-fast: false matrix: @@ -29,8 +36,12 @@ jobs: - 6.2.14 - 7.2.3 dotnet: - - 6.0 - - 8.0 + - '6.0' + - '8.0' + os: + - ubuntu-latest + - macos-latest + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -38,34 +49,45 @@ jobs: submodules: recursive - name: Install redis + # TODO: make this step macos compatible: https://github.com/aws/glide-for-redis/issues/781 + if: ${{ matrix.os == 'ubuntu-latest' }} uses: ./.github/workflows/install-redis with: redis-version: ${{ matrix.redis }} - - name: Install protoc (protobuf) - uses: arduino/setup-protoc@v3 + - name: Install shared software dependencies + uses: ./.github/workflows/install-shared-dependencies with: - version: "25.1" + os: ${{ matrix.os }} + target: ${{ matrix.os == 'ubuntu-latest' && 'x86_64-unknown-linux-gnu' || 'x86_64-apple-darwin' }} + github-token: ${{ secrets.GITHUB_TOKEN }} - name: Set up dotnet ${{ matrix.dotnet }} - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ matrix.dotnet }} - - name: Start redis server - run: redis-server & - - name: Format working-directory: ./csharp run: dotnet format --verify-no-changes --verbosity diagnostic + - uses: ./.github/workflows/test-benchmark + with: + language-flag: -csharp + - name: Test dotnet ${{ matrix.dotnet }} working-directory: ./csharp - run: dotnet test --framework net${{ matrix.dotnet }} /warnaserror + run: dotnet test --framework net${{ matrix.dotnet }} "-l:html;LogFileName=TestReport.html" --results-directory . -warnaserror - - uses: ./.github/workflows/test-benchmark + - name: Upload test reports + if: always() + continue-on-error: true + uses: actions/upload-artifact@v4 with: - language-flag: -csharp + name: test-reports-dotnet-${{ matrix.dotnet }}-redis-${{ matrix.redis }}-${{ matrix.os }} + path: | + csharp/TestReport.html + benchmarks/results/* lint-rust: timeout-minutes: 10 diff --git a/.github/workflows/install-shared-dependencies/action.yml b/.github/workflows/install-shared-dependencies/action.yml index 2174952692..e09981ea7c 100644 --- a/.github/workflows/install-shared-dependencies/action.yml +++ b/.github/workflows/install-shared-dependencies/action.yml @@ -31,7 +31,7 @@ runs: shell: bash if: "${{ inputs.os == 'macos-latest' }}" run: | - brew install git gcc pkgconfig openssl redis + brew install git gcc pkgconfig openssl redis coreutils - name: Install software dependencies for Ubuntu shell: bash diff --git a/csharp/.gitignore b/csharp/.gitignore index 92e9f50cc3..71475a2a33 100644 --- a/csharp/.gitignore +++ b/csharp/.gitignore @@ -94,6 +94,7 @@ ClientBin/ *~ *.dbmdl *.[Pp]ublish.xml +*.html *.publishsettings From 565eef7d33bbb88ef545a64b6fee06b56c69bbc0 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Thu, 29 Feb 2024 10:17:09 -0800 Subject: [PATCH 31/65] Fix java min benchmarking. (#105) (#1046) * Fix java min benchmarking. --------- Signed-off-by: Yury-Fridlyand --- .github/workflows/java-benchmark.yml | 56 ------------------- .github/workflows/java.yml | 21 +++++-- benchmarks/install_and_test.sh | 6 +- java/README.md | 4 +- .../glide/benchmarks/BenchmarkingApp.java | 56 ++++++++++++++----- 5 files changed, 63 insertions(+), 80 deletions(-) delete mode 100644 .github/workflows/java-benchmark.yml diff --git a/.github/workflows/java-benchmark.yml b/.github/workflows/java-benchmark.yml deleted file mode 100644 index 21d7d7fea1..0000000000 --- a/.github/workflows/java-benchmark.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Java client benchmarks - -on: - workflow_dispatch: - inputs: - name: - required: false - type: string - -run-name: ${{ inputs.name == '' && format('{0} @ {1}', github.ref_name, github.sha) || inputs.name }} - -jobs: - java-benchmark: - timeout-minutes: 25 - strategy: - # Run all jobs - fail-fast: false - matrix: - java: - - 11 - - 17 - redis: - - 6.2.14 - - 7.2.3 - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: ${{ matrix.java }} - - - name: Install redis - uses: ./.github/workflows/install-redis - with: - redis-version: ${{ matrix.redis }} - - - name: benchmark - uses: ./.github/workflows/test-benchmark - with: - language-flag: -java - - - name: Upload test reports - if: always() - continue-on-error: true - uses: actions/upload-artifact@v4 - with: - name: test-reports-java-${{ matrix.java }}-redis-${{ matrix.redis }} - path: | - java/benchmarks/build/reports/** - benchmarks/results/** diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml index 941ad2b349..f0d3c1a01c 100644 --- a/.github/workflows/java.yml +++ b/.github/workflows/java.yml @@ -6,14 +6,22 @@ on: paths: - glide-core/src/** - submodules/** - - "java/**" - - ".github/workflows/java.yml" + - java/** + - .github/workflows/java.yml + - .github/workflows/install-shared-dependencies/action.yml + - .github/workflows/install-redis/action.yml + - .github/workflows/test-benchmark/action.yml + - .github/workflows/lint-rust/action.yml pull_request: paths: - glide-core/src/** - submodules/** - - "java/**" - - ".github/workflows/java.yml" + - java/** + - .github/workflows/java.yml + - .github/workflows/install-shared-dependencies/action.yml + - .github/workflows/install-redis/action.yml + - .github/workflows/test-benchmark/action.yml + - .github/workflows/lint-rust/action.yml jobs: build-and-test-java-client: @@ -67,6 +75,10 @@ jobs: working-directory: java run: ./gradlew spotlessDiagnose | grep 'All formatters are well behaved for all files' + - uses: ./.github/workflows/test-benchmark + with: + language-flag: -java + - name: Upload test reports if: always() continue-on-error: true @@ -77,6 +89,7 @@ jobs: java/client/build/reports/** java/integTest/build/reports/** utils/clusters/** + benchmarks/results/** build-amazonlinux-latest: if: github.repository_owner == 'aws' diff --git a/benchmarks/install_and_test.sh b/benchmarks/install_and_test.sh index 72b56fb2cb..ebd650f39e 100755 --- a/benchmarks/install_and_test.sh +++ b/benchmarks/install_and_test.sh @@ -33,7 +33,6 @@ chosenClients="all" host="localhost" port=6379 tlsFlag="--tls" -javaTlsFlag="-tls" function runPythonBenchmark(){ # generate protobuf files @@ -74,7 +73,7 @@ function runCSharpBenchmark(){ function runJavaBenchmark(){ cd ${BENCH_FOLDER}/../java - ./gradlew :benchmarks:run --args="-resultsFile \"${BENCH_FOLDER}/$1\" -dataSize \"$2\" -concurrentTasks \"$concurrentTasks\" -clients \"$chosenClients\" -host $host $javaPortFlag -clientCount \"$clientCount\" $javaTlsFlag $javaClusterFlag" + ./gradlew :benchmarks:run --args="-resultsFile \"${BENCH_FOLDER}/$1\" --dataSize \"$2\" --concurrentTasks \"$concurrentTasks\" --clients \"$chosenClients\" --host $host $portFlag --clientCount \"$clientCount\" $tlsFlag $clusterFlag $minimalFlag" } function runRustBenchmark(){ @@ -218,15 +217,12 @@ do -no-csv) writeResultsCSV=0 ;; -no-tls) tlsFlag= - javaTlsFlag= ;; -is-cluster) clusterFlag="--clusterModeEnabled" - javaClusterFlag="-clusterModeEnabled" ;; -port) portFlag="--port "$2 - javaPortFlag="-port "$2 shift ;; -minimal) diff --git a/java/README.md b/java/README.md index fbedc1b499..b7b9a3dcaa 100644 --- a/java/README.md +++ b/java/README.md @@ -111,8 +111,8 @@ assert getResponse.get() == "foobar" : "Failed on client.get("key") request"; You can run benchmarks using `./gradlew run`. You can set arguments using the args flag like: ```shell -./gradlew run --args="-help" -./gradlew run --args="-resultsFile=output -dataSize \"100 1000\" -concurrentTasks \"10 100\" -clients all -host localhost -port 6279 -clientCount \"1 5\" -tls" +./gradlew run --args="--help" +./gradlew run --args="--resultsFile=output --dataSize \"100 1000\" --concurrentTasks \"10 100\" --clients all --host localhost --port 6279 --clientCount \"1 5\" --tls" ``` The following arguments are accepted: diff --git a/java/benchmarks/src/main/java/glide/benchmarks/BenchmarkingApp.java b/java/benchmarks/src/main/java/glide/benchmarks/BenchmarkingApp.java index 9c5d196699..594c82c030 100644 --- a/java/benchmarks/src/main/java/glide/benchmarks/BenchmarkingApp.java +++ b/java/benchmarks/src/main/java/glide/benchmarks/BenchmarkingApp.java @@ -66,36 +66,66 @@ private static Options getOptions() { // create the Options Options options = new Options(); - options.addOption(Option.builder("help").desc("print this message").build()); + options.addOption(Option.builder("h").longOpt("help").desc("Print this message").build()); options.addOption( - Option.builder("configuration").hasArg(true).desc("Configuration flag [Release]").build()); + Option.builder() + .longOpt("configuration") + .hasArg(true) + .desc("Configuration flag [Release]") + .build()); options.addOption( - Option.builder("resultsFile") + Option.builder() + .longOpt("resultsFile") .hasArg(true) .desc("Result filepath (stdout if empty) []") .build()); options.addOption( - Option.builder("dataSize").hasArg(true).desc("Data block size [100 4000]").build()); + Option.builder() + .longOpt("dataSize") + .hasArg(true) + .desc("Data block size [100 4000]") + .build()); options.addOption( - Option.builder("concurrentTasks") + Option.builder() + .longOpt("concurrentTasks") .hasArg(true) .desc("Number of concurrent tasks [100, 1000]") .build()); options.addOption( - Option.builder("clients").hasArg(true).desc("one of: all|jedis|lettuce|glide").build()); - options.addOption(Option.builder("host").hasArg(true).desc("Hostname [localhost]").build()); - options.addOption(Option.builder("port").hasArg(true).desc("Port number [6379]").build()); + Option.builder() + .longOpt("clients") + .hasArg(true) + .desc("one of: all|jedis|lettuce|glide") + .build()); options.addOption( - Option.builder("clientCount").hasArg(true).desc("Number of clients to run [1]").build()); - options.addOption(Option.builder("tls").hasArg(false).desc("TLS [false]").build()); + Option.builder().longOpt("host").hasArg(true).desc("Hostname [localhost]").build()); options.addOption( - Option.builder("clusterModeEnabled") + Option.builder().longOpt("port").hasArg(true).desc("Port number [6379]").build()); + options.addOption( + Option.builder() + .longOpt("clientCount") + .hasArg(true) + .desc("Number of clients to run [1]") + .build()); + options.addOption(Option.builder().longOpt("tls").hasArg(false).desc("TLS [false]").build()); + options.addOption( + Option.builder() + .longOpt("clusterModeEnabled") .hasArg(false) .desc("Is cluster-mode enabled, other standalone mode is used [false]") .build()); - Option.builder("minimal").hasArg(false).desc("Run·benchmark·in·minimal·mode").build(); options.addOption( - Option.builder("debugLogging").hasArg(false).desc("Verbose logs [false]").build()); + Option.builder() + .longOpt("minimal") + .hasArg(false) + .desc("Run benchmark in minimal mode") + .build()); + options.addOption( + Option.builder() + .longOpt("debugLogging") + .hasArg(false) + .desc("Verbose logs [false]") + .build()); return options; } From cd591464115ef3e19628bdd096fc59d2dad5ab63 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Thu, 29 Feb 2024 10:17:38 -0800 Subject: [PATCH 32/65] Update docs for C# client (#111) (#1045) * Update docs for C# client (#111) --------- Signed-off-by: Yury-Fridlyand --- csharp/DEVELOPER.md | 131 +++++++++++++++++++++++++++++++++++--------- csharp/README.md | 2 +- 2 files changed, 106 insertions(+), 27 deletions(-) diff --git a/csharp/DEVELOPER.md b/csharp/DEVELOPER.md index ca5c9369b4..1042aae2e4 100644 --- a/csharp/DEVELOPER.md +++ b/csharp/DEVELOPER.md @@ -4,30 +4,73 @@ This document describes how to set up your development environment to build and ### Development Overview -The GLIDE C# wrapper consists of both C# and Rust code. +We're excited to share that the GLIDE C# client is currently in development! However, it's important to note that this client is a work in progress and is not yet complete or fully tested. Your contributions and feedback are highly encouraged as we work towards refining and improving this implementation. Thank you for your interest and understanding as we continue to develop this C# wrapper. + +The C# client contains the following parts: + +1. Rust part of the C# client located in `lib/src`; it communicates with [GLIDE core rust library](../glide-core/README.md). +2. C# part of the client located in `lib`; it translates Rust async API into .Net async API. +3. Integration tests for the C# client located in `tests` directory. +4. A dedicated benchmarking tool designed to evaluate and compare the performance of GLIDE for Redis and other .Net clients. It is located in `/benchmarks/csharp`. + +TODO: examples, UT, design docs ### Build from source +Software Dependencies: + +- .Net SDK 6 or later +- git +- rustup +- redis + +Please also install the following packages to build [GLIDE core rust library](../glide-core/README.md): + +- GCC +- protoc (protobuf compiler) +- pkg-config +- openssl +- openssl-dev + #### Prerequisites -Software Dependencies +**.Net** + +It is recommended to visit https://dotnet.microsoft.com/en-us/download/dotnet to download .Net installer. +You can also use a package manager to install the .Net SDK: + +```bash +brew install dotnet@6 # MacOS +sudo apt-get install dotnet6 # Linux +``` + +**Protoc installation** + +Download a binary matching your system from the [official release page](https://github.com/protocolbuffers/protobuf/releases/tag/v25.1) and make it accessible in your $PATH by moving it or creating a symlink. +For example, on Linux you can copy it to `/usr/bin`: -- .net sdk 6 or later -- git -- GCC -- pkg-config -- protoc (protobuf compiler) -- openssl -- openssl-dev -- rustup +```bash +sudo cp protoc /usr/bin/ +``` + +**Redis installation** + +To install `redis-server` and `redis-cli` on your host, follow the [Redis Installation Guide](https://redis.io/docs/install/install-redis/). + +**Dependencies installation for Ubuntu** + +```bash +sudo apt-get update -y +sudo apt-get install -y openssl openssl-dev gcc +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source "$HOME/.cargo/env" +``` **Dependencies installation for MacOS** -visit https://dotnet.microsoft.com/en-us/download/dotnet -to download .net installer ```bash brew update -brew install git gcc pkgconfig protobuf openssl +brew install git gcc pkgconfig openssl curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source "$HOME/.cargo/env" ``` @@ -36,34 +79,70 @@ 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. Build the c# wrapper: - Choose a build option from the following and run it from the `csharp` folder: - Build in release mode, stripped from all debug symbols: +3. Build the C# wrapper + +```bash +dotnet build +``` + +4. Run tests + +Run test suite from `csharp` directory: + +```bash +dotnet test +``` + +5. Run benchmark + + 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 root project folder: ```bash - dotnet build + cd /benchmarks/csharp + dotnet run --framework net8.0 --dataSize 1024 --resultsFile test.json --concurrentTasks 4 --clients all --host localhost --clientCount 4 ``` -4. Run benchmark: + 3. Use a [helper script](../benchmarks/README.md) which runs end-to-end benchmarking workflow: - 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/). + ```bash + cd /benchmarks + ./install_and_test.sh -csharp + ``` - 2. Execute the following command from the root project folder: - ```bash - cd benchmarks/csharp - dotnet run --framework net8.0 --dataSize 1024 --resultsFile test.json --concurrentTasks 4 --clients all --host localhost --clientCount 4 - ``` + Run benchmarking script with `-h` flag to get list and help about all command line parameters. + +6. Lint the code + +Before making a contribution ensure that all new user API and non-obvious places in code is well documented and run a code linter. + +C# linter: + +```bash +dotnet format --verify-no-changes --verbosity diagnostic +``` + +Rust linter: + +```bash +cargo clippy --all-features --all-targets -- -D warnings +cargo fmt --all -- --check +``` ### Submodules diff --git a/csharp/README.md b/csharp/README.md index 860f5e67c0..5ebc9e1961 100644 --- a/csharp/README.md +++ b/csharp/README.md @@ -36,4 +36,4 @@ glideClient.Dispose(); ### Building & Testing -Development instructions for local building & testing the package are in the [DEVELOPER.md](https://github.com/aws/glide-for-redis/blob/main/csharp/DEVELOPER.md#build-from-source) file. +Development instructions for local building & testing the package are in the [DEVELOPER.md](DEVELOPER.md#build-from-source) file. From 4639a9bdcc634d8978f6e4fbdd7319ba1c33a889 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Thu, 29 Feb 2024 13:02:13 -0800 Subject: [PATCH 33/65] Java: Add `expire`, `expireAt`, `pexpire`, `pexpireAt` and `ttl` commands. (#1033) * Java: Add `expire`, `expireAt`, `pexpire`, `pexpireAt` and `ttl` commands --------- Signed-off-by: Andrew Carbonetto Signed-off-by: Yury-Fridlyand Co-authored-by: SanHalacogluImproving Co-authored-by: Yury-Fridlyand * Updated based on PR comments. * Spotless and fixed ordering in TransactionTests. --------- Signed-off-by: Andrew Carbonetto Signed-off-by: Yury-Fridlyand Co-authored-by: Andrew Carbonetto Co-authored-by: Yury-Fridlyand --- .../src/main/java/glide/api/BaseClient.java | 70 ++++++ .../api/commands/GenericBaseCommands.java | 214 ++++++++++++++++- .../glide/api/models/BaseTransaction.java | 225 ++++++++++++++++++ .../api/models/commands/ExpireOptions.java | 45 ++++ .../test/java/glide/api/RedisClientTest.java | 215 +++++++++++++++++ .../glide/api/models/TransactionTests.java | 81 ++++++- .../test/java/glide/SharedCommandTests.java | 123 ++++++++++ 7 files changed, 965 insertions(+), 8 deletions(-) create mode 100644 java/client/src/main/java/glide/api/models/commands/ExpireOptions.java diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 6d56840502..376300465b 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -8,6 +8,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.DecrBy; import static redis_request.RedisRequestOuterClass.RequestType.Del; import static redis_request.RedisRequestOuterClass.RequestType.Exists; +import static redis_request.RedisRequestOuterClass.RequestType.Expire; +import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; @@ -24,6 +26,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.LPush; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; +import static redis_request.RedisRequestOuterClass.RequestType.PExpire; +import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; import static redis_request.RedisRequestOuterClass.RequestType.SAdd; @@ -31,6 +35,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.SMembers; import static redis_request.RedisRequestOuterClass.RequestType.SRem; import static redis_request.RedisRequestOuterClass.RequestType.SetString; +import static redis_request.RedisRequestOuterClass.RequestType.TTL; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import glide.api.commands.GenericBaseCommands; @@ -38,6 +43,7 @@ import glide.api.commands.ListBaseCommands; import glide.api.commands.SetCommands; import glide.api.commands.StringCommands; +import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.SetOptions; import glide.api.models.configuration.BaseClientConfiguration; import glide.api.models.exceptions.RedisException; @@ -404,4 +410,68 @@ public CompletableFuture exists(@NonNull String[] keys) { public CompletableFuture unlink(@NonNull String[] keys) { return commandManager.submitNewCommand(Unlink, keys, this::handleLongResponse); } + + @Override + public CompletableFuture expire(@NonNull String key, long seconds) { + return commandManager.submitNewCommand( + Expire, new String[] {key, Long.toString(seconds)}, this::handleBooleanResponse); + } + + @Override + public CompletableFuture expire( + @NonNull String key, long seconds, @NonNull ExpireOptions expireOptions) { + String[] arguments = + ArrayUtils.addAll(new String[] {key, Long.toString(seconds)}, expireOptions.toArgs()); + return commandManager.submitNewCommand(Expire, arguments, this::handleBooleanResponse); + } + + @Override + public CompletableFuture expireAt(@NonNull String key, long unixSeconds) { + return commandManager.submitNewCommand( + ExpireAt, new String[] {key, Long.toString(unixSeconds)}, this::handleBooleanResponse); + } + + @Override + public CompletableFuture expireAt( + @NonNull String key, long unixSeconds, @NonNull ExpireOptions expireOptions) { + String[] arguments = + ArrayUtils.addAll(new String[] {key, Long.toString(unixSeconds)}, expireOptions.toArgs()); + return commandManager.submitNewCommand(ExpireAt, arguments, this::handleBooleanResponse); + } + + @Override + public CompletableFuture pexpire(@NonNull String key, long milliseconds) { + return commandManager.submitNewCommand( + PExpire, new String[] {key, Long.toString(milliseconds)}, this::handleBooleanResponse); + } + + @Override + public CompletableFuture pexpire( + @NonNull String key, long milliseconds, @NonNull ExpireOptions expireOptions) { + String[] arguments = + ArrayUtils.addAll(new String[] {key, Long.toString(milliseconds)}, expireOptions.toArgs()); + return commandManager.submitNewCommand(PExpire, arguments, this::handleBooleanResponse); + } + + @Override + public CompletableFuture pexpireAt(@NonNull String key, long unixMilliseconds) { + return commandManager.submitNewCommand( + PExpireAt, + new String[] {key, Long.toString(unixMilliseconds)}, + this::handleBooleanResponse); + } + + @Override + public CompletableFuture pexpireAt( + @NonNull String key, long unixMilliseconds, @NonNull ExpireOptions expireOptions) { + String[] arguments = + ArrayUtils.addAll( + new String[] {key, Long.toString(unixMilliseconds)}, expireOptions.toArgs()); + return commandManager.submitNewCommand(PExpireAt, arguments, this::handleBooleanResponse); + } + + @Override + public CompletableFuture ttl(@NonNull String key) { + return commandManager.submitNewCommand(TTL, new String[] {key}, this::handleLongResponse); + } } diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index 13a5b05353..e7a47bf279 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -1,11 +1,11 @@ /** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api.commands; +import glide.api.models.commands.ExpireOptions; import java.util.concurrent.CompletableFuture; /** - * Generic Commands interface to handle generic commands for all server requests for both standalone - * and cluster clients. + * Generic Commands interface to handle generic commands for all server requests. * * @see Generic Commands */ @@ -53,4 +53,214 @@ public interface GenericBaseCommands { * */ CompletableFuture unlink(String[] keys); + + /** + * Sets a timeout on key in seconds. After the timeout has expired, the key + * will automatically be deleted.
+ * If key already has an existing expire + * set, the time to live is updated to the new value.
+ * If seconds is a non-positive number, the key will be deleted rather + * than expired.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param seconds The timeout in seconds. + * @return true if the timeout was set. false if the timeout was not + * set. e.g. key doesn't exist. + * @example + *
+     * Boolean isSet = client.expire("my_key", 60).get()
+     * assert isSet //Indicates that a timeout of 60 seconds has been set for "my_key."
+     * 
+ */ + CompletableFuture expire(String key, long seconds); + + /** + * Sets a timeout on key in seconds. After the timeout has expired, the key + * will automatically be deleted.
+ * If key already has an existing expire + * set, the time to live is updated to the new value.
+ * If seconds is a non-positive number, the key will be deleted rather + * than expired.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param seconds The timeout in seconds. + * @param expireOptions The expire options. + * @return true if the timeout was set. false if the timeout was not + * set. e.g. key doesn't exist, or operation skipped due to the provided + * arguments. + * @example + *
+     * Boolean isSet = client.expire("my_key", 60, ExpireOptions.HAS_NO_EXPIRY).get()
+     * assert isSet //Indicates that a timeout of 60 seconds has been set for "my_key."
+     * 
+ */ + CompletableFuture expire(String key, long seconds, ExpireOptions expireOptions); + + /** + * Sets a timeout on key. It takes an absolute Unix timestamp (seconds since January + * 1, 1970) instead of specifying the number of seconds.
+ * A timestamp in the past will delete the key immediately. After the timeout has + * expired, the key will automatically be deleted.
+ * If key already has an existing expire set, the time to live is + * updated to the new value.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param unixSeconds The timeout in an absolute Unix timestamp. + * @return true if the timeout was set. false if the timeout was not + * set. e.g. key doesn't exist. + * @example + *
+     * Boolean isSet = client.expireAt("my_key", Instant.now().getEpochSecond() + 10).get()
+     * assert isSet
+     * 
+ */ + CompletableFuture expireAt(String key, long unixSeconds); + + /** + * Sets a timeout on key. It takes an absolute Unix timestamp (seconds since January + * 1, 1970) instead of specifying the number of seconds.
+ * A timestamp in the past will delete the key immediately. After the timeout has + * expired, the key will automatically be deleted.
+ * If key already has an existing expire set, the time to live is + * updated to the new value.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param unixSeconds The timeout in an absolute Unix timestamp. + * @param expireOptions The expire options. + * @return true if the timeout was set. false if the timeout was not + * set. e.g. key doesn't exist, or operation skipped due to the provided + * arguments. + * @example + *
+     * Boolean isSet = client.expireAt("my_key", Instant.now().getEpochSecond() + 10, ExpireOptions.HasNoExpiry).get()
+     * assert isSet
+     * 
+ */ + CompletableFuture expireAt(String key, long unixSeconds, ExpireOptions expireOptions); + + /** + * Sets a timeout on key in milliseconds. After the timeout has expired, the + * key will automatically be deleted.
+ * If key already has an existing + * expire set, the time to live is updated to the new value.
+ * If milliseconds is a non-positive number, the key will be deleted + * rather than expired.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param milliseconds The timeout in milliseconds. + * @return true if the timeout was set. false if the timeout was not + * set. e.g. key doesn't exist. + * @example + *
+     * Boolean isSet = client.pexpire("my_key", 60000).get()
+     * assert isSet
+     * 
+ */ + CompletableFuture pexpire(String key, long milliseconds); + + /** + * Sets a timeout on key in milliseconds. After the timeout has expired, the + * key will automatically be deleted.
+ * If key already has an existing expire set, the time to live is updated to the new + * value.
+ * If milliseconds is a non-positive number, the key will be deleted + * rather than expired.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param milliseconds The timeout in milliseconds. + * @param expireOptions The expire options. + * @return true if the timeout was set. false if the timeout was not + * set. e.g. key doesn't exist, or operation skipped due to the provided + * arguments. + * @example + *
+     * Boolean isSet = client.pexpire("my_key", 60000, ExpireOptions.HasNoExpiry).get()
+     * assert isSet
+     * 
+ */ + CompletableFuture pexpire(String key, long milliseconds, ExpireOptions expireOptions); + + /** + * Sets a timeout on key. It takes an absolute Unix timestamp (milliseconds since + * January 1, 1970) instead of specifying the number of milliseconds.
+ * A timestamp in the past will delete the key immediately. After the timeout has + * expired, the key will automatically be deleted.
+ * If key already has an existing expire set, the time to live is + * updated to the new value.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param unixMilliseconds The timeout in an absolute Unix timestamp. + * @return true if the timeout was set. false if the timeout was not + * set. e.g. key doesn't exist. + * @example + *
+     * Boolean isSet = client.pexpireAt("my_key", Instant.now().toEpochMilli() + 10).get()
+     * assert isSet
+     * 
+ */ + CompletableFuture pexpireAt(String key, long unixMilliseconds); + + /** + * Sets a timeout on key. It takes an absolute Unix timestamp (milliseconds since + * January 1, 1970) instead of specifying the number of milliseconds.
+ * A timestamp in the past will delete the key immediately. After the timeout has + * expired, the key will automatically be deleted.
+ * If key already has an existing expire set, the time to live is + * updated to the new value.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param unixMilliseconds The timeout in an absolute Unix timestamp. + * @param expireOptions The expire option. + * @return true if the timeout was set. false if the timeout was not + * set. e.g. key doesn't exist, or operation skipped due to the provided + * arguments. + * @example + *
+     * Boolean isSet = client.pexpireAt("my_key", Instant.now().toEpochMilli() + 10, ExpireOptions.HasNoExpiry).get()
+     * assert isSet
+     * 
+ */ + CompletableFuture pexpireAt( + String key, long unixMilliseconds, ExpireOptions expireOptions); + + /** + * Returns the remaining time to live of key that has a timeout. + * + * @see redis.io for details. + * @param key The key to return its timeout. + * @return TTL in seconds, -2 if key does not exist, or -1 + * if key exists but has no associated expire. + * @example + *
+     * Long timeRemaining = client.ttl("my_key").get()
+     * assert timeRemaining == 3600L //Indicates that "my_key" has a remaining time to live of 3600 seconds.
+     * Long timeRemaining = client.ttl("nonexistent_key").get()
+     * assert timeRemaining == -2L //Returns -2 for a non-existing key.
+     * 
+ */ + CompletableFuture ttl(String key); } diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index 7c18c3dec3..611baab04a 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -7,6 +7,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.DecrBy; import static redis_request.RedisRequestOuterClass.RequestType.Del; import static redis_request.RedisRequestOuterClass.RequestType.Exists; +import static redis_request.RedisRequestOuterClass.RequestType.Expire; +import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; @@ -24,6 +26,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.LPush; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; +import static redis_request.RedisRequestOuterClass.RequestType.PExpire; +import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -32,8 +36,10 @@ import static redis_request.RedisRequestOuterClass.RequestType.SMembers; import static redis_request.RedisRequestOuterClass.RequestType.SRem; import static redis_request.RedisRequestOuterClass.RequestType.SetString; +import static redis_request.RedisRequestOuterClass.RequestType.TTL; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; +import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.InfoOptions; import glide.api.models.commands.InfoOptions.Section; import glide.api.models.commands.SetOptions; @@ -672,6 +678,225 @@ public T unlink(String[] keys) { return getThis(); } + /** + * Sets a timeout on key in seconds. After the timeout has expired, the key + * will automatically be deleted.
+ * If key already has an existing expire + * set, the time to live is updated to the new value.
+ * If seconds is a non-positive number, the key will be deleted rather + * than expired.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param seconds The timeout in seconds. + * @return Command response - true if the timeout was set. false if the + * timeout was not set. e.g. key doesn't exist. + */ + public T expire(@NonNull String key, long seconds) { + ArgsArray commandArgs = buildArgs(key, Long.toString(seconds)); + + protobufTransaction.addCommands(buildCommand(Expire, commandArgs)); + return getThis(); + } + + /** + * Sets a timeout on key in seconds. After the timeout has expired, the key + * will automatically be deleted.
+ * If key already has an existing expire + * set, the time to live is updated to the new value.
+ * If seconds is a non-positive number, the key will be deleted rather + * than expired.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param seconds The timeout in seconds. + * @param expireOptions The expire options. + * @return Command response - true if the timeout was set. false if the + * timeout was not set. e.g. key doesn't exist, or operation skipped due to the + * provided arguments. + */ + public T expire(@NonNull String key, long seconds, @NonNull ExpireOptions expireOptions) { + ArgsArray commandArgs = + buildArgs( + ArrayUtils.addAll(new String[] {key, Long.toString(seconds)}, expireOptions.toArgs())); + + protobufTransaction.addCommands(buildCommand(Expire, commandArgs)); + return getThis(); + } + + /** + * Sets a timeout on key. It takes an absolute Unix timestamp (seconds since January + * 1, 1970) instead of specifying the number of seconds.
+ * A timestamp in the past will delete the key immediately. After the timeout has + * expired, the key will automatically be deleted.
+ * If key already has an existing expire set, the time to live is + * updated to the new value.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param unixSeconds The timeout in an absolute Unix timestamp. + * @return Command response - true if the timeout was set. false if the + * timeout was not set. e.g. key doesn't exist. + */ + public T expireAt(@NonNull String key, long unixSeconds) { + ArgsArray commandArgs = buildArgs(key, Long.toString(unixSeconds)); + + protobufTransaction.addCommands(buildCommand(ExpireAt, commandArgs)); + return getThis(); + } + + /** + * Sets a timeout on key. It takes an absolute Unix timestamp (seconds since January + * 1, 1970) instead of specifying the number of seconds.
+ * A timestamp in the past will delete the key immediately. After the timeout has + * expired, the key will automatically be deleted.
+ * If key already has an existing expire set, the time to live is + * updated to the new value.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param unixSeconds The timeout in an absolute Unix timestamp. + * @param expireOptions The expire options. + * @return Command response - true if the timeout was set. false if the + * timeout was not set. e.g. key doesn't exist, or operation skipped due to the + * provided arguments. + */ + public T expireAt(@NonNull String key, long unixSeconds, @NonNull ExpireOptions expireOptions) { + ArgsArray commandArgs = + buildArgs( + ArrayUtils.addAll( + new String[] {key, Long.toString(unixSeconds)}, expireOptions.toArgs())); + + protobufTransaction.addCommands(buildCommand(ExpireAt, commandArgs)); + return getThis(); + } + + /** + * Sets a timeout on key in milliseconds. After the timeout has expired, the + * key will automatically be deleted.
+ * If key already has an existing + * expire set, the time to live is updated to the new value.
+ * If milliseconds is a non-positive number, the key will be deleted + * rather than expired.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param milliseconds The timeout in milliseconds. + * @return Command response - true if the timeout was set. false if the + * timeout was not set. e.g. key doesn't exist. + */ + public T pexpire(@NonNull String key, long milliseconds) { + ArgsArray commandArgs = buildArgs(key, Long.toString(milliseconds)); + + protobufTransaction.addCommands(buildCommand(PExpire, commandArgs)); + return getThis(); + } + + /** + * Sets a timeout on key in milliseconds. After the timeout has expired, the + * key will automatically be deleted.
+ * If key already has an existing expire set, the time to live is updated to the new + * value.
+ * If milliseconds is a non-positive number, the key will be deleted + * rather than expired.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param milliseconds The timeout in milliseconds. + * @param expireOptions The expire options. + * @return Command response - true if the timeout was set. false if the + * timeout was not set. e.g. key doesn't exist, or operation skipped due to the + * provided arguments. + */ + public T pexpire(@NonNull String key, long milliseconds, @NonNull ExpireOptions expireOptions) { + ArgsArray commandArgs = + buildArgs( + ArrayUtils.addAll( + new String[] {key, Long.toString(milliseconds)}, expireOptions.toArgs())); + + protobufTransaction.addCommands(buildCommand(PExpire, commandArgs)); + return getThis(); + } + + /** + * Sets a timeout on key. It takes an absolute Unix timestamp (milliseconds since + * January 1, 1970) instead of specifying the number of milliseconds.
+ * A timestamp in the past will delete the key immediately. After the timeout has + * expired, the key will automatically be deleted.
+ * If key already has an existing expire set, the time to live is + * updated to the new value.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param unixMilliseconds The timeout in an absolute Unix timestamp. + * @return Command response - true if the timeout was set. false if the + * timeout was not set. e.g. key doesn't exist. + */ + public T pexpireAt(@NonNull String key, long unixMilliseconds) { + ArgsArray commandArgs = buildArgs(key, Long.toString(unixMilliseconds)); + + protobufTransaction.addCommands(buildCommand(PExpireAt, commandArgs)); + return getThis(); + } + + /** + * Sets a timeout on key. It takes an absolute Unix timestamp (milliseconds since + * January 1, 1970) instead of specifying the number of milliseconds.
+ * A timestamp in the past will delete the key immediately. After the timeout has + * expired, the key will automatically be deleted.
+ * If key already has an existing expire set, the time to live is + * updated to the new value.
+ * The timeout will only be cleared by commands that delete or overwrite the contents of key + * . + * + * @see redis.io for details. + * @param key The key to set timeout on it. + * @param unixMilliseconds The timeout in an absolute Unix timestamp. + * @param expireOptions The expire option. + * @return Command response - true if the timeout was set. false if the + * timeout was not set. e.g. key doesn't exist, or operation skipped due to the + * provided arguments. + */ + public T pexpireAt( + @NonNull String key, long unixMilliseconds, @NonNull ExpireOptions expireOptions) { + ArgsArray commandArgs = + buildArgs( + ArrayUtils.addAll( + new String[] {key, Long.toString(unixMilliseconds)}, expireOptions.toArgs())); + + protobufTransaction.addCommands(buildCommand(PExpireAt, commandArgs)); + return getThis(); + } + + /** + * Returns the remaining time to live of key that has a timeout. + * + * @see redis.io for details. + * @param key The key to return its timeout. + * @return Command response - TTL in seconds, -2 if key does not exist, + * or -1 if key exists but has no associated expire. + */ + public T ttl(@NonNull String key) { + ArgsArray commandArgs = buildArgs(key); + + protobufTransaction.addCommands(buildCommand(TTL, commandArgs)); + return getThis(); + } + /** Build protobuf {@link Command} object for given command and arguments. */ protected Command buildCommand(RequestType requestType) { return buildCommand(requestType, buildArgs()); diff --git a/java/client/src/main/java/glide/api/models/commands/ExpireOptions.java b/java/client/src/main/java/glide/api/models/commands/ExpireOptions.java new file mode 100644 index 0000000000..2f51745af5 --- /dev/null +++ b/java/client/src/main/java/glide/api/models/commands/ExpireOptions.java @@ -0,0 +1,45 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.models.commands; + +import glide.api.commands.GenericBaseCommands; +import lombok.RequiredArgsConstructor; + +/** + * Optional arguments for {@link GenericBaseCommands#expire(String, long, ExpireOptions)}, and + * similar commands. + * + * @see redis.io + */ +@RequiredArgsConstructor +public enum ExpireOptions { + /** + * Sets expiry only when the key has no expiry. Equivalent to NX in the Redis API. + */ + HAS_NO_EXPIRY("NX"), + /** + * Sets expiry only when the key has an existing expiry. Equivalent to XX in the + * Redis API. + */ + HAS_EXISTING_EXPIRY("XX"), + /** + * Sets expiry only when the new expiry is greater than current one. Equivalent to GT + * in the Redis API. + */ + NEW_EXPIRY_GREATER_THAN_CURRENT("GT"), + /** + * Sets expiry only when the new expiry is less than current one. Equivalent to LT in + * the Redis API. + */ + NEW_EXPIRY_LESS_THAN_CURRENT("LT"); + + private final String redisApi; + + /** + * Converts ExpireOptions into a String[]. + * + * @return String[] + */ + public String[] toArgs() { + return new String[] {this.redisApi}; + } +} diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 06d0a145e9..1bcabe8dc1 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -17,6 +17,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.DecrBy; import static redis_request.RedisRequestOuterClass.RequestType.Del; import static redis_request.RedisRequestOuterClass.RequestType.Exists; +import static redis_request.RedisRequestOuterClass.RequestType.Expire; +import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; @@ -34,6 +36,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.LPush; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; +import static redis_request.RedisRequestOuterClass.RequestType.PExpire; +import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -43,8 +47,10 @@ import static redis_request.RedisRequestOuterClass.RequestType.SRem; import static redis_request.RedisRequestOuterClass.RequestType.Select; import static redis_request.RedisRequestOuterClass.RequestType.SetString; +import static redis_request.RedisRequestOuterClass.RequestType.TTL; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; +import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.InfoOptions; import glide.api.models.commands.SetOptions; import glide.api.models.commands.SetOptions.Expiry; @@ -319,6 +325,215 @@ public void exists_returns_long_success() { assertEquals(numberExisting, result); } + @SneakyThrows + @Test + public void expire_returns_success() { + // setup + String key = "testKey"; + long seconds = 10L; + String[] arguments = new String[] {key, Long.toString(seconds)}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(true); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Expire), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.expire(key, seconds); + + // verify + assertEquals(testResponse, response); + assertEquals(true, response.get()); + } + + @SneakyThrows + @Test + public void expire_with_expireOptions_returns_success() { + // setup + String key = "testKey"; + long seconds = 10L; + String[] arguments = new String[] {key, Long.toString(seconds), "NX"}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(false); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(Expire), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.expire(key, seconds, ExpireOptions.HAS_NO_EXPIRY); + + // verify + assertEquals(testResponse, response); + assertEquals(false, response.get()); + } + + @SneakyThrows + @Test + public void expireAt_returns_success() { + // setup + String key = "testKey"; + long unixSeconds = 100000L; + String[] arguments = new String[] {key, Long.toString(unixSeconds)}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(true); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ExpireAt), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.expireAt(key, unixSeconds); + + // verify + assertEquals(testResponse, response); + assertEquals(true, response.get()); + } + + @SneakyThrows + @Test + public void expireAt_with_expireOptions_returns_success() { + // setup + String key = "testKey"; + long unixSeconds = 100000L; + String[] arguments = new String[] {key, Long.toString(unixSeconds), "XX"}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(false); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ExpireAt), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = + service.expireAt(key, unixSeconds, ExpireOptions.HAS_EXISTING_EXPIRY); + + // verify + assertEquals(testResponse, response); + assertEquals(false, response.get()); + } + + @SneakyThrows + @Test + public void pexpire_returns_success() { + // setup + String key = "testKey"; + long milliseconds = 50000L; + String[] arguments = new String[] {key, Long.toString(milliseconds)}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(true); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PExpire), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pexpire(key, milliseconds); + + // verify + assertEquals(testResponse, response); + assertEquals(true, response.get()); + } + + @SneakyThrows + @Test + public void pexpire_with_expireOptions_returns_success() { + // setup + String key = "testKey"; + long milliseconds = 50000L; + String[] arguments = new String[] {key, Long.toString(milliseconds), "LT"}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(false); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PExpire), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = + service.pexpire(key, milliseconds, ExpireOptions.NEW_EXPIRY_LESS_THAN_CURRENT); + + // verify + assertEquals(testResponse, response); + assertEquals(false, response.get()); + } + + @SneakyThrows + @Test + public void pexpireAt_returns_success() { + // setup + String key = "testKey"; + long unixMilliseconds = 999999L; + String[] arguments = new String[] {key, Long.toString(unixMilliseconds)}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(true); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PExpireAt), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.pexpireAt(key, unixMilliseconds); + + // verify + assertEquals(testResponse, response); + assertEquals(true, response.get()); + } + + @SneakyThrows + @Test + public void pexpireAt_with_expireOptions_returns_success() { + // setup + String key = "testKey"; + long unixMilliseconds = 999999L; + String[] arguments = new String[] {key, Long.toString(unixMilliseconds), "GT"}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(false); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(PExpireAt), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = + service.pexpireAt(key, unixMilliseconds, ExpireOptions.NEW_EXPIRY_GREATER_THAN_CURRENT); + + // verify + assertEquals(testResponse, response); + assertEquals(false, response.get()); + } + + @SneakyThrows + @Test + public void ttl_returns_success() { + // setup + String key = "testKey"; + long ttl = 999L; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(ttl); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(TTL), eq(new String[] {key}), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.ttl(key); + + // verify + assertEquals(testResponse, response); + assertEquals(ttl, response.get()); + } + @SneakyThrows @Test public void info_returns_success() { diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index c43a224016..e9aa5ba4d1 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -7,6 +7,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.DecrBy; import static redis_request.RedisRequestOuterClass.RequestType.Del; import static redis_request.RedisRequestOuterClass.RequestType.Exists; +import static redis_request.RedisRequestOuterClass.RequestType.Expire; +import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.GetString; import static redis_request.RedisRequestOuterClass.RequestType.HashDel; import static redis_request.RedisRequestOuterClass.RequestType.HashExists; @@ -24,6 +26,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.LPush; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; +import static redis_request.RedisRequestOuterClass.RequestType.PExpire; +import static redis_request.RedisRequestOuterClass.RequestType.PExpireAt; import static redis_request.RedisRequestOuterClass.RequestType.Ping; import static redis_request.RedisRequestOuterClass.RequestType.RPop; import static redis_request.RedisRequestOuterClass.RequestType.RPush; @@ -32,8 +36,10 @@ import static redis_request.RedisRequestOuterClass.RequestType.SMembers; import static redis_request.RedisRequestOuterClass.RequestType.SRem; import static redis_request.RedisRequestOuterClass.RequestType.SetString; +import static redis_request.RedisRequestOuterClass.RequestType.TTL; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; +import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.InfoOptions; import glide.api.models.commands.SetOptions; import java.util.LinkedList; @@ -74,15 +80,9 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) .addArgs(RETURN_OLD_VALUE) .build())); - transaction.exists(new String[] {"key1", "key2"}); - results.add(Pair.of(Exists, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").build())); - transaction.del(new String[] {"key1", "key2"}); results.add(Pair.of(Del, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").build())); - transaction.unlink(new String[] {"key1", "key2"}); - results.add(Pair.of(Unlink, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").build())); - transaction.ping(); results.add(Pair.of(Ping, ArgsArray.newBuilder().build())); @@ -186,6 +186,75 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.scard("key"); results.add(Pair.of(SCard, ArgsArray.newBuilder().addArgs("key").build())); + transaction.exists(new String[] {"key1", "key2"}); + results.add(Pair.of(Exists, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").build())); + + transaction.unlink(new String[] {"key1", "key2"}); + results.add(Pair.of(Unlink, ArgsArray.newBuilder().addArgs("key1").addArgs("key2").build())); + + 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())); + + transaction.pexpire("key", 99999L); + results.add( + Pair.of( + PExpire, ArgsArray.newBuilder().addArgs("key").addArgs(Long.toString(99999L)).build())); + + 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.pexpireAt("key", 9999999L); + results.add( + Pair.of( + PExpireAt, + ArgsArray.newBuilder().addArgs("key").addArgs(Long.toString(9999999L)).build())); + + 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.ttl("key"); + results.add(Pair.of(TTL, ArgsArray.newBuilder().addArgs("key").build())); + var protobufTransaction = transaction.getProtobufTransaction().build(); for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) { diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index a2f13dc324..032e769c7c 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -2,6 +2,7 @@ package glide; import static glide.TestConfiguration.CLUSTER_PORTS; +import static glide.TestConfiguration.REDIS_VERSION; import static glide.TestConfiguration.STANDALONE_PORTS; import static glide.api.BaseClient.OK; import static glide.api.models.commands.SetOptions.ConditionalSet.ONLY_IF_DOES_NOT_EXIST; @@ -18,11 +19,13 @@ import glide.api.BaseClient; import glide.api.RedisClient; import glide.api.RedisClusterClient; +import glide.api.models.commands.ExpireOptions; import glide.api.models.commands.SetOptions; import glide.api.models.configuration.NodeAddress; import glide.api.models.configuration.RedisClientConfiguration; import glide.api.models.configuration.RedisClusterClientConfiguration; import glide.api.models.exceptions.RequestException; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.Set; @@ -664,4 +667,124 @@ public void exists_multiple_keys(BaseClient client) { client.exists(new String[] {key1, key2, key1, UUID.randomUUID().toString()}).get(); assertEquals(3L, existsKeysNum); } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void expire_pexpire_and_ttl_with_positive_timeout(BaseClient client) { + String key = UUID.randomUUID().toString(); + assertEquals(OK, client.set(key, "expire_timeout").get()); + assertTrue(client.expire(key, 10L).get()); + assertTrue(client.ttl(key).get() <= 10L); + + // set command clears the timeout. + assertEquals(OK, client.set(key, "pexpire_timeout").get()); + if (REDIS_VERSION.feature() < 7) { + assertTrue(client.pexpire(key, 10000L).get()); + } else { + assertTrue(client.pexpire(key, 10000L, ExpireOptions.HAS_NO_EXPIRY).get()); + } + assertTrue(client.ttl(key).get() <= 10L); + + // TTL will be updated to the new value = 15 + if (REDIS_VERSION.feature() < 7) { + assertTrue(client.expire(key, 15L).get()); + } else { + assertTrue(client.expire(key, 15L, ExpireOptions.HAS_EXISTING_EXPIRY).get()); + } + assertTrue(client.ttl(key).get() <= 15L); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void expireAt_pexpireAt_and_ttl_with_positive_timeout(BaseClient client) { + String key = UUID.randomUUID().toString(); + assertEquals(OK, client.set(key, "expireAt_timeout").get()); + assertTrue(client.expireAt(key, Instant.now().getEpochSecond() + 10L).get()); + assertTrue(client.ttl(key).get() <= 10L); + + // extend TTL + if (REDIS_VERSION.feature() < 7) { + assertTrue(client.expireAt(key, Instant.now().getEpochSecond() + 50L).get()); + } else { + assertTrue( + client + .expireAt( + key, + Instant.now().getEpochSecond() + 50L, + ExpireOptions.NEW_EXPIRY_GREATER_THAN_CURRENT) + .get()); + } + assertTrue(client.ttl(key).get() <= 50L); + + if (REDIS_VERSION.feature() < 7) { + assertTrue(client.pexpireAt(key, Instant.now().toEpochMilli() + 50000L).get()); + } else { + // set command clears the timeout. + assertEquals(OK, client.set(key, "pexpireAt_timeout").get()); + assertFalse( + client + .pexpireAt( + key, Instant.now().toEpochMilli() + 50000L, ExpireOptions.HAS_EXISTING_EXPIRY) + .get()); + } + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void expire_pexpire_with_timestamp_in_the_past_or_negative_timeout(BaseClient client) { + String key = UUID.randomUUID().toString(); + + assertEquals(OK, client.set(key, "expire_with_past_timestamp").get()); + assertEquals(-1L, client.ttl(key).get()); + assertTrue(client.expire(key, -10L).get()); + assertEquals(-2L, client.ttl(key).get()); + + assertEquals(OK, client.set(key, "pexpire_with_past_timestamp").get()); + assertTrue(client.pexpire(key, -10000L).get()); + assertEquals(-2L, client.ttl(key).get()); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void expireAt_pexpireAt_with_timestamp_in_the_past_or_negative_timeout(BaseClient client) { + String key = UUID.randomUUID().toString(); + + assertEquals(OK, client.set(key, "expireAt_with_past_timestamp").get()); + // set timeout in the past + assertTrue(client.expireAt(key, Instant.now().getEpochSecond() - 50L).get()); + assertEquals(-2L, client.ttl(key).get()); + + assertEquals(OK, client.set(key, "pexpireAt_with_past_timestamp").get()); + // set timeout in the past + assertTrue(client.pexpireAt(key, Instant.now().toEpochMilli() - 50000L).get()); + assertEquals(-2L, client.ttl(key).get()); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void expire_pexpire_and_ttl_with_non_existing_key(BaseClient client) { + String key = UUID.randomUUID().toString(); + + assertFalse(client.expire(key, 10L).get()); + assertFalse(client.pexpire(key, 10000L).get()); + + assertEquals(-2L, client.ttl(key).get()); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void expireAt_pexpireAt_and_ttl_with_non_existing_key(BaseClient client) { + String key = UUID.randomUUID().toString(); + + assertFalse(client.expireAt(key, Instant.now().getEpochSecond() + 10L).get()); + assertFalse(client.pexpireAt(key, Instant.now().toEpochMilli() + 10000L).get()); + + assertEquals(-2L, client.ttl(key).get()); + } } From 0b16f99b2082de9c040c8743eec7857b8eb7efb4 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Fri, 1 Mar 2024 15:49:49 -0800 Subject: [PATCH 34/65] Java: Added clientName field to configuration. (#117) (#1049) * Java: Added clientName field to configuration. (#117) * Add IT tests. --- .../BaseClientConfiguration.java | 6 ++++ .../glide/managers/ConnectionManager.java | 4 +++ .../glide/managers/ConnectionManagerTest.java | 4 +++ .../test/java/glide/cluster/ClientTest.java | 29 +++++++++++++++++++ .../java/glide/standalone/ClientTest.java | 28 ++++++++++++++++++ 5 files changed, 71 insertions(+) create mode 100644 java/integTest/src/test/java/glide/cluster/ClientTest.java create mode 100644 java/integTest/src/test/java/glide/standalone/ClientTest.java diff --git a/java/client/src/main/java/glide/api/models/configuration/BaseClientConfiguration.java b/java/client/src/main/java/glide/api/models/configuration/BaseClientConfiguration.java index 747bf806b1..f22002f183 100644 --- a/java/client/src/main/java/glide/api/models/configuration/BaseClientConfiguration.java +++ b/java/client/src/main/java/glide/api/models/configuration/BaseClientConfiguration.java @@ -53,6 +53,12 @@ public abstract class BaseClientConfiguration { */ private final Integer requestTimeout; + /** + * Client name to be used for the client. Will be used with CLIENT SETNAME command during + * connection establishment. + */ + private final String clientName; + /** * Advanced users can pass an extended {@link ThreadPoolResource} to pass a user-defined event * loop group. If set, users are responsible for shutting the resource down when no longer in use. diff --git a/java/client/src/main/java/glide/managers/ConnectionManager.java b/java/client/src/main/java/glide/managers/ConnectionManager.java index d0b3ad36ed..d9a8f58574 100644 --- a/java/client/src/main/java/glide/managers/ConnectionManager.java +++ b/java/client/src/main/java/glide/managers/ConnectionManager.java @@ -111,6 +111,10 @@ private ConnectionRequest.Builder setupConnectionRequestBuilderBaseConfiguration connectionRequestBuilder.setRequestTimeout(configuration.getRequestTimeout()); } + if (configuration.getClientName() != null) { + connectionRequestBuilder.setClientName(configuration.getClientName()); + } + return connectionRequestBuilder; } diff --git a/java/client/src/test/java/glide/managers/ConnectionManagerTest.java b/java/client/src/test/java/glide/managers/ConnectionManagerTest.java index 2877739ea5..b04dd5b312 100644 --- a/java/client/src/test/java/glide/managers/ConnectionManagerTest.java +++ b/java/client/src/test/java/glide/managers/ConnectionManagerTest.java @@ -54,6 +54,8 @@ public class ConnectionManagerTest { private static int REQUEST_TIMEOUT = 3; + private static String CLIENT_NAME = "ClientName"; + @BeforeEach public void setUp() { channel = mock(ChannelHandler.class); @@ -132,6 +134,7 @@ public void connection_request_protobuf_generation_with_all_fields_set() { .factor(FACTOR) .build()) .databaseId(DATABASE_ID) + .clientName(CLIENT_NAME) .build(); ConnectionRequest expectedProtobufConnectionRequest = ConnectionRequest.newBuilder() @@ -158,6 +161,7 @@ public void connection_request_protobuf_generation_with_all_fields_set() { .setExponentBase(EXPONENT_BASE) .build()) .setDatabaseId(DATABASE_ID) + .setClientName(CLIENT_NAME) .build(); CompletableFuture completedFuture = new CompletableFuture<>(); Response response = Response.newBuilder().setConstantResponse(ConstantResponse.OK).build(); diff --git a/java/integTest/src/test/java/glide/cluster/ClientTest.java b/java/integTest/src/test/java/glide/cluster/ClientTest.java new file mode 100644 index 0000000000..b9474f592b --- /dev/null +++ b/java/integTest/src/test/java/glide/cluster/ClientTest.java @@ -0,0 +1,29 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.cluster; + +import static glide.TestConfiguration.CLUSTER_PORTS; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import glide.api.RedisClusterClient; +import glide.api.models.configuration.NodeAddress; +import glide.api.models.configuration.RedisClusterClientConfiguration; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +public class ClientTest { + @Test + @SneakyThrows + public void custom_command_info() { + RedisClusterClient client = + RedisClusterClient.CreateClient( + RedisClusterClientConfiguration.builder() + .address(NodeAddress.builder().port(CLUSTER_PORTS[0]).build()) + .clientName("TEST_CLIENT_NAME") + .build()) + .get(); + + String clientInfo = + (String) client.customCommand(new String[] {"CLIENT", "INFO"}).get().getSingleValue(); + assertTrue(clientInfo.contains("name=TEST_CLIENT_NAME")); + } +} diff --git a/java/integTest/src/test/java/glide/standalone/ClientTest.java b/java/integTest/src/test/java/glide/standalone/ClientTest.java new file mode 100644 index 0000000000..ebee99e915 --- /dev/null +++ b/java/integTest/src/test/java/glide/standalone/ClientTest.java @@ -0,0 +1,28 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.standalone; + +import static glide.TestConfiguration.STANDALONE_PORTS; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import glide.api.RedisClient; +import glide.api.models.configuration.NodeAddress; +import glide.api.models.configuration.RedisClientConfiguration; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +public class ClientTest { + @Test + @SneakyThrows + public void custom_command_info() { + RedisClient client = + RedisClient.CreateClient( + RedisClientConfiguration.builder() + .address(NodeAddress.builder().port(STANDALONE_PORTS[0]).build()) + .clientName("TEST_CLIENT_NAME") + .build()) + .get(); + + String clientInfo = (String) client.customCommand(new String[] {"CLIENT", "INFO"}).get(); + assertTrue(clientInfo.contains("name=TEST_CLIENT_NAME")); + } +} From 3f634ae306b872db009fa6f2694db50e38ff6c4c Mon Sep 17 00:00:00 2001 From: ort-bot Date: Sun, 3 Mar 2024 00:19:49 +0000 Subject: [PATCH 35/65] Updated attribution files --- glide-core/THIRD_PARTY_LICENSES_RUST | 4 ++-- node/THIRD_PARTY_LICENSES_NODE | 8 ++++---- python/THIRD_PARTY_LICENSES_PYTHON | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/glide-core/THIRD_PARTY_LICENSES_RUST b/glide-core/THIRD_PARTY_LICENSES_RUST index a1f0658144..eed81e3826 100644 --- a/glide-core/THIRD_PARTY_LICENSES_RUST +++ b/glide-core/THIRD_PARTY_LICENSES_RUST @@ -16489,7 +16489,7 @@ the following restrictions: ---- -Package: mio:0.8.10 +Package: mio:0.8.11 The following copyrights and licenses were found in the source code of this package: @@ -23838,7 +23838,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pemfile:2.1.0 +Package: rustls-pemfile:2.1.1 The following copyrights and licenses were found in the source code of this package: diff --git a/node/THIRD_PARTY_LICENSES_NODE b/node/THIRD_PARTY_LICENSES_NODE index 81ea45dbfb..0643db9713 100644 --- a/node/THIRD_PARTY_LICENSES_NODE +++ b/node/THIRD_PARTY_LICENSES_NODE @@ -15614,7 +15614,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: libloading:0.8.1 +Package: libloading:0.8.2 The following copyrights and licenses were found in the source code of this package: @@ -17073,7 +17073,7 @@ the following restrictions: ---- -Package: mio:0.8.10 +Package: mio:0.8.11 The following copyrights and licenses were found in the source code of this package: @@ -25234,7 +25234,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pemfile:2.1.0 +Package: rustls-pemfile:2.1.1 The following copyrights and licenses were found in the source code of this package: @@ -41602,7 +41602,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -Package: @types:node:20.11.22 +Package: @types:node:20.11.24 The following copyrights and licenses were found in the source code of this package: diff --git a/python/THIRD_PARTY_LICENSES_PYTHON b/python/THIRD_PARTY_LICENSES_PYTHON index dd4b8d6c88..cbcd4a7ecb 100644 --- a/python/THIRD_PARTY_LICENSES_PYTHON +++ b/python/THIRD_PARTY_LICENSES_PYTHON @@ -17180,7 +17180,7 @@ the following restrictions: ---- -Package: mio:0.8.10 +Package: mio:0.8.11 The following copyrights and licenses were found in the source code of this package: @@ -25903,7 +25903,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: rustls-pemfile:2.1.0 +Package: rustls-pemfile:2.1.1 The following copyrights and licenses were found in the source code of this package: @@ -45318,7 +45318,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: types-protobuf:4.24.0.20240129 +Package: types-protobuf:4.24.0.20240302 The following copyrights and licenses were found in the source code of this package: From ceefa0728a18b9620dc5cb45848482d2069aba8e Mon Sep 17 00:00:00 2001 From: Shoham Elias <116083498+shohamazon@users.noreply.github.com> Date: Sun, 3 Mar 2024 16:07:44 +0200 Subject: [PATCH 36/65] Python: adds ZRANGE command (#906) --- CHANGELOG.md | 1 + python/python/glide/__init__.py | 18 +- python/python/glide/async_commands/core.py | 145 ++++++++---- .../python/glide/async_commands/sorted_set.py | 160 +++++++++++++ .../glide/async_commands/transaction.py | 90 ++++++- python/python/tests/test_async_client.py | 220 +++++++++++++++++- python/python/tests/test_transaction.py | 8 +- 7 files changed, 583 insertions(+), 59 deletions(-) create mode 100644 python/python/glide/async_commands/sorted_set.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bf984c3b0..e06cb2ffc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Node: Allow routing Cluster requests by address. ([#1021](https://github.com/aws/glide-for-redis/pull/1021)) * Python: Added HSETNX command. ([#954](https://github.com/aws/glide-for-redis/pull/954)) * Python: Added SISMEMBER command ([#971](https://github.com/aws/glide-for-redis/pull/971)) +* Python: Added ZRANGE command ([#906](https://github.com/aws/glide-for-redis/pull/906)) ## 0.2.0 (2024-02-11) diff --git a/python/python/glide/__init__.py b/python/python/glide/__init__.py index b4448ffad2..978706bc63 100644 --- a/python/python/glide/__init__.py +++ b/python/python/glide/__init__.py @@ -5,11 +5,18 @@ ExpireOptions, ExpirySet, ExpiryType, - InfBound, InfoSection, - ScoreLimit, UpdateOptions, ) +from glide.async_commands.sorted_set import ( + InfBound, + LexBoundary, + Limit, + RangeByIndex, + RangeByLex, + RangeByScore, + ScoreBoundary, +) from glide.async_commands.transaction import ClusterTransaction, Transaction from glide.config import ( BaseClientConfiguration, @@ -45,13 +52,18 @@ "BaseClientConfiguration", "ClusterClientConfiguration", "RedisClientConfiguration", - "ScoreLimit", + "ScoreBoundary", "ConditionalChange", "ExpireOptions", "ExpirySet", "ExpiryType", "InfBound", "InfoSection", + "LexBoundary", + "Limit", + "RangeByIndex", + "RangeByLex", + "RangeByScore", "UpdateOptions", "Logger", "LogLevel", diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index fa51137e66..4b8efe787c 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -16,6 +16,14 @@ get_args, ) +from glide.async_commands.sorted_set import ( + InfBound, + RangeByIndex, + RangeByLex, + RangeByScore, + ScoreBoundary, + _create_zrange_args, +) from glide.constants import TOK, TResult from glide.protobuf.redis_request_pb2 import RequestType from glide.routes import Route @@ -123,29 +131,6 @@ class UpdateOptions(Enum): GREATER_THAN = "GT" -class InfBound(Enum): - """ - Enumeration representing positive and negative infinity bounds for sorted set scores. - """ - - POS_INF = "+inf" - NEG_INF = "-inf" - - -class ScoreLimit: - """ - Represents a score limit in a sorted set. - - Args: - value (float): The score value. - is_inclusive (bool): Whether the score value is inclusive. Defaults to False. - """ - - def __init__(self, value: float, is_inclusive: bool = True): - """Convert the score limit to the Redis protocol format.""" - self.value = str(value) if is_inclusive else f"({value}" - - class ExpirySet: """SET option: Represents the expiry type and value to be executed with "SET" command.""" @@ -1253,9 +1238,9 @@ async def zadd( If `changed` is set, returns the number of elements updated in the sorted set. Examples: - >>> await zadd("my_sorted_set", {"member1": 10.5, "member2": 8.2}) + >>> await client.zadd("my_sorted_set", {"member1": 10.5, "member2": 8.2}) 2 # Indicates that two elements have been added or updated in the sorted set "my_sorted_set." - >>> await zadd("existing_sorted_set", {"member1": 15.0, "member2": 5.5}, existing_options=ConditionalChange.XX) + >>> await client.zadd("existing_sorted_set", {"member1": 15.0, "member2": 5.5}, existing_options=ConditionalChange.XX) 2 # Updates the scores of two existing members in the sorted set "existing_sorted_set." """ args = [key] @@ -1316,9 +1301,9 @@ async def zadd_incr( If there was a conflict with choosing the XX/NX/LT/GT options, the operation aborts and None is returned. Examples: - >>> await zaddIncr("my_sorted_set", member , 5.0) + >>> await client.zaddIncr("my_sorted_set", member , 5.0) 5.0 - >>> await zaddIncr("existing_sorted_set", member , "3.0" , UpdateOptions.LESS_THAN) + >>> await client.zaddIncr("existing_sorted_set", member , "3.0" , UpdateOptions.LESS_THAN) None """ args = [key] @@ -1357,9 +1342,9 @@ async def zcard(self, key: str) -> int: If `key` does not exist, it is treated as an empty sorted set, and the command returns 0. Examples: - >>> await zcard("my_sorted_set") + >>> await client.zcard("my_sorted_set") 3 # Indicates that there are 3 elements in the sorted set "my_sorted_set". - >>> await zcard("non_existing_key") + >>> await client.zcard("non_existing_key") 0 """ return cast(int, await self._execute_command(RequestType.Zcard, [key])) @@ -1367,8 +1352,8 @@ async def zcard(self, key: str) -> int: async def zcount( self, key: str, - min_score: Union[InfBound, ScoreLimit], - max_score: Union[InfBound, ScoreLimit], + min_score: Union[InfBound, ScoreBoundary], + max_score: Union[InfBound, ScoreBoundary], ) -> int: """ Returns the number of members in the sorted set stored at `key` with scores between `min_score` and `max_score`. @@ -1377,12 +1362,12 @@ async def zcount( Args: key (str): The key of the sorted set. - min_score (Union[InfBound, ScoreLimit]): The minimum score to count from. + min_score (Union[InfBound, ScoreBoundary]): The minimum score to count from. Can be an instance of InfBound representing positive/negative infinity, - or ScoreLimit representing a specific score and inclusivity. - max_score (Union[InfBound, ScoreLimit]): The maximum score to count up to. + or ScoreBoundary representing a specific score and inclusivity. + max_score (Union[InfBound, ScoreBoundary]): The maximum score to count up to. Can be an instance of InfBound representing positive/negative infinity, - or ScoreLimit representing a specific score and inclusivity. + or ScoreBoundary representing a specific score and inclusivity. Returns: int: The number of members in the specified score range. @@ -1390,15 +1375,25 @@ async def zcount( If `max_score` < `min_score`, 0 is returned. Examples: - >>> await client.zcount("my_sorted_set", ScoreLimit(5.0 , is_inclusive=true) , InfBound.POS_INF) + >>> await client.zcount("my_sorted_set", ScoreBoundary(5.0 , is_inclusive=true) , InfBound.POS_INF) 2 # Indicates that there are 2 members with scores between 5.0 (not exclusive) and +inf in the sorted set "my_sorted_set". - >>> await client.zcount("my_sorted_set", ScoreLimit(5.0 , is_inclusive=true) , ScoreLimit(10.0 , is_inclusive=false)) - 1 # Indicates that there is one ScoreLimit with 5.0 < score <= 10.0 in the sorted set "my_sorted_set". + >>> await client.zcount("my_sorted_set", ScoreBoundary(5.0 , is_inclusive=true) , ScoreBoundary(10.0 , is_inclusive=false)) + 1 # Indicates that there is one ScoreBoundary with 5.0 < score <= 10.0 in the sorted set "my_sorted_set". """ + score_min = ( + min_score.value["score_arg"] + if type(min_score) == InfBound + else min_score.value + ) + score_max = ( + max_score.value["score_arg"] + if type(max_score) == InfBound + else max_score.value + ) return cast( int, await self._execute_command( - RequestType.Zcount, [key, min_score.value, max_score.value] + RequestType.Zcount, [key, score_min, score_max] ), ) @@ -1466,6 +1461,78 @@ async def zpopmin( ), ) + async def zrange( + self, + key: str, + range_query: Union[RangeByIndex, RangeByLex, RangeByScore], + reverse: bool = False, + ) -> List[str]: + """ + 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 zrange_withscores. + + Args: + key (str): The key of the sorted set. + range_query (Union[RangeByIndex, RangeByLex, RangeByScore]): 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. + reverse (bool): If True, reverses the sorted set, with index 0 as the element with the highest score. + + Returns: + List[str]: 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. + + Examples: + >>> await client.zrange("my_sorted_set", RangeByIndex(0, -1)) + ['member1', 'member2', 'member3'] # Returns all members in ascending order. + >>> await client.zrange("my_sorted_set", RangeByScore(start=InfBound.NEG_INF, stop=ScoreBoundary(3))) + ['member2', 'member3'] # Returns members with scores within the range of negative infinity to 3, in ascending order. + """ + args = _create_zrange_args(key, range_query, reverse, with_scores=False) + + return cast(List[str], await self._execute_command(RequestType.Zrange, args)) + + async def zrange_withscores( + self, + key: str, + range_query: Union[RangeByIndex, RangeByScore], + reverse: bool = False, + ) -> Mapping[str, float]: + """ + 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. + + Args: + key (str): The key of the sorted set. + range_query (Union[RangeByIndex, RangeByScore]): The range query object representing the type of range query to perform. + - For range queries by index (rank), use RangeByIndex. + - For range queries by score, use RangeByScore. + reverse (bool): If True, reverses the sorted set, with index 0 as the element with the highest score. + + Returns: + Mapping[str , float]: 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. + + 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))) + {'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) + + return cast( + Mapping[str, float], await self._execute_command(RequestType.Zrange, args) + ) + async def zrem( self, key: str, diff --git a/python/python/glide/async_commands/sorted_set.py b/python/python/glide/async_commands/sorted_set.py new file mode 100644 index 0000000000..83c6037341 --- /dev/null +++ b/python/python/glide/async_commands/sorted_set.py @@ -0,0 +1,160 @@ +# Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 + +from enum import Enum +from typing import List, Optional, Union + + +class InfBound(Enum): + """ + Enumeration representing numeric and lexicographic positive and negative infinity bounds for sorted set. + """ + + POS_INF = {"score_arg": "+inf", "lex_arg": "+"} + """ + Positive infinity bound for sorted set. + score_arg: represents numeric positive infinity (+inf). + lex_arg: represents lexicographic positive infinity (+). + """ + NEG_INF = {"score_arg": "-inf", "lex_arg": "-"} + """ + Negative infinity bound for sorted set. + score_arg: represents numeric negative infinity (-inf). + lex_arg: represents lexicographic negative infinity (-). + """ + + +class ScoreBoundary: + """ + Represents a specific numeric score boundary in a sorted set. + + Args: + value (float): The score value. + is_inclusive (bool): Whether the score value is inclusive. Defaults to True. + """ + + def __init__(self, value: float, is_inclusive: bool = True): + # Convert the score boundary to the Redis protocol format + self.value = str(value) if is_inclusive else f"({value}" + + +class LexBoundary: + """ + Represents a specific lexicographic boundary in a sorted set. + + Args: + value (str): The lex value. + is_inclusive (bool): Whether the score value is inclusive. Defaults to True. + """ + + def __init__(self, value: str, is_inclusive: bool = True): + # Convert the lexicographic boundary to the Redis protocol format + self.value = f"[{value}" if is_inclusive else f"({value}" + + +class Limit: + """ + 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). + Args: + offset (int): The offset from the start of the range. + count (int): The number of elements to include in the range. + A negative count returns all elements from the offset. + """ + + def __init__(self, offset: int, count: int): + self.offset = offset + self.count = count + + +class RangeByIndex: + """ + Represents a range by index (rank) in a sorted set. + + The `start` and `stop` arguments represent zero-based indexes. + + Args: + start (int): The start index of the range. + stop (int): The stop index of the range. + """ + + def __init__(self, start: int, stop: int): + self.start = start + self.stop = stop + + +class RangeByScore: + """ + Represents a range by score in a sorted set. + + The `start` and `stop` arguments represent score boundaries. + + Args: + start (Union[InfBound, ScoreBoundary]): The start score boundary. + stop (Union[InfBound, ScoreBoundary]): The stop score boundary. + limit (Optional[Limit]): The limit argument for a range query. Defaults to None. See `Limit` class for more information. + """ + + def __init__( + self, + start: Union[InfBound, ScoreBoundary], + stop: Union[InfBound, ScoreBoundary], + limit: Optional[Limit] = None, + ): + self.start = ( + start.value["score_arg"] if type(start) == InfBound else start.value + ) + self.stop = stop.value["score_arg"] if type(stop) == InfBound else stop.value + self.limit = limit + + +class RangeByLex: + """ + Represents a range by lexicographical order in a sorted set. + + The `start` and `stop` arguments represent lexicographical boundaries. + + Args: + start (Union[InfBound, LexBoundary]): The start lexicographic boundary. + stop (Union[InfBound, LexBoundary]): The stop lexicographic boundary. + limit (Optional[Limit]): The limit argument for a range query. Defaults to None. See `Limit` class for more information. + """ + + def __init__( + self, + start: Union[InfBound, LexBoundary], + stop: Union[InfBound, LexBoundary], + limit: Optional[Limit] = None, + ): + self.start = start.value["lex_arg"] if type(start) == InfBound else start.value + self.stop = stop.value["lex_arg"] if type(stop) == InfBound else stop.value + self.limit = limit + + +def _create_zrange_args( + key: str, + range_query: Union[RangeByLex, RangeByScore, RangeByIndex], + reverse: bool, + with_scores: bool, +) -> List[str]: + args = [key, str(range_query.start), str(range_query.stop)] + + if isinstance(range_query, RangeByScore): + args.append("BYSCORE") + elif isinstance(range_query, RangeByLex): + args.append("BYLEX") + if reverse: + args.append("REV") + if hasattr(range_query, "limit") and range_query.limit is not None: + args.extend( + [ + "LIMIT", + str(range_query.limit.offset), + str(range_query.limit.count), + ] + ) + if with_scores: + args.append("WITHSCORES") + + return args diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index ed3ba10172..58cfab4ac8 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -7,11 +7,17 @@ ConditionalChange, ExpireOptions, ExpirySet, - InfBound, InfoSection, - ScoreLimit, UpdateOptions, ) +from glide.async_commands.sorted_set import ( + InfBound, + RangeByIndex, + RangeByLex, + RangeByScore, + ScoreBoundary, + _create_zrange_args, +) from glide.protobuf.redis_request_pb2 import RequestType TTransaction = TypeVar("TTransaction", bound="BaseTransaction") @@ -1082,8 +1088,8 @@ def zcard(self: TTransaction, key: str) -> TTransaction: def zcount( self: TTransaction, key: str, - min_score: Union[InfBound, ScoreLimit], - max_score: Union[InfBound, ScoreLimit], + min_score: Union[InfBound, ScoreBoundary], + max_score: Union[InfBound, ScoreBoundary], ) -> TTransaction: """ Returns the number of members in the sorted set stored at `key` with scores between `min_score` and `max_score`. @@ -1092,21 +1098,29 @@ def zcount( Args: key (str): The key of the sorted set. - min_score (Union[InfBound, ScoreLimit]): The minimum score to count from. + min_score (Union[InfBound, ScoreBoundary]): The minimum score to count from. Can be an instance of InfBound representing positive/negative infinity, - or ScoreLimit representing a specific score and inclusivity. - max_score (Union[InfBound, ScoreLimit]): The maximum score to count up to. + or ScoreBoundary representing a specific score and inclusivity. + max_score (Union[InfBound, ScoreBoundary]): The maximum score to count up to. Can be an instance of InfBound representing positive/negative infinity, - or ScoreLimit representing a specific score and inclusivity. + or ScoreBoundary representing a specific score and inclusivity. Commands response: int: The number of members in the specified score range. If key does not exist, 0 is returned. If `max_score` < `min_score`, 0 is returned. """ - return self.append_command( - RequestType.Zcount, [key, min_score.value, max_score.value] + score_min = ( + min_score.value["score_arg"] + if type(min_score) == InfBound + else min_score.value + ) + score_max = ( + max_score.value["score_arg"] + if type(max_score) == InfBound + else max_score.value ) + return self.append_command(RequestType.Zcount, [key, score_min, score_max]) def zpopmax( self: TTransaction, key: str, count: Optional[int] = None @@ -1154,6 +1168,62 @@ def zpopmin( RequestType.ZPopMin, [key, str(count)] if count else [key] ) + def zrange( + self: TTransaction, + key: str, + range_query: Union[RangeByIndex, RangeByLex, RangeByScore], + reverse: bool = False, + ) -> TTransaction: + """ + 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. + + Args: + key (str): The key of the sorted set. + range_query (Union[RangeByIndex, RangeByLex, RangeByScore]): 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. + reverse (bool): If True, reverses the sorted set, with index 0 as the element with the highest score. + + Commands response: + List[str]: 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. + """ + args = _create_zrange_args(key, range_query, reverse, with_scores=False) + + return self.append_command(RequestType.Zrange, args) + + def zrange_withscores( + self: TTransaction, + key: str, + range_query: Union[RangeByIndex, RangeByScore], + reverse: bool = False, + ) -> TTransaction: + """ + 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. + + Args: + key (str): The key of the sorted set. + range_query (Union[RangeByIndex, RangeByScore]): The range query object representing the type of range query to perform. + - For range queries by index (rank), use RangeByIndex. + - For range queries by score, use RangeByScore. + reverse (bool): If True, reverses the sorted set, with index 0 as the element with the highest score. + + Commands response: + Mapping[str , float]: 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. + """ + args = _create_zrange_args(key, range_query, reverse, with_scores=True) + + return self.append_command(RequestType.Zrange, args) + def zrem( self: TTransaction, key: str, diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 5fff8c965e..217920710b 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -18,9 +18,17 @@ ExpiryType, InfBound, InfoSection, - ScoreLimit, UpdateOptions, ) +from glide.async_commands.sorted_set import ( + InfBound, + LexBoundary, + Limit, + RangeByIndex, + RangeByLex, + RangeByScore, + ScoreBoundary, +) from glide.config import ProtocolVersion, RedisCredentials from glide.constants import OK from glide.redis_client import RedisClient, RedisClusterClient, TRedisClient @@ -1170,18 +1178,32 @@ async def test_zcount(self, redis_client: TRedisClient): assert await redis_client.zcount(key, InfBound.NEG_INF, InfBound.POS_INF) == 3 assert ( - await redis_client.zcount(key, ScoreLimit(1, False), ScoreLimit(3, False)) + await redis_client.zcount( + key, + ScoreBoundary(1, is_inclusive=False), + ScoreBoundary(3, is_inclusive=False), + ) == 1 ) assert ( - await redis_client.zcount(key, ScoreLimit(1, False), ScoreLimit(3, True)) + await redis_client.zcount( + key, + ScoreBoundary(1, is_inclusive=False), + ScoreBoundary(3, is_inclusive=True), + ) == 2 ) assert ( - await redis_client.zcount(key, InfBound.NEG_INF, ScoreLimit(3, True)) == 3 + await redis_client.zcount( + key, InfBound.NEG_INF, ScoreBoundary(3, is_inclusive=True) + ) + == 3 ) assert ( - await redis_client.zcount(key, InfBound.POS_INF, ScoreLimit(3, True)) == 0 + await redis_client.zcount( + key, InfBound.POS_INF, ScoreBoundary(3, is_inclusive=True) + ) + == 0 ) assert ( await redis_client.zcount( @@ -1234,6 +1256,194 @@ async def test_zpopmax(self, redis_client: TRedisClient): assert await redis_client.zpopmax("non_exisitng_key") == {} + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_zrange_by_index(self, redis_client: TRedisClient): + key = get_random_string(10) + members_scores = {"one": 1, "two": 2, "three": 3} + assert await redis_client.zadd(key, members_scores=members_scores) == 3 + + assert await redis_client.zrange(key, RangeByIndex(start=0, stop=1)) == [ + "one", + "two", + ] + + assert ( + await redis_client.zrange_withscores(key, RangeByIndex(start=0, stop=-1)) + ) == {"one": 1.0, "two": 2.0, "three": 3.0} + + assert await redis_client.zrange( + key, RangeByIndex(start=0, stop=1), reverse=True + ) == [ + "three", + "two", + ] + + assert await redis_client.zrange(key, RangeByIndex(start=3, stop=1)) == [] + assert ( + await redis_client.zrange_withscores(key, RangeByIndex(start=3, stop=1)) + == {} + ) + + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_zrange_byscore(self, redis_client: TRedisClient): + key = get_random_string(10) + members_scores = {"one": 1, "two": 2, "three": 3} + assert await redis_client.zadd(key, members_scores=members_scores) == 3 + + assert await redis_client.zrange( + key, + RangeByScore( + start=InfBound.NEG_INF, stop=ScoreBoundary(3, is_inclusive=False) + ), + ) == ["one", "two"] + + assert ( + await redis_client.zrange_withscores( + key, + RangeByScore(start=InfBound.NEG_INF, stop=InfBound.POS_INF), + ) + ) == {"one": 1.0, "two": 2.0, "three": 3.0} + + assert await redis_client.zrange( + key, + RangeByScore( + start=ScoreBoundary(3, is_inclusive=False), stop=InfBound.NEG_INF + ), + reverse=True, + ) == ["two", "one"] + + assert ( + await redis_client.zrange( + key, + RangeByScore( + start=InfBound.NEG_INF, + stop=InfBound.POS_INF, + limit=Limit(offset=1, count=2), + ), + ) + ) == ["two", "three"] + + assert ( + await redis_client.zrange( + key, + RangeByScore( + start=InfBound.NEG_INF, stop=ScoreBoundary(3, is_inclusive=False) + ), + reverse=True, + ) + == [] + ) # stop is greater than start with reverse set to True + + assert ( + await redis_client.zrange( + key, + RangeByScore( + start=InfBound.POS_INF, stop=ScoreBoundary(3, is_inclusive=False) + ), + ) + == [] + ) # start is greater than stop + + assert ( + await redis_client.zrange_withscores( + key, + RangeByScore( + start=InfBound.POS_INF, stop=ScoreBoundary(3, is_inclusive=False) + ), + ) + == {} + ) # start is greater than stop + + assert ( + await redis_client.zrange_withscores( + key, + RangeByScore( + start=InfBound.NEG_INF, stop=ScoreBoundary(3, is_inclusive=False) + ), + reverse=True, + ) + == {} + ) # stop is greater than start with reverse set to True + + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_zrange_bylex(self, redis_client: TRedisClient): + key = get_random_string(10) + members_scores = {"a": 1, "b": 2, "c": 3} + assert await redis_client.zadd(key, members_scores=members_scores) == 3 + + assert await redis_client.zrange( + key, + RangeByLex( + start=InfBound.NEG_INF, stop=LexBoundary("c", is_inclusive=False) + ), + ) == ["a", "b"] + + assert ( + await redis_client.zrange( + key, + RangeByLex( + start=InfBound.NEG_INF, + stop=InfBound.POS_INF, + limit=Limit(offset=1, count=2), + ), + ) + ) == ["b", "c"] + + assert await redis_client.zrange( + key, + RangeByLex( + start=LexBoundary("c", is_inclusive=False), stop=InfBound.NEG_INF + ), + reverse=True, + ) == ["b", "a"] + + assert ( + await redis_client.zrange( + key, + RangeByLex( + start=InfBound.NEG_INF, stop=LexBoundary("c", is_inclusive=False) + ), + reverse=True, + ) + == [] + ) # stop is greater than start with reverse set to True + + assert ( + await redis_client.zrange( + key, + RangeByLex( + start=InfBound.POS_INF, stop=LexBoundary("c", is_inclusive=False) + ), + ) + == [] + ) # start is greater than stop + + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_zrange_different_types_of_keys(self, redis_client: TRedisClient): + key = get_random_string(10) + + assert ( + await redis_client.zrange("non_existing_key", RangeByIndex(start=0, stop=1)) + == [] + ) + + assert ( + await redis_client.zrange_withscores( + "non_existing_key", RangeByIndex(start=0, stop=-1) + ) + ) == {} + + assert await redis_client.set(key, "value") == OK + with pytest.raises(RequestError): + await redis_client.zrange(key, RangeByIndex(start=0, stop=1)) + + with pytest.raises(RequestError): + await redis_client.zrange_withscores(key, RangeByIndex(start=0, stop=1)) + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_type(self, redis_client: TRedisClient): diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 621e2b1751..b3e7838ede 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -5,7 +5,7 @@ import pytest from glide import RequestError -from glide.async_commands.core import InfBound, ScoreLimit +from glide.async_commands.sorted_set import InfBound, RangeByIndex, ScoreBoundary from glide.async_commands.transaction import ( BaseTransaction, ClusterTransaction, @@ -146,10 +146,14 @@ def transaction_test( args.append(1) transaction.zcard(key8) args.append(2) - transaction.zcount(key8, ScoreLimit(2, True), InfBound.POS_INF) + transaction.zcount(key8, ScoreBoundary(2, is_inclusive=True), InfBound.POS_INF) args.append(2) transaction.zscore(key8, "two") args.append(2.0) + transaction.zrange(key8, RangeByIndex(start=0, stop=-1)) + args.append(["two", "three"]) + transaction.zrange_withscores(key8, RangeByIndex(start=0, stop=-1)) + args.append({"two": 2, "three": 3}) transaction.zpopmin(key8) args.append({"two": 2.0}) transaction.zpopmax(key8) From 9690de0d25d6514b016f8f7fc512e2772bb69068 Mon Sep 17 00:00:00 2001 From: barshaul Date: Mon, 4 Mar 2024 11:42:06 +0200 Subject: [PATCH 37/65] Node: Added testTimeout config to jest to fix macos timeout failures --- node/jest.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/node/jest.config.js b/node/jest.config.js index b83febd3e8..a5a0e5c267 100644 --- a/node/jest.config.js +++ b/node/jest.config.js @@ -4,4 +4,5 @@ module.exports = { testEnvironment: "node", testRegex: "/tests/.*\\.(test|spec)?\\.(ts|tsx)$", moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], + testTimeout: 20000, }; From 6805f6a9bb383f722570ef37413f0879284233fb Mon Sep 17 00:00:00 2001 From: ort-bot Date: Mon, 4 Mar 2024 00:18:55 +0000 Subject: [PATCH 38/65] Updated attribution files --- glide-core/THIRD_PARTY_LICENSES_RUST | 4 ++-- node/THIRD_PARTY_LICENSES_NODE | 4 ++-- python/THIRD_PARTY_LICENSES_PYTHON | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/glide-core/THIRD_PARTY_LICENSES_RUST b/glide-core/THIRD_PARTY_LICENSES_RUST index eed81e3826..11a970a0a6 100644 --- a/glide-core/THIRD_PARTY_LICENSES_RUST +++ b/glide-core/THIRD_PARTY_LICENSES_RUST @@ -683,7 +683,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ahash:0.8.10 +Package: ahash:0.8.11 The following copyrights and licenses were found in the source code of this package: @@ -1599,7 +1599,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: arc-swap:1.6.0 +Package: arc-swap:1.7.0 The following copyrights and licenses were found in the source code of this package: diff --git a/node/THIRD_PARTY_LICENSES_NODE b/node/THIRD_PARTY_LICENSES_NODE index 0643db9713..c87ebc1a45 100644 --- a/node/THIRD_PARTY_LICENSES_NODE +++ b/node/THIRD_PARTY_LICENSES_NODE @@ -683,7 +683,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ahash:0.8.10 +Package: ahash:0.8.11 The following copyrights and licenses were found in the source code of this package: @@ -1651,7 +1651,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: arc-swap:1.6.0 +Package: arc-swap:1.7.0 The following copyrights and licenses were found in the source code of this package: diff --git a/python/THIRD_PARTY_LICENSES_PYTHON b/python/THIRD_PARTY_LICENSES_PYTHON index cbcd4a7ecb..ecd51f8676 100644 --- a/python/THIRD_PARTY_LICENSES_PYTHON +++ b/python/THIRD_PARTY_LICENSES_PYTHON @@ -683,7 +683,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: ahash:0.8.10 +Package: ahash:0.8.11 The following copyrights and licenses were found in the source code of this package: @@ -1599,7 +1599,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: arc-swap:1.6.0 +Package: arc-swap:1.7.0 The following copyrights and licenses were found in the source code of this package: From 93e4703f1ce09bfb6cd2b84cb7c206589baab07d Mon Sep 17 00:00:00 2001 From: Shachar Langbeheim Date: Mon, 4 Mar 2024 13:44:24 +0200 Subject: [PATCH 39/65] Clarify which versions of npm are workable. (#1063) --- node/DEVELOPER.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/node/DEVELOPER.md b/node/DEVELOPER.md index dd596d5db1..e903a6191d 100644 --- a/node/DEVELOPER.md +++ b/node/DEVELOPER.md @@ -12,10 +12,9 @@ The GLIDE Node wrapper consists of both TypeScript and Rust code. Rust bindings Software Dependencies -> Note: Currently, we only support npm major version 8. f you have a later version installed, you can downgrade it with `npm i -g npm@8`. > If your NodeJS version is below the supported version specified in the client's [documentation](https://github.com/aws/glide-for-redis/blob/main/node/README.md#nodejs-supported-version), you can upgrade it using [NVM](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). -- npm v8 +- npm - git - GCC - pkg-config @@ -29,7 +28,6 @@ Software Dependencies ```bash sudo apt update -y sudo apt install -y nodejs npm git gcc pkg-config protobuf-compiler openssl libssl-dev -npm i -g npm@8 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source "$HOME/.cargo/env" ``` @@ -39,7 +37,6 @@ source "$HOME/.cargo/env" ```bash sudo yum update -y sudo yum install -y nodejs git gcc pkgconfig protobuf-compiler openssl openssl-devel gettext -npm i -g npm@8 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source "$HOME/.cargo/env" ``` @@ -49,7 +46,6 @@ source "$HOME/.cargo/env" ```bash brew update brew install nodejs git gcc pkgconfig protobuf openssl -npm i -g npm@8 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source "$HOME/.cargo/env" ``` @@ -113,6 +109,10 @@ Before starting this step, make sure you've installed all software requirments. > Note: Once building completed, you'll find the compiled JavaScript code in the `node/build-ts` folder. +### Troubleshooting + +- If the build fails after running `npx tsc` because `glide-rs` isn't found, check if your npm version is in the range 9.0.0-9.4.1, and if so, upgrade it. 9.4.2 contains a fix to a change introduced in 9.0.0 that is required in order to build the library. + ### Test To run tests, use the following command: From e374ca8da6a266568cd1d00731050e916ab2c7be Mon Sep 17 00:00:00 2001 From: Shoham Elias <116083498+shohamazon@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:37:00 +0200 Subject: [PATCH 40/65] fix changelog changes and features for 0.2.0 (#1051) --- CHANGELOG.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e06cb2ffc4..dd21b1c526 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,22 @@ #### Changes -- Node: Allow routing Cluster requests by address. ([#1021](https://github.com/aws/glide-for-redis/pull/1021)) +* Node: Allow routing Cluster requests by address. ([#1021](https://github.com/aws/glide-for-redis/pull/1021)) * Python: Added HSETNX command. ([#954](https://github.com/aws/glide-for-redis/pull/954)) * Python: Added SISMEMBER command ([#971](https://github.com/aws/glide-for-redis/pull/971)) +* Python, Node: Added TYPE command ([#945](https://github.com/aws/glide-for-redis/pull/945), [#980](https://github.com/aws/glide-for-redis/pull/980)) +* Python, Node: Added HLEN command ([#944](https://github.com/aws/glide-for-redis/pull/944), [#981](https://github.com/aws/glide-for-redis/pull/981)) +* Python, Node: Added ZCOUNT command ([#878](https://github.com/aws/glide-for-redis/pull/878)) ([#909](https://github.com/aws/glide-for-redis/pull/909)) +* Python: Added ECHO command ([#953](https://github.com/aws/glide-for-redis/pull/953)) +* Python, Node: Added ZPOPMIN command ([#975](https://github.com/aws/glide-for-redis/pull/975), [#1008](https://github.com/aws/glide-for-redis/pull/1008)) +* Node: Added STRLEN command ([#993](https://github.com/aws/glide-for-redis/pull/993)) +* Node: Added LINDEX command ([#999](https://github.com/aws/glide-for-redis/pull/999)) +* Python, Node: Added ZPOPMAX command ([#996](https://github.com/aws/glide-for-redis/pull/996), [#1009](https://github.com/aws/glide-for-redis/pull/1009)) * Python: Added ZRANGE command ([#906](https://github.com/aws/glide-for-redis/pull/906)) +#### Features + +* Python: Allow chaining function calls on transaction. ([#987](https://github.com/aws/glide-for-redis/pull/987)) + ## 0.2.0 (2024-02-11) #### Changes @@ -16,18 +28,10 @@ * Python, Node: Added RPOPCOUNT and LPOPCOUNT to transaction ([#874](https://github.com/aws/glide-for-redis/pull/874)) * Standalone client: Improve connection errors. ([#854](https://github.com/aws/glide-for-redis/pull/854)) * Python, Node: When recieving LPOP/RPOP with count, convert result to Array. ([#811](https://github.com/aws/glide-for-redis/pull/811)) -* Python, Node: Added TYPE command ([#945](https://github.com/aws/glide-for-redis/pull/945), [#980](https://github.com/aws/glide-for-redis/pull/980)) -* Python, Node: Added HLEN command ([#944](https://github.com/aws/glide-for-redis/pull/944), [#981](https://github.com/aws/glide-for-redis/pull/981)) -* Python, Node: Added ZCOUNT command ([#878](https://github.com/aws/glide-for-redis/pull/878)) ([#909](https://github.com/aws/glide-for-redis/pull/909)) -* Python: Added ECHO command ([#953](https://github.com/aws/glide-for-redis/pull/953)) -* Python, Node: Added ZPOPMIN command ([#975](https://github.com/aws/glide-for-redis/pull/975), [#1008](https://github.com/aws/glide-for-redis/pull/1008)) -* Node: Added STRLEN command ([#993](https://github.com/aws/glide-for-redis/pull/993)) -* Node: Added LINDEX command ([#999](https://github.com/aws/glide-for-redis/pull/999)) -* Python, Node: Added ZPOPMAX command ([#996](https://github.com/aws/glide-for-redis/pull/996), [#1009](https://github.com/aws/glide-for-redis/pull/1009)) #### Features * Python, Node: Added support in Lua Scripts ([#775](https://github.com/aws/glide-for-redis/pull/775), [#860](https://github.com/aws/glide-for-redis/pull/860)) -* Python, Node: Allow chaining function calls on transaction. ([#902](https://github.com/aws/glide-for-redis/pull/902)), ([#987](https://github.com/aws/glide-for-redis/pull/987)) +* Node: Allow chaining function calls on transaction. ([#902](https://github.com/aws/glide-for-redis/pull/902)) #### Fixes * Core: Fixed `Connection Refused` error not to close the client ([#872](https://github.com/aws/glide-for-redis/pull/872)) From 0e7a8663a7b7215b6bc5d357603db2531eb71770 Mon Sep 17 00:00:00 2001 From: Aaron <69273634+aaron-congo@users.noreply.github.com> Date: Mon, 4 Mar 2024 08:52:01 -0800 Subject: [PATCH 41/65] Add DEVELOPER.md for Go (#1054) --- go/DEVELOPER.md | 213 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 go/DEVELOPER.md diff --git a/go/DEVELOPER.md b/go/DEVELOPER.md new file mode 100644 index 0000000000..6f365c756d --- /dev/null +++ b/go/DEVELOPER.md @@ -0,0 +1,213 @@ +# Developer Guide + +This document describes how to set up your development environment to build and test the GLIDE for Redis Go wrapper. + +### Development Overview + +We're excited to share that the GLIDE Go client is currently in development! However, it's important to note that this client is a work in progress and is not yet complete or fully tested. Your contributions and feedback are highly encouraged as we work towards refining and improving this implementation. Thank you for your interest and understanding as we continue to develop this Go wrapper. + +The GLIDE for Redis Go wrapper consists of both Go and Rust code. The Go and Rust components communicate in two ways: +1. Using the [protobuf](https://github.com/protocolbuffers/protobuf) protocol. +2. Using shared C objects. [cgo](https://pkg.go.dev/cmd/cgo) is used to interact with the C objects from Go code. + +### Build from source + +#### Prerequisites + +Software Dependencies + +- Go +- GNU Make +- git +- GCC +- pkg-config +- protoc (protobuf compiler) >= v3.20.0 +- openssl +- openssl-dev +- rustup +- redis + +**Redis installation** + +To install redis-server and redis-cli on your host, follow the [Redis Installation Guide](https://redis.io/docs/install/install-redis/). + +**Dependencies installation for Ubuntu** + +```bash +sudo apt update -y +sudo apt install -y git gcc pkg-config openssl libssl-dev unzip make +# Install Go +sudo snap install go --classic +export PATH="$PATH:$HOME/go/bin" +# Install rust +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source "$HOME/.cargo/env" +# Check that the Rust compiler is installed +rustc --version +# Install protobuf compiler +PB_REL="https://github.com/protocolbuffers/protobuf/releases" +curl -LO $PB_REL/download/v3.20.3/protoc-3.20.3-linux-x86_64.zip +unzip protoc-3.20.3-linux-x86_64.zip -d $HOME/.local +export PATH="$PATH:$HOME/.local/bin" +# Check that the protobuf compiler is installed. A minimum version of 3.20.0 is required. +protoc --version +``` + +**Dependencies installation for CentOS** + +```bash +sudo yum update -y +sudo yum install -y git gcc pkgconfig openssl openssl-devel unzip wget tar +# Install Go +wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz +sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz +export PATH="$PATH:/usr/local/go/bin" +export PATH="$PATH:$HOME/go/bin" +# Install rust +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source "$HOME/.cargo/env" +# Check that the Rust compiler is installed +rustc --version +# Install protobuf compiler +PB_REL="https://github.com/protocolbuffers/protobuf/releases" +curl -LO $PB_REL/download/v3.20.3/protoc-3.20.3-linux-x86_64.zip +unzip protoc-3.20.3-linux-x86_64.zip -d $HOME/.local +export PATH="$PATH:$HOME/.local/bin" +# Check that the protobuf compiler is installed. A minimum version of 3.20.0 is required. +protoc --version +``` + +**Dependencies installation for MacOS** + +```bash +brew update +brew install go make git gcc pkgconfig protobuf@3 openssl +export PATH="$PATH:$HOME/go/bin" +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source "$HOME/.cargo/env" +# Check that the protobuf compiler is installed. A minimum version of 3.20.0 is required. +protoc --version +# Check that the Rust compiler is installed +rustc --version +``` + +#### Building and installation steps + +Before starting this step, make sure you've installed all software requirements. + +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: + ```bash + git submodule update --init --recursive + ``` +3. Install build dependencies: + ```bash + cd go + make install-build-tools + ``` +4. Build the Go wrapper: + ```bash + make build + ``` +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 go folder: + ```bash + go test -race ./... + ``` +6. Install Go development tools with: + + ```bash + make install-dev-tools + ``` + +### Test + +To run tests, use the following command: + +```bash +go test -race ./... +``` + +For more detailed test output, add the `-v` flag: + +```bash +go test -race ./... -v +``` + +To execute a specific test, include `-run `. For example: + +```bash +go test -race ./... -run TestConnectionRequestProtobufGeneration_allFieldsSet -v +``` + +### Submodules + +After pulling new changes, ensure that you update the submodules by running the following command: + +```bash +git submodule update +``` + +### Generate protobuf files + +During the initial build, Go protobuf files were created in `go/protobuf`. If modifications are made to the protobuf definition files (.proto files located in `glide-core/src/protobuf`), it becomes necessary to regenerate the Go protobuf files. To do so, run: + +```bash +make generate-protobuf +``` + + +### Linters + +Development on the Go wrapper may involve changes in either the Go or Rust code. Each language has distinct linter tests that must be passed before committing changes. + +#### Language-specific Linters + +**Go:** + +- go vet +- gofumpt +- staticcheck +- golines + +**Rust:** + +- clippy +- fmt + +#### Running the linters + +Run from the main `/go` folder + +1. Go + ```bash + make install-dev-tools + make lint + ``` +2. Rust + ```bash + rustup component add clippy rustfmt + cargo clippy --all-features --all-targets -- -D warnings + cargo fmt --manifest-path ./Cargo.toml --all + ``` + +#### Fixing lint formatting errors + +The following command can be used to fix Go formatting errors reported by gofumpt or golines. Note that golines does not always format comments well if they surpass the max line length (127 characters). + +Run from the main `/go` folder + +```bash +make format +``` + +### Recommended extensions for VS Code + +- [Go](https://marketplace.visualstudio.com/items?itemName=golang.Go) +- [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) From cfc07802f52060144e661f32e9f4ae55c4a10a57 Mon Sep 17 00:00:00 2001 From: Aaron <69273634+aaron-congo@users.noreply.github.com> Date: Mon, 4 Mar 2024 09:16:51 -0800 Subject: [PATCH 42/65] Improve Go CI and Makefile targets (#1055) --- .github/workflows/go.yml | 30 ++-- go/Makefile | 43 +++++- go/go.mod | 18 +-- go/go.sum | 314 --------------------------------------- go/tools.go | 10 -- 5 files changed, 55 insertions(+), 360 deletions(-) delete mode 100644 go/tools.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 80246396b4..1beb65f518 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -24,8 +24,8 @@ jobs: fail-fast: false matrix: go: - - '1.18' - - '1.21' + - '1.18.10' + - '1.22.0' redis: - 6.2.14 - 7.2.3 @@ -60,9 +60,9 @@ jobs: with: redis-version: ${{ matrix.redis }} - - name: Install client dependencies + - name: Install tools for Go ${{ matrix.go }} working-directory: ./go - run: make install-tools + run: make install-tools-go${{ matrix.go }} - name: Build client working-directory: ./go @@ -70,11 +70,11 @@ jobs: - name: Run linters working-directory: ./go - run: make lint + run: make lint-ci - - name: Run unit tests + - name: Run tests working-directory: ./go - run: make unit-test-report + run: make test-and-report - name: Upload test reports if: always() @@ -83,7 +83,7 @@ jobs: with: name: test-reports-go-${{ matrix.go }}-redis-${{ matrix.redis }}-${{ matrix.os }} path: | - go/reports/unit-test-report.html + go/reports/test-report.html build-amazonlinux-latest: if: github.repository_owner == 'aws' @@ -93,7 +93,7 @@ jobs: matrix: go: - 1.18.10 - - 1.21.6 + - 1.22.0 runs-on: ubuntu-latest container: amazonlinux:latest timeout-minutes: 15 @@ -135,9 +135,9 @@ jobs: echo "/usr/local/go/bin" >> $GITHUB_PATH echo "$HOME/go/bin" >> $GITHUB_PATH - - name: Install client dependencies + - name: Install tools for Go ${{ matrix.go }} working-directory: ./go - run: make install-tools + run: make install-tools-go${{ matrix.go }} - name: Build client working-directory: ./go @@ -145,11 +145,11 @@ jobs: - name: Run linters working-directory: ./go - run: make lint + run: make lint-ci - - name: Run unit tests + - name: Run tests working-directory: ./go - run: make unit-test-report + run: make test-and-report - name: Upload test reports if: always() @@ -157,7 +157,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: test-reports-go-${{ matrix.go }}-amazon-linux-latest - path: go/reports/unit-test-report.html + path: go/reports/test-report.html lint-rust: timeout-minutes: 15 diff --git a/go/Makefile b/go/Makefile index bd3e101b7b..1ec0145dfd 100644 --- a/go/Makefile +++ b/go/Makefile @@ -1,5 +1,25 @@ -install-tools: - @cat tools.go | grep _ | awk -F'"' '{print $$2}' | xargs -tI % go install % +install-build-tools: + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.32.0 + +install-dev-tools-go1.18.10: + go install github.com/vakenbolt/go-test-report@v0.9.3 + go install mvdan.cc/gofumpt@v0.4.0 + go install github.com/segmentio/golines@v0.11.0 + go install honnef.co/go/tools/cmd/staticcheck@v0.3.3 + +install-dev-tools-go1.22.0: + go install github.com/vakenbolt/go-test-report@v0.9.3 + go install mvdan.cc/gofumpt@v0.6.0 + go install github.com/segmentio/golines@v0.12.2 + go install honnef.co/go/tools/cmd/staticcheck@v0.4.6 + +install-dev-tools: install-dev-tools-go1.22.0 + +install-tools-go1.18.10: install-build-tools install-dev-tools-go1.18.10 + +install-tools-go1.22.0: install-build-tools install-dev-tools-go1.22.0 + +install-tools: install-tools-go1.22.0 build: build-glide-core build-glide-client generate-protobuf go build ./... @@ -23,7 +43,22 @@ generate-protobuf: lint: go vet ./... staticcheck ./... + gofumpt -d . + golines --dry-run --shorten-comments -m 127 . + +lint-ci: + go vet ./... + staticcheck ./... + if [ "$$(gofumpt -l . | wc -l)" -gt 0 ]; then exit 1; fi + if [ "$$(golines -l --shorten-comments -m 127 . | wc -l)" -gt 0 ]; then exit 1; fi + +format: + gofumpt -w . + golines -w --shorten-comments -m 127 . + +test: + go test -v -race `go list ./... | grep -v protobuf` -unit-test-report: +test-and-report: mkdir -p reports - go test -race ./... -json | go-test-report -o reports/unit-test-report.html + go test -v -race `go list ./... | grep -v protobuf` -json | go-test-report -o reports/test-report.html diff --git a/go/go.mod b/go/go.mod index e5c99d1988..50091801de 100644 --- a/go/go.mod +++ b/go/go.mod @@ -2,20 +2,4 @@ module github.com/aws/glide-for-redis/go/glide go 1.18 -require ( - github.com/vakenbolt/go-test-report v0.9.3 - google.golang.org/protobuf v1.32.0 - honnef.co/go/tools v0.3.3 -) - -require ( - github.com/BurntSushi/toml v1.2.1 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5 // indirect -) +require google.golang.org/protobuf v1.32.0 diff --git a/go/go.sum b/go/go.sum index 4ccef17d5a..b010a4f69c 100644 --- a/go/go.sum +++ b/go/go.sum @@ -1,318 +1,4 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/vakenbolt/go-test-report v0.9.3 h1:KPJIZJhr3CKdk82+6KD/LnLF89lvW8aklyRqOjlPJRQ= -github.com/vakenbolt/go-test-report v0.9.3/go.mod h1:sSBCeKCZsuw8Ph983JpYkuEe4fWteYI3YdAtZr9FNds= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE= -golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5 h1:Vk4mysSz+GqQK2eqgWbo4zEO89wkeAjJiFIr9bpqa8k= -golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA= -honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/go/tools.go b/go/tools.go deleted file mode 100644 index b6b28536d0..0000000000 --- a/go/tools.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build tools -// +build tools - -package main - -import ( - _ "github.com/vakenbolt/go-test-report" - _ "google.golang.org/protobuf/cmd/protoc-gen-go" - _ "honnef.co/go/tools/cmd/staticcheck" -) From 5f687a37d5035e70d2b2f3251fd9f48a25f9cd31 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:31:36 -0800 Subject: [PATCH 43/65] Java: Add LRange and LTrim commands. (List Commands) (#1041) --- .../src/main/java/glide/api/BaseClient.java | 18 ++ .../glide/api/commands/ListBaseCommands.java | 55 +++++++ .../glide/api/models/BaseTransaction.java | 155 ++++++++++++------ .../test/java/glide/api/RedisClientTest.java | 53 ++++++ .../glide/api/models/TransactionTests.java | 10 ++ .../test/java/glide/SharedCommandTests.java | 32 +++- .../java/glide/TransactionTestUtilities.java | 14 +- 7 files changed, 280 insertions(+), 57 deletions(-) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 376300465b..b345902957 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -24,6 +24,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.LPop; import static redis_request.RedisRequestOuterClass.RequestType.LPush; +import static redis_request.RedisRequestOuterClass.RequestType.LRange; +import static redis_request.RedisRequestOuterClass.RequestType.LTrim; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; @@ -359,6 +361,22 @@ public CompletableFuture lpopCount(@NonNull String key, long count) { response -> castArray(handleArrayResponse(response), String.class)); } + @Override + public CompletableFuture lrange(@NonNull String key, long start, long end) { + return commandManager.submitNewCommand( + LRange, + new String[] {key, Long.toString(start), Long.toString(end)}, + response -> castArray(handleArrayOrNullResponse(response), String.class)); + } + + @Override + public CompletableFuture ltrim(@NonNull String key, long start, long end) { + return commandManager.submitNewCommand( + LTrim, + new String[] {key, Long.toString(start), Long.toString(end)}, + this::handleStringResponse); + } + @Override public CompletableFuture rpush(@NonNull String key, @NonNull String[] elements) { String[] arguments = ArrayUtils.addFirst(elements, key); 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 989ec73785..948f91b52a 100644 --- a/java/client/src/main/java/glide/api/commands/ListBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java @@ -67,6 +67,61 @@ public interface ListBaseCommands { */ CompletableFuture lpopCount(String key, long count); + /** + * Returns the specified elements of the list stored at key.
+ * The offsets start and end are zero-based indexes, with 0 being the + * first element of the list, 1 being the next element and so on. These offsets can also be + * negative numbers indicating offsets starting at the end of the list, with -1 being the last + * element of the list, -2 being the penultimate, and so on. + * + * @see redis.io for details. + * @param key The key of the list. + * @param start The starting point of the range. + * @param end The end of the range. + * @return Array of elements in the specified range.
+ * If start exceeds the end of the list, or if start is greater than + * end, an empty array will be returned.
+ * If end exceeds the actual end of the list, the range will stop at the actual + * end of the list.
+ * If key does not exist an empty array will be returned.
+ * @example + *
+     * String[] payload = lient.lrange("my_list", 0, 2).get()
+     * assert payload.equals(new String[] {"value1", "value2", "value3"})
+     * String[] payload = client.lrange("my_list", -2, -1).get()
+     * assert payload.equals(new String[] {"value2", "value3"})
+     * String[] payload = client.lrange("non_exiting_key", 0, 2).get()
+     * assert payload.equals(new String[] {})
+     * 
+ */ + CompletableFuture lrange(String key, long start, long end); + + /** + * 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 + * first element of the list, 1 being the next element and so on.
+ * These offsets can also be negative numbers indicating offsets starting at the end of the list, + * with -1 being the last element of the list, -2 being the penultimate, and so on. + * + * @see redis.io for details. + * @param key The key of the list. + * @param start The starting point of the range. + * @param end The end of the range. + * @return Always OK.
+ * If start exceeds the end of the list, or if start is greater than + * end, the result will be an empty list (which causes key to be removed).
+ * If end exceeds the actual end of the list, it will be treated like the last + * element of the list.
+ * If key does not exist, OK will be returned without changes to the database. + * @example + *
+     * String payload = client.ltrim("my_list", 0, 1).get()
+     * assert payload.equals("OK")
+     * 
+ */ + CompletableFuture ltrim(String key, long start, long end); + /** * Inserts all the specified values at the tail of the list stored at key.
* elements are inserted one after the other to the tail of the list, from the 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 611baab04a..e1d2443a83 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -24,6 +24,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.Info; import static redis_request.RedisRequestOuterClass.RequestType.LPop; import static redis_request.RedisRequestOuterClass.RequestType.LPush; +import static redis_request.RedisRequestOuterClass.RequestType.LRange; +import static redis_request.RedisRequestOuterClass.RequestType.LTrim; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; @@ -378,57 +380,6 @@ public T hdel(@NonNull String key, @NonNull String[] fields) { return getThis(); } - /** - * Inserts all the specified values at the tail of the list stored at key.
- * elements are inserted one after the other to the tail of the list, from the - * leftmost element to the rightmost element. If key does not exist, it is created as - * an empty list before performing the push operations. - * - * @see redis.io for details. - * @param key The key of the list. - * @param elements The elements to insert at the tail of the list stored at key. - * @return Command Response - The length of the list after the push operations. - */ - public T rpush(@NonNull String key, @NonNull String[] elements) { - ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(elements, key)); - - protobufTransaction.addCommands(buildCommand(RPush, commandArgs)); - return getThis(); - } - - /** - * Removes and returns the last elements of the list stored at key.
- * The command pops a single element from the end of the list. - * - * @see redis.io for details. - * @param key The key of the list. - * @return Command Response - The value of the last element.
- * If key does not exist, null will be returned.
- */ - public T rpop(@NonNull String key) { - ArgsArray commandArgs = buildArgs(key); - - protobufTransaction.addCommands(buildCommand(RPop, commandArgs)); - return getThis(); - } - - /** - * Removes and returns up to count elements from the list stored at key, - * depending on the list's length. - * - * @see redis.io for details. - * @param count The count of the elements to pop from the list. - * @returns Command Response - An array of popped elements will be returned depending on the - * list's length.
- * If key does not exist, null will be returned.
- */ - public T rpopCount(@NonNull String key, long count) { - ArgsArray commandArgs = buildArgs(key, Long.toString(count)); - - protobufTransaction.addCommands(buildCommand(RPop, commandArgs)); - return getThis(); - } - /** * Returns the values associated with the specified fields in the hash stored at key. * @@ -578,6 +529,108 @@ public T lpopCount(@NonNull String key, long count) { return getThis(); } + /** + * Returns the specified elements of the list stored at key.
+ * The offsets start and end are zero-based indexes, with 0 being the + * first element of the list, 1 being the next element and so on. These offsets can also be + * negative numbers indicating offsets starting at the end of the list, with -1 being the last + * element of the list, -2 being the penultimate, and so on. + * + * @see redis.io for details. + * @param key The key of the list. + * @param start The starting point of the range. + * @param end The end of the range. + * @return Command Response - Array of elements in the specified range.
+ * If start exceeds the end of the list, or if start is greater than + * end, an empty array will be returned.
+ * If end exceeds the actual end of the list, the range will stop at the actual + * end of the list.
+ * If key does not exist an empty array will be returned.
+ */ + public T lrange(@NonNull String key, long start, long end) { + ArgsArray commandArgs = buildArgs(key, Long.toString(start), Long.toString(end)); + + protobufTransaction.addCommands(buildCommand(LRange, 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 + * first element of the list, 1 being the next element and so on.
+ * These offsets can also be negative numbers indicating offsets starting at the end of the list, + * with -1 being the last element of the list, -2 being the penultimate, and so on. + * + * @see redis.io for details. + * @param key The key of the list. + * @param start The starting point of the range. + * @param end The end of the range. + * @return Command Response - Always OK.
+ * If start exceeds the end of the list, or if start is greater than + * end, the result will be an empty list (which causes key to be removed).
+ * If end exceeds the actual end of the list, it will be treated like the last + * element of the list.
+ * If key does not exist, OK will be returned without changes to the database. + */ + public T ltrim(@NonNull String key, long start, long end) { + ArgsArray commandArgs = buildArgs(key, Long.toString(start), Long.toString(end)); + + protobufTransaction.addCommands(buildCommand(LTrim, commandArgs)); + return getThis(); + } + + /** + * Inserts all the specified values at the tail of the list stored at key.
+ * elements are inserted one after the other to the tail of the list, from the + * leftmost element to the rightmost element. If key does not exist, it is created as + * an empty list before performing the push operations. + * + * @see redis.io for details. + * @param key The key of the list. + * @param elements The elements to insert at the tail of the list stored at key. + * @return Command Response - The length of the list after the push operations. + */ + public T rpush(@NonNull String key, @NonNull String[] elements) { + ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(elements, key)); + + protobufTransaction.addCommands(buildCommand(RPush, commandArgs)); + return getThis(); + } + + /** + * Removes and returns the last elements of the list stored at key.
+ * The command pops a single element from the end of the list. + * + * @see redis.io for details. + * @param key The key of the list. + * @return Command Response - The value of the last element.
+ * If key does not exist, null will be returned.
+ */ + public T rpop(@NonNull String key) { + ArgsArray commandArgs = buildArgs(key); + + protobufTransaction.addCommands(buildCommand(RPop, commandArgs)); + return getThis(); + } + + /** + * Removes and returns up to count elements from the list stored at key, + * depending on the list's length. + * + * @see redis.io for details. + * @param count The count of the elements to pop from the list. + * @return Command Response - An array of popped elements will be returned depending on the list's + * length.
+ * If key does not exist, null will be returned.
+ */ + public T rpopCount(@NonNull String key, long count) { + ArgsArray commandArgs = buildArgs(key, Long.toString(count)); + + protobufTransaction.addCommands(buildCommand(RPop, commandArgs)); + return getThis(); + } + /** * Add specified members to the set stored at key. Specified members that are already * a member of this set are ignored. diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 1bcabe8dc1..9a8fcd6aed 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -34,6 +34,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.Info; import static redis_request.RedisRequestOuterClass.RequestType.LPop; import static redis_request.RedisRequestOuterClass.RequestType.LPush; +import static redis_request.RedisRequestOuterClass.RequestType.LRange; +import static redis_request.RedisRequestOuterClass.RequestType.LTrim; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; @@ -1040,6 +1042,57 @@ public void lpopCount_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void lrange_returns_success() { + // setup + String key = "testKey"; + long start = 2L; + long end = 4L; + 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); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(LRange), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.lrange(key, start, end); + String[] payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + + @SneakyThrows + @Test + public void ltrim_returns_success() { + // setup + String key = "testKey"; + long start = 2L; + long end = 2L; + String[] args = new String[] {key, Long.toString(end), Long.toString(start)}; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(OK); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(LTrim), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.ltrim(key, start, end); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(OK, payload); + } + @SneakyThrows @Test public void rpush_returns_success() { diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index e9aa5ba4d1..0a86c6dbee 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -24,6 +24,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.Info; import static redis_request.RedisRequestOuterClass.RequestType.LPop; import static redis_request.RedisRequestOuterClass.RequestType.LPush; +import static redis_request.RedisRequestOuterClass.RequestType.LRange; +import static redis_request.RedisRequestOuterClass.RequestType.LTrim; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; import static redis_request.RedisRequestOuterClass.RequestType.PExpire; @@ -165,6 +167,14 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.lpopCount("key", 2); results.add(Pair.of(LPop, ArgsArray.newBuilder().addArgs("key").addArgs("2").build())); + transaction.lrange("key", 1, 2); + results.add( + Pair.of(LRange, ArgsArray.newBuilder().addArgs("key").addArgs("1").addArgs("2").build())); + + transaction.ltrim("key", 1, 2); + results.add( + Pair.of(LTrim, ArgsArray.newBuilder().addArgs("key").addArgs("1").addArgs("2").build())); + transaction.rpush("key", new String[] {"element"}); results.add(Pair.of(RPush, ArgsArray.newBuilder().addArgs("key").addArgs("element").build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 032e769c7c..1776dc5332 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -545,20 +545,22 @@ public void hincrBy_hincrByFloat_type_error(BaseClient client) { @SneakyThrows @ParameterizedTest @MethodSource("getClients") - public void lpush_lpop_existing_non_existing_key(BaseClient client) { + public void lpush_lpop_lrange_existing_non_existing_key(BaseClient client) { String key = UUID.randomUUID().toString(); String[] valueArray = new String[] {"value4", "value3", "value2", "value1"}; assertEquals(4, client.lpush(key, valueArray).get()); assertEquals("value1", client.lpop(key).get()); + assertArrayEquals(new String[] {"value2", "value3", "value4"}, client.lrange(key, 0, -1).get()); assertArrayEquals(new String[] {"value2", "value3"}, client.lpopCount(key, 2).get()); + assertArrayEquals(new String[] {}, client.lrange("non_existing_key", 0, -1).get()); assertNull(client.lpop("non_existing_key").get()); } @SneakyThrows @ParameterizedTest @MethodSource("getClients") - public void lpush_lpop_type_error(BaseClient client) { + public void lpush_lpop_lrange_type_error(BaseClient client) { String key = UUID.randomUUID().toString(); assertEquals(OK, client.set(key, "foo").get()); @@ -573,6 +575,32 @@ public void lpush_lpop_type_error(BaseClient client) { Exception lpopCountException = assertThrows(ExecutionException.class, () -> client.lpopCount(key, 2).get()); assertTrue(lpopCountException.getCause() instanceof RequestException); + + Exception lrangeException = + assertThrows(ExecutionException.class, () -> client.lrange(key, 0, -1).get()); + assertTrue(lrangeException.getCause() instanceof RequestException); + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void ltrim_existing_non_existing_key_and_type_error(BaseClient client) { + String key = UUID.randomUUID().toString(); + String[] valueArray = new String[] {"value4", "value3", "value2", "value1"}; + + assertEquals(4, client.lpush(key, valueArray).get()); + assertEquals(OK, client.ltrim(key, 0, 1).get()); + assertArrayEquals(new String[] {"value1", "value2"}, client.lrange(key, 0, -1).get()); + + // `start` is greater than `end` so the key will be removed. + assertEquals(OK, client.ltrim(key, 4, 2).get()); + assertArrayEquals(new String[] {}, client.lrange(key, 0, -1).get()); + + assertEquals(OK, client.set(key, "foo").get()); + + Exception ltrimException = + assertThrows(ExecutionException.class, () -> client.ltrim(key, 0, 1).get()); + assertTrue(ltrimException.getCause() instanceof RequestException); } @SneakyThrows diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index bcdbb8af78..61db76a57c 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -1,6 +1,8 @@ /** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ package glide; +import static glide.api.BaseClient.OK; + import glide.api.models.BaseTransaction; import glide.api.models.commands.SetOptions; import java.util.Map; @@ -61,7 +63,9 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.hincrBy(key4, field3, 5); baseTransaction.hincrByFloat(key4, field3, 5.5); - baseTransaction.lpush(key5, new String[] {value1, value2, value3}); + baseTransaction.lpush(key5, new String[] {value1, value2, value3, value3}); + baseTransaction.ltrim(key5, 1, -1); + baseTransaction.lrange(key5, 0, -2); baseTransaction.lpop(key5); baseTransaction.lpopCount(key5, 2); @@ -79,7 +83,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact public static Object[] transactionTestResult() { return new Object[] { - "OK", + OK, value1, null, new String[] {value1, value2}, @@ -88,7 +92,7 @@ public static Object[] transactionTestResult() { null, 1L, null, - "OK", + OK, new String[] {value2, value1}, 1L, 3L, @@ -104,7 +108,9 @@ public static Object[] transactionTestResult() { 1L, 5L, 10.5, - 3L, + 4L, + OK, + new String[] {value3, value2}, value3, new String[] {value2, value1}, 3L, From 10186a303254e146f00ec3c0ad238582dfc7cdd3 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:42:47 -0800 Subject: [PATCH 44/65] Java: Add LLen command. (List Commands) (#1042) * Java: Add LLen command. (List Commands) * Added examples. * Star import fix. * Rebase Spotless. --- .../src/main/java/glide/api/BaseClient.java | 6 +++++ .../glide/api/commands/ListBaseCommands.java | 15 ++++++++++++ .../glide/api/models/BaseTransaction.java | 16 +++++++++++++ .../test/java/glide/api/RedisClientTest.java | 24 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 4 ++++ .../test/java/glide/SharedCommandTests.java | 19 +++++++++++++++ .../java/glide/TransactionTestUtilities.java | 2 ++ 7 files changed, 86 insertions(+) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index b345902957..1b78309d34 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -22,6 +22,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Incr; import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; +import static redis_request.RedisRequestOuterClass.RequestType.LLen; import static redis_request.RedisRequestOuterClass.RequestType.LPop; import static redis_request.RedisRequestOuterClass.RequestType.LPush; import static redis_request.RedisRequestOuterClass.RequestType.LRange; @@ -377,6 +378,11 @@ public CompletableFuture ltrim(@NonNull String key, long start, long end this::handleStringResponse); } + @Override + public CompletableFuture llen(@NonNull String key) { + return commandManager.submitNewCommand(LLen, new String[] {key}, this::handleLongResponse); + } + @Override public CompletableFuture rpush(@NonNull String key, @NonNull String[] elements) { String[] arguments = ArrayUtils.addFirst(elements, key); 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 948f91b52a..053f0d6697 100644 --- a/java/client/src/main/java/glide/api/commands/ListBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java @@ -122,6 +122,21 @@ public interface ListBaseCommands { */ CompletableFuture ltrim(String key, long start, long end); + /** + * Returns the length of the list stored at key. + * + * @see redis.io for details. + * @param key The key of the list. + * @return The length of the list at key.
+ * If key does not exist, it is interpreted as an empty list and 0 is returned. + * @example + *
+     * Long lenList = client.llen("my_list").get()
+     * assert lenList == 3L //Indicates that there are 3 elements in the list.
+     * 
+ */ + CompletableFuture llen(String key); + /** * Inserts all the specified values at the tail of the list stored at key.
* elements are inserted one after the other to the tail of the list, from the 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 e1d2443a83..d5ca352558 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -22,6 +22,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.Info; +import static redis_request.RedisRequestOuterClass.RequestType.LLen; import static redis_request.RedisRequestOuterClass.RequestType.LPop; import static redis_request.RedisRequestOuterClass.RequestType.LPush; import static redis_request.RedisRequestOuterClass.RequestType.LRange; @@ -580,6 +581,21 @@ public T ltrim(@NonNull String key, long start, long end) { return getThis(); } + /** + * Returns the length of the list stored at key. + * + * @see redis.io for details. + * @param key The key of the list. + * @return Command Response - The length of the list at key.
+ * If key does not exist, it is interpreted as an empty list and 0 is returned. + */ + public T llen(@NonNull String key) { + ArgsArray commandArgs = buildArgs(key); + + protobufTransaction.addCommands(buildCommand(LLen, commandArgs)); + return getThis(); + } + /** * Inserts all the specified values at the tail of the list stored at key.
* elements are inserted one after the other to the tail of the list, from the diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 9a8fcd6aed..b8f89ac6d9 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -32,6 +32,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.Info; +import static redis_request.RedisRequestOuterClass.RequestType.LLen; import static redis_request.RedisRequestOuterClass.RequestType.LPop; import static redis_request.RedisRequestOuterClass.RequestType.LPush; import static redis_request.RedisRequestOuterClass.RequestType.LRange; @@ -1093,6 +1094,29 @@ public void ltrim_returns_success() { assertEquals(OK, payload); } + @SneakyThrows + @Test + public void llen_returns_success() { + // setup + String key = "testKey"; + String[] args = new String[] {key}; + long value = 2L; + + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(LLen), eq(args), any())).thenReturn(testResponse); + + // exercise + CompletableFuture response = service.llen(key); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void rpush_returns_success() { diff --git a/java/client/src/test/java/glide/api/models/TransactionTests.java b/java/client/src/test/java/glide/api/models/TransactionTests.java index 0a86c6dbee..89a507fbb7 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -22,6 +22,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.IncrBy; import static redis_request.RedisRequestOuterClass.RequestType.IncrByFloat; import static redis_request.RedisRequestOuterClass.RequestType.Info; +import static redis_request.RedisRequestOuterClass.RequestType.LLen; import static redis_request.RedisRequestOuterClass.RequestType.LPop; import static redis_request.RedisRequestOuterClass.RequestType.LPush; import static redis_request.RedisRequestOuterClass.RequestType.LRange; @@ -175,6 +176,9 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) results.add( Pair.of(LTrim, ArgsArray.newBuilder().addArgs("key").addArgs("1").addArgs("2").build())); + transaction.llen("key"); + results.add(Pair.of(LLen, ArgsArray.newBuilder().addArgs("key").build())); + transaction.rpush("key", new String[] {"element"}); results.add(Pair.of(RPush, ArgsArray.newBuilder().addArgs("key").addArgs("element").build())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 1776dc5332..26acf92eda 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -603,6 +603,25 @@ public void ltrim_existing_non_existing_key_and_type_error(BaseClient client) { assertTrue(ltrimException.getCause() instanceof RequestException); } + @SneakyThrows + @ParameterizedTest + @MethodSource("getClients") + public void llen_existing_non_existing_key_and_type_error(BaseClient client) { + String key1 = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String[] valueArray = new String[] {"value4", "value3", "value2", "value1"}; + + assertEquals(4, client.lpush(key1, valueArray).get()); + assertEquals(4, client.llen(key1).get()); + assertEquals(0, client.llen("non_existing_key").get()); + + assertEquals(OK, client.set(key2, "foo").get()); + + Exception lrangeException = + assertThrows(ExecutionException.class, () -> client.llen(key2).get()); + assertTrue(lrangeException.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 61db76a57c..3524d7e26d 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -64,6 +64,7 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.hincrByFloat(key4, field3, 5.5); baseTransaction.lpush(key5, new String[] {value1, value2, value3, value3}); + baseTransaction.llen(key5); baseTransaction.ltrim(key5, 1, -1); baseTransaction.lrange(key5, 0, -2); baseTransaction.lpop(key5); @@ -109,6 +110,7 @@ public static Object[] transactionTestResult() { 5L, 10.5, 4L, + 4L, OK, new String[] {value3, value2}, value3, From 0a60159ae8f8e8edc2d5de0c7df94754399fada0 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:43:05 -0800 Subject: [PATCH 45/65] Java: Add non-null tags to missing commands (#123) (#1067) --- .../src/main/java/glide/api/BaseClient.java | 8 +++---- .../src/main/java/glide/api/RedisClient.java | 2 +- .../java/glide/api/RedisClusterClient.java | 7 +++--- .../glide/api/models/BaseTransaction.java | 24 +++++++++---------- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 1b78309d34..784cf6d6e9 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -404,24 +404,24 @@ public CompletableFuture rpopCount(@NonNull String key, long count) { } @Override - public CompletableFuture sadd(String key, String[] members) { + public CompletableFuture sadd(@NonNull String key, @NonNull String[] members) { String[] arguments = ArrayUtils.addFirst(members, key); return commandManager.submitNewCommand(SAdd, arguments, this::handleLongResponse); } @Override - public CompletableFuture srem(String key, String[] members) { + public CompletableFuture srem(@NonNull String key, @NonNull String[] members) { String[] arguments = ArrayUtils.addFirst(members, key); return commandManager.submitNewCommand(SRem, arguments, this::handleLongResponse); } @Override - public CompletableFuture> smembers(String key) { + public CompletableFuture> smembers(@NonNull String key) { return commandManager.submitNewCommand(SMembers, new String[] {key}, this::handleSetResponse); } @Override - public CompletableFuture scard(String key) { + public CompletableFuture scard(@NonNull String key) { return commandManager.submitNewCommand(SCard, new String[] {key}, this::handleLongResponse); } diff --git a/java/client/src/main/java/glide/api/RedisClient.java b/java/client/src/main/java/glide/api/RedisClient.java index 5b4d28ab4a..58844630a9 100644 --- a/java/client/src/main/java/glide/api/RedisClient.java +++ b/java/client/src/main/java/glide/api/RedisClient.java @@ -45,7 +45,7 @@ public CompletableFuture customCommand(@NonNull String[] args) { } @Override - public CompletableFuture exec(Transaction transaction) { + public CompletableFuture exec(@NonNull Transaction transaction) { return commandManager.submitNewCommand(transaction, this::handleArrayOrNullResponse); } diff --git a/java/client/src/main/java/glide/api/RedisClusterClient.java b/java/client/src/main/java/glide/api/RedisClusterClient.java index 719fd07451..6b693ed1db 100644 --- a/java/client/src/main/java/glide/api/RedisClusterClient.java +++ b/java/client/src/main/java/glide/api/RedisClusterClient.java @@ -53,7 +53,8 @@ public CompletableFuture> customCommand(@NonNull String[] a } @Override - public CompletableFuture> customCommand(String[] args, Route route) { + public CompletableFuture> customCommand( + @NonNull String[] args, @NonNull Route route) { return commandManager.submitNewCommand( CustomCommand, args, route, response -> handleCustomCommandResponse(route, response)); } @@ -69,14 +70,14 @@ protected ClusterValue handleCustomCommandResponse(Route route, Response } @Override - public CompletableFuture exec(ClusterTransaction transaction) { + public CompletableFuture exec(@NonNull ClusterTransaction transaction) { return commandManager.submitNewCommand( transaction, Optional.empty(), this::handleArrayOrNullResponse); } @Override public CompletableFuture[]> exec( - ClusterTransaction transaction, Route route) { + @NonNull ClusterTransaction transaction, Route route) { return commandManager .submitNewCommand(transaction, Optional.ofNullable(route), this::handleArrayOrNullResponse) .thenApply( 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 d5ca352558..42a8ab222b 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -117,7 +117,7 @@ public T ping() { * @param msg The ping argument that will be returned. * @return A response from Redis with a String. */ - public T ping(String msg) { + public T ping(@NonNull String msg) { ArgsArray commandArgs = buildArgs(msg); protobufTransaction.addCommands(buildCommand(Ping, commandArgs)); @@ -144,7 +144,7 @@ public T info() { * @return Response from Redis with a String containing the requested {@link * Section}s. */ - public T info(InfoOptions options) { + public T info(@NonNull InfoOptions options) { ArgsArray commandArgs = buildArgs(options.toArgs()); protobufTransaction.addCommands(buildCommand(Info, commandArgs)); @@ -159,7 +159,7 @@ public T info(InfoOptions options) { * @param keys The keys we wanted to remove. * @return Command Response - The number of keys that were removed. */ - public T del(String[] keys) { + public T del(@NonNull String[] keys) { ArgsArray commandArgs = buildArgs(keys); protobufTransaction.addCommands(buildCommand(Del, commandArgs)); @@ -174,7 +174,7 @@ public T del(String[] keys) { * @return Response from Redis. key exists, returns the value of * key as a String. Otherwise, return null. */ - public T get(String key) { + public T get(@NonNull String key) { ArgsArray commandArgs = buildArgs(key); protobufTransaction.addCommands(buildCommand(GetString, commandArgs)); @@ -189,7 +189,7 @@ public T get(String key) { * @param value The value to store with the given key. * @return Response from Redis. */ - public T set(String key, String value) { + public T set(@NonNull String key, @NonNull String value) { ArgsArray commandArgs = buildArgs(key, value); protobufTransaction.addCommands(buildCommand(SetString, commandArgs)); @@ -209,7 +209,7 @@ public T set(String key, String value) { * {@link ConditionalSet#ONLY_IF_DOES_NOT_EXIST} conditions, return null. * Otherwise, return OK. */ - public T set(String key, String value, SetOptions options) { + public T set(@NonNull String key, @NonNull String value, @NonNull SetOptions options) { ArgsArray commandArgs = buildArgs(ArrayUtils.addAll(new String[] {key, value}, options.toArgs())); @@ -659,7 +659,7 @@ public T rpopCount(@NonNull String key, long count) { * @remarks If key does not exist, a new set is created before adding members * . */ - public T sadd(String key, String[] members) { + public T sadd(@NonNull String key, @NonNull String[] members) { ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(members, key)); protobufTransaction.addCommands(buildCommand(SAdd, commandArgs)); @@ -678,7 +678,7 @@ public T sadd(String key, String[] members) { * @remarks If key does not exist, it is treated as an empty set and this command * returns 0. */ - public T srem(String key, String[] members) { + public T srem(@NonNull String key, @NonNull String[] members) { ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(members, key)); protobufTransaction.addCommands(buildCommand(SRem, commandArgs)); @@ -693,7 +693,7 @@ public T srem(String key, String[] members) { * @return Command Response - A Set of all members of the set. * @remarks If key does not exist an empty set will be returned. */ - public T smembers(String key) { + public T smembers(@NonNull String key) { ArgsArray commandArgs = buildArgs(key); protobufTransaction.addCommands(buildCommand(SMembers, commandArgs)); @@ -708,7 +708,7 @@ public T smembers(String key) { * @return Command Response - The cardinality (number of elements) of the set, or 0 if the key * does not exist. */ - public T scard(String key) { + public T scard(@NonNull String key) { ArgsArray commandArgs = buildArgs(key); protobufTransaction.addCommands(buildCommand(SCard, commandArgs)); @@ -723,7 +723,7 @@ public T scard(String key) { * @return Command Response - The number of keys that exist. If the same existing key is mentioned * in keys multiple times, it will be counted multiple times. */ - public T exists(String[] keys) { + public T exists(@NonNull String[] keys) { ArgsArray commandArgs = buildArgs(keys); protobufTransaction.addCommands(buildCommand(Exists, commandArgs)); @@ -740,7 +740,7 @@ public T exists(String[] keys) { * @param keys The list of keys to unlink. * @return Command Response - The number of keys that were unlinked. */ - public T unlink(String[] keys) { + public T unlink(@NonNull String[] keys) { ArgsArray commandArgs = buildArgs(keys); protobufTransaction.addCommands(buildCommand(Unlink, commandArgs)); From 30d8654965179fa0b1a5ca3c5396cc9191652e88 Mon Sep 17 00:00:00 2001 From: ort-bot Date: Tue, 5 Mar 2024 00:18:27 +0000 Subject: [PATCH 46/65] Updated attribution files --- glide-core/THIRD_PARTY_LICENSES_RUST | 12 ++++++------ node/THIRD_PARTY_LICENSES_NODE | 14 +++++++------- python/THIRD_PARTY_LICENSES_PYTHON | 12 ++++++------ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/glide-core/THIRD_PARTY_LICENSES_RUST b/glide-core/THIRD_PARTY_LICENSES_RUST index 11a970a0a6..6f16e9db54 100644 --- a/glide-core/THIRD_PARTY_LICENSES_RUST +++ b/glide-core/THIRD_PARTY_LICENSES_RUST @@ -14361,7 +14361,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: js-sys:0.3.68 +Package: js-sys:0.3.69 The following copyrights and licenses were found in the source code of this package: @@ -31750,7 +31750,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen:0.2.91 +Package: wasm-bindgen:0.2.92 The following copyrights and licenses were found in the source code of this package: @@ -31979,7 +31979,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-backend:0.2.91 +Package: wasm-bindgen-backend:0.2.92 The following copyrights and licenses were found in the source code of this package: @@ -32208,7 +32208,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro:0.2.91 +Package: wasm-bindgen-macro:0.2.92 The following copyrights and licenses were found in the source code of this package: @@ -32437,7 +32437,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro-support:0.2.91 +Package: wasm-bindgen-macro-support:0.2.92 The following copyrights and licenses were found in the source code of this package: @@ -32666,7 +32666,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-shared:0.2.91 +Package: wasm-bindgen-shared:0.2.92 The following copyrights and licenses were found in the source code of this package: diff --git a/node/THIRD_PARTY_LICENSES_NODE b/node/THIRD_PARTY_LICENSES_NODE index c87ebc1a45..cd76823a39 100644 --- a/node/THIRD_PARTY_LICENSES_NODE +++ b/node/THIRD_PARTY_LICENSES_NODE @@ -14927,7 +14927,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: js-sys:0.3.68 +Package: js-sys:0.3.69 The following copyrights and licenses were found in the source code of this package: @@ -23612,7 +23612,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: regex-automata:0.4.5 +Package: regex-automata:0.4.6 The following copyrights and licenses were found in the source code of this package: @@ -34062,7 +34062,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen:0.2.91 +Package: wasm-bindgen:0.2.92 The following copyrights and licenses were found in the source code of this package: @@ -34291,7 +34291,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-backend:0.2.91 +Package: wasm-bindgen-backend:0.2.92 The following copyrights and licenses were found in the source code of this package: @@ -34520,7 +34520,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro:0.2.91 +Package: wasm-bindgen-macro:0.2.92 The following copyrights and licenses were found in the source code of this package: @@ -34749,7 +34749,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro-support:0.2.91 +Package: wasm-bindgen-macro-support:0.2.92 The following copyrights and licenses were found in the source code of this package: @@ -34978,7 +34978,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-shared:0.2.91 +Package: wasm-bindgen-shared:0.2.92 The following copyrights and licenses were found in the source code of this package: diff --git a/python/THIRD_PARTY_LICENSES_PYTHON b/python/THIRD_PARTY_LICENSES_PYTHON index ecd51f8676..bd4f340c34 100644 --- a/python/THIRD_PARTY_LICENSES_PYTHON +++ b/python/THIRD_PARTY_LICENSES_PYTHON @@ -15027,7 +15027,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: js-sys:0.3.68 +Package: js-sys:0.3.69 The following copyrights and licenses were found in the source code of this package: @@ -34268,7 +34268,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen:0.2.91 +Package: wasm-bindgen:0.2.92 The following copyrights and licenses were found in the source code of this package: @@ -34497,7 +34497,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-backend:0.2.91 +Package: wasm-bindgen-backend:0.2.92 The following copyrights and licenses were found in the source code of this package: @@ -34726,7 +34726,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro:0.2.91 +Package: wasm-bindgen-macro:0.2.92 The following copyrights and licenses were found in the source code of this package: @@ -34955,7 +34955,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-macro-support:0.2.91 +Package: wasm-bindgen-macro-support:0.2.92 The following copyrights and licenses were found in the source code of this package: @@ -35184,7 +35184,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: wasm-bindgen-shared:0.2.91 +Package: wasm-bindgen-shared:0.2.92 The following copyrights and licenses were found in the source code of this package: From fb93d1aa985ab52c4861bd1a0ced4c3bcf2c5e00 Mon Sep 17 00:00:00 2001 From: Shachar Langbeheim Date: Tue, 5 Mar 2024 14:05:59 +0200 Subject: [PATCH 47/65] Reconnect standalone connection on unrecoverable errors. (#1076) These errors represent any situation in which the connection has reached a state in which it is no longer usable. --- glide-core/src/client/standalone_client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/glide-core/src/client/standalone_client.rs b/glide-core/src/client/standalone_client.rs index 8889102315..f2a337e07d 100644 --- a/glide-core/src/client/standalone_client.rs +++ b/glide-core/src/client/standalone_client.rs @@ -225,7 +225,7 @@ impl StandaloneClient { let mut connection = reconnecting_connection.get_connection().await?; let result = connection.send_packed_command(cmd).await; match result { - Err(err) if err.is_connection_dropped() => { + Err(err) if err.is_unrecoverable_error() => { log_warn("send request", format!("received disconnect error `{err}`")); reconnecting_connection.reconnect(); Err(err) @@ -321,7 +321,7 @@ impl StandaloneClient { .send_packed_commands(pipeline, offset, count) .await; match result { - Err(err) if err.is_connection_dropped() => { + Err(err) if err.is_unrecoverable_error() => { log_warn( "pipeline request", format!("received disconnect error `{err}`"), From 0ee5ab0340ce74bcb8a6d551dabd0f90ec62908c Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Tue, 5 Mar 2024 11:33:52 -0800 Subject: [PATCH 48/65] Add `CLIENT ID` and `CLIENT GETNAME` commands. (#1014) * Add `CLIENT ID` and `CLIENT GETNAME` commands. (#98) Signed-off-by: Yury-Fridlyand Co-authored-by: Andrew Carbonetto Co-authored-by: SanHalacogluImproving --- .../src/main/java/glide/api/BaseClient.java | 3 +- .../src/main/java/glide/api/RedisClient.java | 13 +++ .../java/glide/api/RedisClusterClient.java | 37 ++++++++ .../ConnectionManagementClusterCommands.java | 75 +++++++++++++++- .../ConnectionManagementCommands.java | 31 ++++++- .../glide/api/models/BaseTransaction.java | 25 ++++++ .../test/java/glide/api/RedisClientTest.java | 40 +++++++++ .../glide/api/RedisClusterClientTest.java | 87 ++++++++++++++++++- .../glide/api/models/TransactionTests.java | 8 ++ .../test/java/glide/SharedCommandTests.java | 2 +- .../src/test/java/glide/TestUtilities.java | 14 +++ .../test/java/glide/cluster/CommandTests.java | 61 +++++++++++++ .../java/glide/standalone/CommandTests.java | 18 ++++ 13 files changed, 407 insertions(+), 7 deletions(-) create mode 100644 java/integTest/src/test/java/glide/TestUtilities.java diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 784cf6d6e9..72d0785360 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -79,6 +79,7 @@ public abstract class BaseClient HashCommands, ListBaseCommands, SetCommands { + /** Redis simple string response with "OK" */ public static final String OK = ConstantResponse.OK.toString(); @@ -214,7 +215,7 @@ protected Object[] handleArrayOrNullResponse(Response response) throws RedisExce /** * @param response A Protobuf response * @return A map of String to V - * @param Value type could be even map too + * @param Value type */ @SuppressWarnings("unchecked") // raw Map cast to Map protected Map handleMapResponse(Response response) throws RedisException { diff --git a/java/client/src/main/java/glide/api/RedisClient.java b/java/client/src/main/java/glide/api/RedisClient.java index 58844630a9..11911f9bf5 100644 --- a/java/client/src/main/java/glide/api/RedisClient.java +++ b/java/client/src/main/java/glide/api/RedisClient.java @@ -1,6 +1,8 @@ /** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api; +import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; +import static redis_request.RedisRequestOuterClass.RequestType.ClientId; import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand; import static redis_request.RedisRequestOuterClass.RequestType.Info; import static redis_request.RedisRequestOuterClass.RequestType.Ping; @@ -75,4 +77,15 @@ public CompletableFuture select(long index) { return commandManager.submitNewCommand( Select, new String[] {Long.toString(index)}, this::handleStringResponse); } + + @Override + public CompletableFuture clientId() { + return commandManager.submitNewCommand(ClientId, new String[0], this::handleLongResponse); + } + + @Override + public CompletableFuture clientGetName() { + return commandManager.submitNewCommand( + ClientGetName, new String[0], this::handleStringOrNullResponse); + } } diff --git a/java/client/src/main/java/glide/api/RedisClusterClient.java b/java/client/src/main/java/glide/api/RedisClusterClient.java index 6b693ed1db..fb8c060687 100644 --- a/java/client/src/main/java/glide/api/RedisClusterClient.java +++ b/java/client/src/main/java/glide/api/RedisClusterClient.java @@ -1,6 +1,8 @@ /** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api; +import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; +import static redis_request.RedisRequestOuterClass.RequestType.ClientId; import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand; import static redis_request.RedisRequestOuterClass.RequestType.Info; import static redis_request.RedisRequestOuterClass.RequestType.Ping; @@ -144,4 +146,39 @@ public CompletableFuture> info( ? ClusterValue.of(handleStringResponse(response)) : ClusterValue.of(handleMapResponse(response))); } + + @Override + public CompletableFuture clientId() { + return commandManager.submitNewCommand(ClientId, new String[0], this::handleLongResponse); + } + + @Override + public CompletableFuture> clientId(@NonNull Route route) { + return commandManager.submitNewCommand( + ClientId, + new String[0], + route, + response -> + route.isSingleNodeRoute() + ? ClusterValue.of(handleLongResponse(response)) + : ClusterValue.of(handleMapResponse(response))); + } + + @Override + public CompletableFuture clientGetName() { + return commandManager.submitNewCommand( + ClientGetName, new String[0], this::handleStringOrNullResponse); + } + + @Override + public CompletableFuture> clientGetName(@NonNull Route route) { + return commandManager.submitNewCommand( + ClientGetName, + new String[0], + route, + response -> + route.isSingleNodeRoute() + ? ClusterValue.of(handleStringOrNullResponse(response)) + : ClusterValue.of(handleMapResponse(response))); + } } diff --git a/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java b/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java index 5620052dfe..49774f0c09 100644 --- a/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java +++ b/java/client/src/main/java/glide/api/commands/ConnectionManagementClusterCommands.java @@ -1,13 +1,14 @@ /** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api.commands; +import glide.api.models.ClusterValue; import glide.api.models.configuration.RequestRoutingConfiguration.Route; import java.util.concurrent.CompletableFuture; /** - * Connection Management Commands interface. + * Connection Management Commands interface for cluster client. * - * @see: Connection Management Commands + * @see Connection Management Commands */ public interface ConnectionManagementClusterCommands { @@ -48,4 +49,74 @@ public interface ConnectionManagementClusterCommands { * @return String with a copy of the argument message. */ CompletableFuture ping(String message, Route route); + + /** + * Gets the current connection id.
+ * The command will be routed to a random node. + * + * @see redis.io for details. + * @return The id of the client. + * @example + *
{@code
+     * long id = client.clientId().get();
+     * assert id > 0
+     * }
+ */ + CompletableFuture clientId(); + + /** + * Gets the current connection id. + * + * @see redis.io for details. + * @param route Routing configuration for the command. Client will route the command to the nodes + * defined. + * @return A {@link ClusterValue} which holds a single value if single node route is used or a + * dictionary where each address is the key and its corresponding node response is the value. + * The value is the id of the client on that node. + * @example + *
{@code
+     * long id = client.clientId(new SlotIdRoute(...)).get().getSingleValue();
+     * assert id > 0;
+     *
+     * Map idPerNode = client.clientId(ALL_NODES).get().getMultiValue();
+     * assert idPerNode.get("") > 0;
+     * 
+ */ + CompletableFuture> clientId(Route route); + + /** + * Gets the name of the current connection.
+ * The command will be routed a random node. + * + * @see redis.io for details. + * @return The name of the client connection as a string if a name is set, or null if + * no name is assigned. + * @example + *
{@code
+     * String clientName = client.clientGetName().get();
+     * assert clientName != null;
+     * }
+ */ + CompletableFuture clientGetName(); + + /** + * Gets the name of the current connection. + * + * @see redis.io for details. + * @param route Routing configuration for the command. Client will route the command to the nodes + * defined. + * @return A {@link ClusterValue} which holds a single value if single node route is used or a + * dictionary where each address is the key and its corresponding node response is the value. + * The value is the name of the client connection as a string if a name is set, or null if no + * name is assigned. + * @example + *
{@code
+     * String clientName = client.clientGetName(new SlotIdRoute(...)).get().getSingleValue();
+     * assert clientName != null;
+     *
+     * Map clientNamePerNode = client.clientGetName(ALL_NODES).get().getMultiValue();
+     * assert clientNamePerNode.get("") != null
+     * }
+ */ + CompletableFuture> clientGetName(Route route); } diff --git a/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java b/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java index a38cff926e..f1840921c6 100644 --- a/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java +++ b/java/client/src/main/java/glide/api/commands/ConnectionManagementCommands.java @@ -4,9 +4,9 @@ import java.util.concurrent.CompletableFuture; /** - * Connection Management Commands interface. + * Connection Management Commands interface for standalone client. * - * @see: Connection Management Commands + * @see Connection Management Commands */ public interface ConnectionManagementCommands { @@ -26,4 +26,31 @@ public interface ConnectionManagementCommands { * @return String with a copy of the argument message. */ CompletableFuture ping(String message); + + /** + * Gets the current connection id. + * + * @see redis.io for details. + * @return The id of the client. + * @example + *
{@code
+     * Long id = client.clientId().get();
+     * assert id > 0;
+     * }
+ */ + CompletableFuture clientId(); + + /** + * Gets the name of the current connection. + * + * @see redis.io for details. + * @return The name of the client connection as a string if a name is set, or null if + * no name is assigned. + * @example + *
{@code
+     * String clientName = client.clientGetName().get();
+     * assert clientName != null;
+     * }
+ */ + CompletableFuture clientGetName(); } 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 42a8ab222b..6eed1f4ce1 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -2,6 +2,8 @@ package glide.api.models; import static glide.utils.ArrayTransformUtils.convertMapToArgArray; +import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; +import static redis_request.RedisRequestOuterClass.RequestType.ClientId; import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand; import static redis_request.RedisRequestOuterClass.RequestType.Decr; import static redis_request.RedisRequestOuterClass.RequestType.DecrBy; @@ -966,6 +968,29 @@ public T ttl(@NonNull String key) { return getThis(); } + /** + * Get the current connection id. + * + * @see redis.io for details. + * @return Command response - The id of the client. + */ + public T clientId() { + protobufTransaction.addCommands(buildCommand(ClientId)); + return getThis(); + } + + /** + * Get the name of the current connection. + * + * @see redis.io for details. + * @return Command response - The name of the client connection as a string if a name is set, or + * null if no name is assigned. + */ + public T clientGetName() { + protobufTransaction.addCommands(buildCommand(ClientGetName)); + return getThis(); + } + /** Build protobuf {@link Command} object for given command and arguments. */ protected Command buildCommand(RequestType requestType) { return buildCommand(requestType, buildArgs()); diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index b8f89ac6d9..c56d90e30f 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -12,6 +12,8 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; +import static redis_request.RedisRequestOuterClass.RequestType.ClientId; import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand; import static redis_request.RedisRequestOuterClass.RequestType.Decr; import static redis_request.RedisRequestOuterClass.RequestType.DecrBy; @@ -1286,4 +1288,42 @@ public void scard_returns_success() { assertEquals(testResponse, response); assertEquals(value, payload); } + + @SneakyThrows + @Test + public void clientId_returns_success() { + // setup + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(42L); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ClientId), eq(new String[0]), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.clientId(); + + // verify + assertEquals(testResponse, response); + assertEquals(42L, response.get()); + } + + @SneakyThrows + @Test + public void clientGetName_returns_success() { + // setup + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn("TEST"); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ClientGetName), eq(new String[0]), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.clientGetName(); + + // verify + assertEquals(testResponse, response); + assertEquals("TEST", response.get()); + } } diff --git a/java/client/src/test/java/glide/api/RedisClusterClientTest.java b/java/client/src/test/java/glide/api/RedisClusterClientTest.java index 341cd40025..616f3440c7 100644 --- a/java/client/src/test/java/glide/api/RedisClusterClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClusterClientTest.java @@ -11,6 +11,8 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; +import static redis_request.RedisRequestOuterClass.RequestType.ClientId; import static redis_request.RedisRequestOuterClass.RequestType.Info; import static redis_request.RedisRequestOuterClass.RequestType.Ping; @@ -118,7 +120,6 @@ public TestClient(CommandManager commandManager, Object objectToReturn) { object = objectToReturn; } - @SuppressWarnings("unchecked") @Override protected T handleRedisResponse(Class classType, boolean isNullable, Response response) { return (T) object; @@ -356,4 +357,88 @@ public void info_with_options_and_multi_node_route_returns_multi_value() { 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); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ClientId), eq(new String[0]), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.clientId(); + + // verify + assertEquals(testResponse, response); + assertEquals(42L, response.get()); + } + + @Test + @SneakyThrows + 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); + + var value = client.clientId(ALL_NODES).get(); + assertEquals(data, value.getMultiValue()); + } + + @Test + @SneakyThrows + 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()); + } + + @SneakyThrows + @Test + public void clientGetName_returns_success() { + // setup + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn("TEST"); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ClientGetName), eq(new String[0]), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.clientGetName(); + + // verify + assertEquals(testResponse, response); + assertEquals("TEST", response.get()); + } + + @Test + @SneakyThrows + 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()); + } + + @Test + @SneakyThrows + 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()); + } } 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 89a507fbb7..8a8660f900 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -3,6 +3,8 @@ import static glide.api.models.commands.SetOptions.RETURN_OLD_VALUE; import static org.junit.jupiter.api.Assertions.assertEquals; +import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; +import static redis_request.RedisRequestOuterClass.RequestType.ClientId; import static redis_request.RedisRequestOuterClass.RequestType.Decr; import static redis_request.RedisRequestOuterClass.RequestType.DecrBy; import static redis_request.RedisRequestOuterClass.RequestType.Del; @@ -269,6 +271,12 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.ttl("key"); results.add(Pair.of(TTL, ArgsArray.newBuilder().addArgs("key").build())); + transaction.clientId(); + results.add(Pair.of(ClientId, ArgsArray.newBuilder().build())); + + transaction.clientGetName(); + results.add(Pair.of(ClientGetName, ArgsArray.newBuilder().build())); + var protobufTransaction = transaction.getProtobufTransaction().build(); for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) { diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 26acf92eda..e8742fc103 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -296,7 +296,7 @@ public void set_missing_value_and_returnOldValue_is_null(BaseClient client) { assertEquals(OK, ok); SetOptions options = SetOptions.builder().returnOldValue(true).build(); - String data = client.set("another", ANOTHER_VALUE, options).get(); + String data = client.set(UUID.randomUUID().toString(), ANOTHER_VALUE, options).get(); assertNull(data); } diff --git a/java/integTest/src/test/java/glide/TestUtilities.java b/java/integTest/src/test/java/glide/TestUtilities.java new file mode 100644 index 0000000000..49570a117e --- /dev/null +++ b/java/integTest/src/test/java/glide/TestUtilities.java @@ -0,0 +1,14 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide; + +import glide.api.models.ClusterValue; +import lombok.experimental.UtilityClass; + +@UtilityClass +public class TestUtilities { + + /** Extract first value from {@link ClusterValue} assuming it contains a multi-value. */ + public static T getFirstEntryFromMultiValue(ClusterValue data) { + return data.getMultiValue().get(data.getMultiValue().keySet().toArray(String[]::new)[0]); + } +} diff --git a/java/integTest/src/test/java/glide/cluster/CommandTests.java b/java/integTest/src/test/java/glide/cluster/CommandTests.java index 408cabb8a6..4c9be4f1ca 100644 --- a/java/integTest/src/test/java/glide/cluster/CommandTests.java +++ b/java/integTest/src/test/java/glide/cluster/CommandTests.java @@ -3,6 +3,7 @@ import static glide.TestConfiguration.CLUSTER_PORTS; import static glide.TestConfiguration.REDIS_VERSION; +import static glide.TestUtilities.getFirstEntryFromMultiValue; import static glide.api.models.commands.InfoOptions.Section.CLIENTS; import static glide.api.models.commands.InfoOptions.Section.CLUSTER; import static glide.api.models.commands.InfoOptions.Section.COMMANDSTATS; @@ -274,4 +275,64 @@ public void info_with_multi_node_route_and_options() { } } } + + @Test + @SneakyThrows + public void clientId() { + var id = clusterClient.clientId().get(); + assertTrue(id > 0); + } + + @Test + @SneakyThrows + public void clientId_with_single_node_route() { + var data = clusterClient.clientId(RANDOM).get(); + assertTrue(data.getSingleValue() > 0L); + } + + @Test + @SneakyThrows + public void clientId_with_multi_node_route() { + var data = clusterClient.clientId(ALL_NODES).get(); + data.getMultiValue().values().forEach(id -> assertTrue(id > 0)); + } + + @Test + @SneakyThrows + public void clientGetName() { + // TODO replace with the corresponding command once implemented + clusterClient.customCommand(new String[] {"client", "setname", "clientGetName"}).get(); + + var name = clusterClient.clientGetName().get(); + + assertEquals("clientGetName", name); + } + + @Test + @SneakyThrows + public void clientGetName_with_single_node_route() { + // TODO replace with the corresponding command once implemented + clusterClient + .customCommand( + new String[] {"client", "setname", "clientGetName_with_single_node_route"}, ALL_NODES) + .get(); + + var name = clusterClient.clientGetName(RANDOM).get(); + + assertEquals("clientGetName_with_single_node_route", name.getSingleValue()); + } + + @Test + @SneakyThrows + public void clientGetName_with_multi_node_route() { + // TODO replace with the corresponding command once implemented + clusterClient + .customCommand( + new String[] {"client", "setname", "clientGetName_with_multi_node_route"}, ALL_NODES) + .get(); + + var name = clusterClient.clientGetName(ALL_NODES).get(); + + assertEquals("clientGetName_with_multi_node_route", getFirstEntryFromMultiValue(name)); + } } diff --git a/java/integTest/src/test/java/glide/standalone/CommandTests.java b/java/integTest/src/test/java/glide/standalone/CommandTests.java index 747c6e152d..86382214bb 100644 --- a/java/integTest/src/test/java/glide/standalone/CommandTests.java +++ b/java/integTest/src/test/java/glide/standalone/CommandTests.java @@ -142,4 +142,22 @@ public void select_test_gives_error() { assertThrows(ExecutionException.class, () -> regularClient.select(-1).get()); assertTrue(e.getCause() instanceof RequestException); } + + @Test + @SneakyThrows + public void clientId() { + var id = regularClient.clientId().get(); + assertTrue(id > 0); + } + + @Test + @SneakyThrows + public void clientGetName() { + // TODO replace with the corresponding command once implemented + regularClient.customCommand(new String[] {"client", "setname", "clientGetName"}).get(); + + var name = regularClient.clientGetName().get(); + + assertEquals("clientGetName", name); + } } From 4c9cb79a79aa8712ce2e3c5bed214017c693aa81 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:08:19 -0800 Subject: [PATCH 49/65] Java: Added examples to documentation for HashCommands. (#121) (#1069) * Java: Added examples to documentation for HashCommands. (#121) * Minor Documentation changes. * Minor Documentation changes. * Minor documentation changes. --- .../java/glide/api/commands/HashCommands.java | 61 ++++++++++++------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/java/client/src/main/java/glide/api/commands/HashCommands.java b/java/client/src/main/java/glide/api/commands/HashCommands.java index 5ffe906882..35d1bbc9ac 100644 --- a/java/client/src/main/java/glide/api/commands/HashCommands.java +++ b/java/client/src/main/java/glide/api/commands/HashCommands.java @@ -19,6 +19,14 @@ public interface HashCommands { * @param field The field in the hash stored at key to retrieve from the database. * @return The value associated with field, or null when field * is not present in the hash or key does not exist. + * @example + *
{@code
+     * String payload = client.hget("my_hash", "field1").get();
+     * assert payload.equals("value");
+     *
+     * String payload = client.hget("my_hash", "nonexistent_field").get();
+     * assert payload.equals(null);
+     * }
*/ CompletableFuture hget(String key, String field); @@ -30,6 +38,11 @@ public interface HashCommands { * @param fieldValueMap A field-value map consisting of fields and their corresponding values to * be set in the hash stored at the specified key. * @return The number of fields that were added. + * @example + *
{@code
+     * Long num = client.hset("my_hash", Map.of("field", "value", "field2", "value2")).get();
+     * assert num == 2L;
+     * }
*/ CompletableFuture hset(String key, Map fieldValueMap); @@ -43,6 +56,11 @@ public interface HashCommands { * @return The number of fields that were removed from the hash, not including specified but * non-existing fields.
* If key does not exist, it is treated as an empty hash and it returns 0.
+ * @example + *
{@code
+     * Long num = client.hdel("my_hash", new String[] {}).get("field1", "field2");
+     * assert num == 2L; //Indicates that two fields were successfully removed from the hash.
+     * }
*/ CompletableFuture hdel(String key, String[] fields); @@ -58,10 +76,10 @@ public interface HashCommands { * If key does not exist, it is treated as an empty hash, and it returns an array * of null values.
* @example - *
+     *     
{@code
      * String[] values = client.hmget("my_hash", new String[] {"field1", "field2"}).get()
-     * assert values == new String[] {"value1", "value2"}
-     * 
+ * assert values.equals(new String[] {"value1", "value2"}); + * }
*/ CompletableFuture hmget(String key, String[] fields); @@ -74,12 +92,13 @@ public interface HashCommands { * @return True if the hash contains the specified field. If the hash does not * contain the field, or if the key does not exist, it returns False. * @example - *
-     * Boolean exists = client.hexists("my_hash", "field1").get()
-     * assert exists
-     * Boolean exists = client.hexists("my_hash", "non_existent_field").get()
-     * assert !exists
-     * 
+ *
{@code
+     * Boolean exists = client.hexists("my_hash", "field1").get();
+     * assert exists;
+     *
+     * Boolean exists = client.hexists("my_hash", "non_existent_field").get();
+     * assert !exists;
+     * }
*/ CompletableFuture hexists(String key, String field); @@ -92,10 +111,10 @@ public interface HashCommands { * the map is associated with its corresponding value.
* If key does not exist, it returns an empty map. * @example - *
-     * Map fieldValueMap = client.hgetall("my_hash").get()
-     * assert fieldValueMap.equals(Map.of(field1", "value1", "field2", "value2"))
-     * 
+ *
{@code
+     * Map fieldValueMap = client.hgetall("my_hash").get();
+     * assert fieldValueMap.equals(Map.of(field1", "value1", "field2", "value2"));
+     * }
*/ CompletableFuture> hgetall(String key); @@ -114,10 +133,10 @@ public interface HashCommands { * @return The value of field in the hash stored at key after the * increment or decrement. * @example - *
-     * Long num = client.hincrBy("my_hash", "field1", 5).get()
-     * assert num == 5L
-     * 
+ *
{@code
+     * Long num = client.hincrBy("my_hash", "field1", 5).get();
+     * assert num == 5L;
+     * }
*/ CompletableFuture hincrBy(String key, String field, long amount); @@ -137,10 +156,10 @@ public interface HashCommands { * @returns The value of field in the hash stored at key after the * increment or decrement. * @example - *
-     * Double num = client.hincrByFloat("my_hash", "field1", 2.5).get()
-     * assert num == 2.5
-     * 
+ *
{@code
+     * Double num = client.hincrByFloat("my_hash", "field1", 2.5).get();
+     * assert num == 2.5;
+     * }
*/ CompletableFuture hincrByFloat(String key, String field, double amount); } From ba48e079a1a7152798da6e88f6b28daa4a7ecacf Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:09:09 -0800 Subject: [PATCH 50/65] Java: Refactor channel closure handling (#118) (#1074) * Refactor channel closure handling (#118) * Ensure resources complete with Closing Error when client closes. New command submissions on closed clients now also return a future with a ClosingException Set. (Consistent with Py and Node clients) * Added a private boolean that saves the state of channel handler. * Changed Boolean to Atomic Boolean added IT tests and modified UT tests. * Fix IT * Minor refactor for ClientTests. --- .../handlers/CallbackDispatcher.java | 3 ++- .../connectors/handlers/ChannelHandler.java | 7 +++++++ .../java/glide/managers/CommandManager.java | 7 +++++++ .../ConnectionWithGlideMockTests.java | 10 ++------- .../glide/managers/CommandManagerTest.java | 8 +++++++ .../{ClientTest.java => ClientTests.java} | 21 ++++++++++++++++++- .../{ClientTest.java => ClientTests.java} | 21 ++++++++++++++++++- 7 files changed, 66 insertions(+), 11 deletions(-) rename java/integTest/src/test/java/glide/cluster/{ClientTest.java => ClientTests.java} (55%) rename java/integTest/src/test/java/glide/standalone/{ClientTest.java => ClientTests.java} (54%) diff --git a/java/client/src/main/java/glide/connectors/handlers/CallbackDispatcher.java b/java/client/src/main/java/glide/connectors/handlers/CallbackDispatcher.java index dfaf01bbe7..6c5e86e2d2 100644 --- a/java/client/src/main/java/glide/connectors/handlers/CallbackDispatcher.java +++ b/java/client/src/main/java/glide/connectors/handlers/CallbackDispatcher.java @@ -129,7 +129,8 @@ public void distributeClosingException(String message) { } public void shutdownGracefully() { - responses.values().forEach(future -> future.cancel(false)); + String msg = "Operation terminated: The closing process has been initiated for the resource."; + responses.values().forEach(future -> future.completeExceptionally(new ClosingException(msg))); responses.clear(); } } diff --git a/java/client/src/main/java/glide/connectors/handlers/ChannelHandler.java b/java/client/src/main/java/glide/connectors/handlers/ChannelHandler.java index 32637219d1..4800316803 100644 --- a/java/client/src/main/java/glide/connectors/handlers/ChannelHandler.java +++ b/java/client/src/main/java/glide/connectors/handlers/ChannelHandler.java @@ -9,6 +9,7 @@ import io.netty.channel.ChannelFutureListener; import io.netty.channel.unix.DomainSocketAddress; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; import lombok.NonNull; import lombok.RequiredArgsConstructor; import redis_request.RedisRequestOuterClass.RedisRequest; @@ -22,6 +23,11 @@ public class ChannelHandler { protected final Channel channel; protected final CallbackDispatcher callbackDispatcher; + private AtomicBoolean isClosed = new AtomicBoolean(false); + + public boolean isClosed() { + return this.isClosed.get() || !this.channel.isOpen(); + } /** * Open a new channel for a new client and running it on the provided EventLoopGroup. @@ -84,6 +90,7 @@ public CompletableFuture connect(ConnectionRequest request) { /** Closes the UDS connection and frees corresponding resources. */ public ChannelFuture close() { + this.isClosed.set(true); callbackDispatcher.shutdownGracefully(); return channel.close(); } diff --git a/java/client/src/main/java/glide/managers/CommandManager.java b/java/client/src/main/java/glide/managers/CommandManager.java index 770830ce2f..253a83f317 100644 --- a/java/client/src/main/java/glide/managers/CommandManager.java +++ b/java/client/src/main/java/glide/managers/CommandManager.java @@ -109,6 +109,13 @@ public CompletableFuture submitNewCommand( */ protected CompletableFuture submitCommandToChannel( RedisRequest.Builder command, RedisExceptionCheckedFunction responseHandler) { + if (channel.isClosed()) { + var errorFuture = new CompletableFuture(); + errorFuture.completeExceptionally( + new ClosingException("Channel closed: Unable to submit command.")); + return errorFuture; + } + // write command request to channel // when complete, convert the response to our expected type T using the given responseHandler return channel diff --git a/java/client/src/test/java/glide/connection/ConnectionWithGlideMockTests.java b/java/client/src/test/java/glide/connection/ConnectionWithGlideMockTests.java index 52be39bacc..331af6fa39 100644 --- a/java/client/src/test/java/glide/connection/ConnectionWithGlideMockTests.java +++ b/java/client/src/test/java/glide/connection/ConnectionWithGlideMockTests.java @@ -173,14 +173,8 @@ public void rethrow_error_if_UDS_channel_closed() { stopRustCoreLibMock(); try { var exception = - assertThrows( - ExecutionException.class, () -> client.customCommand(new String[0]).get(1, SECONDS)); - assertTrue(exception.getCause() instanceof RuntimeException); - - // Not a public class, can't import - assertEquals( - "io.netty.channel.StacklessClosedChannelException", - exception.getCause().getCause().getClass().getName()); + 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/managers/CommandManagerTest.java b/java/client/src/test/java/glide/managers/CommandManagerTest.java index 0f7f539ebb..a64c6499ad 100644 --- a/java/client/src/test/java/glide/managers/CommandManagerTest.java +++ b/java/client/src/test/java/glide/managers/CommandManagerTest.java @@ -60,6 +60,7 @@ public void submitNewCommand_return_Object_result() { CompletableFuture future = new CompletableFuture<>(); future.complete(respPointerResponse); when(channelHandler.write(any(), anyBoolean())).thenReturn(future); + when(channelHandler.isClosed()).thenReturn(false); // exercise CompletableFuture result = @@ -81,6 +82,7 @@ public void submitNewCommand_return_Null_result() { CompletableFuture future = new CompletableFuture<>(); future.complete(respPointerResponse); when(channelHandler.write(any(), anyBoolean())).thenReturn(future); + when(channelHandler.isClosed()).thenReturn(false); // exercise CompletableFuture result = @@ -107,6 +109,7 @@ public void submitNewCommand_return_String_result() { CompletableFuture future = new CompletableFuture<>(); future.complete(respPointerResponse); when(channelHandler.write(any(), anyBoolean())).thenReturn(future); + when(channelHandler.isClosed()).thenReturn(false); // exercise CompletableFuture result = @@ -126,6 +129,7 @@ public void submitNewCommand_return_String_result() { public void prepare_request_with_simple_routes(SimpleRoute routeType) { CompletableFuture future = new CompletableFuture<>(); when(channelHandler.write(any(), anyBoolean())).thenReturn(future); + when(channelHandler.isClosed()).thenReturn(false); ArgumentCaptor captor = ArgumentCaptor.forClass(RedisRequest.Builder.class); @@ -156,6 +160,7 @@ public void prepare_request_with_simple_routes(SimpleRoute routeType) { public void prepare_request_with_slot_id_routes(SlotType slotType) { CompletableFuture future = new CompletableFuture<>(); when(channelHandler.write(any(), anyBoolean())).thenReturn(future); + when(channelHandler.isClosed()).thenReturn(false); ArgumentCaptor captor = ArgumentCaptor.forClass(RedisRequest.Builder.class); @@ -188,6 +193,7 @@ public void prepare_request_with_slot_id_routes(SlotType slotType) { public void prepare_request_with_slot_key_routes(SlotType slotType) { CompletableFuture future = new CompletableFuture<>(); when(channelHandler.write(any(), anyBoolean())).thenReturn(future); + when(channelHandler.isClosed()).thenReturn(false); ArgumentCaptor captor = ArgumentCaptor.forClass(RedisRequest.Builder.class); @@ -239,6 +245,7 @@ public void submitNewCommand_with_Transaction_sends_protobuf_request() { CompletableFuture future = new CompletableFuture<>(); when(channelHandler.write(any(), anyBoolean())).thenReturn(future); + when(channelHandler.isClosed()).thenReturn(false); ArgumentCaptor captor = ArgumentCaptor.forClass(RedisRequest.Builder.class); @@ -279,6 +286,7 @@ public void submitNewCommand_with_ClusterTransaction_with_route_sends_protobuf_r CompletableFuture future = new CompletableFuture<>(); when(channelHandler.write(any(), anyBoolean())).thenReturn(future); + when(channelHandler.isClosed()).thenReturn(false); ArgumentCaptor captor = ArgumentCaptor.forClass(RedisRequest.Builder.class); diff --git a/java/integTest/src/test/java/glide/cluster/ClientTest.java b/java/integTest/src/test/java/glide/cluster/ClientTests.java similarity index 55% rename from java/integTest/src/test/java/glide/cluster/ClientTest.java rename to java/integTest/src/test/java/glide/cluster/ClientTests.java index b9474f592b..3924747391 100644 --- a/java/integTest/src/test/java/glide/cluster/ClientTest.java +++ b/java/integTest/src/test/java/glide/cluster/ClientTests.java @@ -2,15 +2,18 @@ package glide.cluster; import static glide.TestConfiguration.CLUSTER_PORTS; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import glide.api.RedisClusterClient; import glide.api.models.configuration.NodeAddress; import glide.api.models.configuration.RedisClusterClientConfiguration; +import glide.api.models.exceptions.ClosingException; +import java.util.concurrent.ExecutionException; import lombok.SneakyThrows; import org.junit.jupiter.api.Test; -public class ClientTest { +public class ClientTests { @Test @SneakyThrows public void custom_command_info() { @@ -26,4 +29,20 @@ public void custom_command_info() { (String) client.customCommand(new String[] {"CLIENT", "INFO"}).get().getSingleValue(); assertTrue(clientInfo.contains("name=TEST_CLIENT_NAME")); } + + @Test + @SneakyThrows + public void close_client_throws_ExecutionException_with_ClosingException_cause() { + RedisClusterClient client = + RedisClusterClient.CreateClient( + RedisClusterClientConfiguration.builder() + .address(NodeAddress.builder().port(CLUSTER_PORTS[0]).build()) + .build()) + .get(); + + client.close(); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.set("foo", "bar").get()); + assertTrue(executionException.getCause() instanceof ClosingException); + } } diff --git a/java/integTest/src/test/java/glide/standalone/ClientTest.java b/java/integTest/src/test/java/glide/standalone/ClientTests.java similarity index 54% rename from java/integTest/src/test/java/glide/standalone/ClientTest.java rename to java/integTest/src/test/java/glide/standalone/ClientTests.java index ebee99e915..960696b06e 100644 --- a/java/integTest/src/test/java/glide/standalone/ClientTest.java +++ b/java/integTest/src/test/java/glide/standalone/ClientTests.java @@ -2,15 +2,18 @@ package glide.standalone; import static glide.TestConfiguration.STANDALONE_PORTS; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import glide.api.RedisClient; import glide.api.models.configuration.NodeAddress; import glide.api.models.configuration.RedisClientConfiguration; +import glide.api.models.exceptions.ClosingException; +import java.util.concurrent.ExecutionException; import lombok.SneakyThrows; import org.junit.jupiter.api.Test; -public class ClientTest { +public class ClientTests { @Test @SneakyThrows public void custom_command_info() { @@ -25,4 +28,20 @@ public void custom_command_info() { String clientInfo = (String) client.customCommand(new String[] {"CLIENT", "INFO"}).get(); assertTrue(clientInfo.contains("name=TEST_CLIENT_NAME")); } + + @Test + @SneakyThrows + public void close_client_throws_ExecutionException_with_ClosingException_cause() { + RedisClient client = + RedisClient.CreateClient( + RedisClientConfiguration.builder() + .address(NodeAddress.builder().port(STANDALONE_PORTS[0]).build()) + .build()) + .get(); + + client.close(); + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> client.set("key", "value").get()); + assertTrue(executionException.getCause() instanceof ClosingException); + } } From f1d5e6755ecde76b1411c60f9a00437c76ec1f82 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:10:21 -0800 Subject: [PATCH 51/65] Java: Added examples to documentation for StringCommands. (#120) (#1068) * Java: Added examples to documentation for StringCommands. (#120) * Minor Documentation changes. * Minor documentation changes. --- .../glide/api/commands/StringCommands.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/java/client/src/main/java/glide/api/commands/StringCommands.java b/java/client/src/main/java/glide/api/commands/StringCommands.java index 97bb98fa31..7431927947 100644 --- a/java/client/src/main/java/glide/api/commands/StringCommands.java +++ b/java/client/src/main/java/glide/api/commands/StringCommands.java @@ -22,6 +22,14 @@ public interface StringCommands { * @param key The key to retrieve from the database. * @return Response from Redis. If key exists, returns the value of * key as a String. Otherwise, return null. + * @example + *
{@code
+     * String payload = client.get("key").get();
+     * assert payload.equals("value");
+     *
+     * String payload = client.get("non_existing_key").get();
+     * assert payload.equals(null);
+     * }
*/ CompletableFuture get(String key); @@ -32,6 +40,11 @@ public interface StringCommands { * @param key The key to store. * @param value The value to store with the given key. * @return Response from Redis containing "OK". + * @example + *
{@code
+     * String payload = client.set("key", "value").get();
+     * assert payload.equals("OK");
+     * }
*/ CompletableFuture set(String key, String value); @@ -47,6 +60,16 @@ public interface StringCommands { * {@link ConditionalSet#ONLY_IF_EXISTS} or {@link ConditionalSet#ONLY_IF_DOES_NOT_EXIST} * conditions, return null. If {@link SetOptionsBuilder#returnOldValue(boolean)} * is set, return the old value as a String. + * @example + *
{@code
+     * String payload =
+     *         client.set("key", "value", SetOptions.builder()
+     *                 .conditionalSet(ONLY_IF_EXISTS)
+     *                 .expiry(SetOptions.Expiry.Seconds(5L))
+     *                 .build())
+     *                 .get();
+     * assert payload.equals("OK");
+     * }
*/ CompletableFuture set(String key, String value, SetOptions options); @@ -58,6 +81,11 @@ public interface StringCommands { * @return An array of values corresponding to the provided keys.
* If a keyis not found, its corresponding value in the list will be null * . + * @example + *
{@code
+     * String payload = client.mget(new String[] {"key1", "key2"}).get();
+     * assert payload.equals(new String[] {"value1", "value2"});
+     * }
*/ CompletableFuture mget(String[] keys); @@ -67,6 +95,11 @@ public interface StringCommands { * @see redis.io for details. * @param keyValueMap A key-value map consisting of keys and their respective values to set. * @return Always OK. + * @example + *
{@code
+     * String payload = client.mset(Map.of("key1", "value1", "key2", "value2"}).get();
+     * assert payload.equals("OK"));
+     * }
*/ CompletableFuture mset(Map keyValueMap); @@ -77,6 +110,11 @@ public interface StringCommands { * @see redis.io for details. * @param key The key to increment its value. * @return The value of key after the increment. + * @example + *
{@code
+     * Long num = client.incr("key").get();
+     * assert num == 5L;
+     * }
*/ CompletableFuture incr(String key); @@ -88,6 +126,11 @@ public interface StringCommands { * @param key The key to increment its value. * @param amount The amount to increment. * @return The value of key after the increment. + * @example + *
{@code
+     * Long num = client.incrBy("key", 2).get();
+     * assert num == 7L;
+     * }
*/ CompletableFuture incrBy(String key, long amount); @@ -101,6 +144,11 @@ public interface StringCommands { * @param key The key to increment its value. * @param amount The amount to increment. * @return The value of key after the increment. + * @example + *
{@code
+     * Long num = client.incrByFloat("key", 0.5).get();
+     * assert num == 7.5;
+     * }
*/ CompletableFuture incrByFloat(String key, double amount); @@ -111,6 +159,11 @@ public interface StringCommands { * @see redis.io for details. * @param key The key to decrement its value. * @return The value of key after the decrement. + * @example + *
{@code
+     * Long num = client.decr("key").get();
+     * assert num == 4L;
+     * }
*/ CompletableFuture decr(String key); @@ -122,6 +175,11 @@ public interface StringCommands { * @param key The key to decrement its value. * @param amount The amount to decrement. * @return The value of key after the decrement. + * @example + *
{@code
+     * Long num = client.decrBy("key", 2).get();
+     * assert num == 2L;
+     * }
*/ CompletableFuture decrBy(String key, long amount); } From 24959845f4b0aec440dfb2702f8b394a255b8c4a Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:19:01 -0800 Subject: [PATCH 52/65] Java: Stylistic documentation changes Set Commands. (#124) (#1071) * Java: Stylistic documentation changes. (#124) * Minor Documentation changes. --- .../java/glide/api/commands/SetCommands.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/java/client/src/main/java/glide/api/commands/SetCommands.java b/java/client/src/main/java/glide/api/commands/SetCommands.java index f2098c9b08..c5463ca18c 100644 --- a/java/client/src/main/java/glide/api/commands/SetCommands.java +++ b/java/client/src/main/java/glide/api/commands/SetCommands.java @@ -21,10 +21,10 @@ public interface SetCommands { * @remarks If key does not exist, a new set is created before adding members * . * @example - *

- * int result = client.sadd("my_set", new String[]{"member1", "member2"}).get(); - * // result: 2 - * + *

{@code
+     * Long result = client.sadd("my_set", new String[]{"member1", "member2"}).get();
+     * assert result == 2L;
+     * }
*/ CompletableFuture sadd(String key, String[] members); @@ -39,10 +39,10 @@ public interface SetCommands { * @remarks If key does not exist, it is treated as an empty set and this command * returns 0. * @example - *

- * int result = client.srem("my_set", new String[]{"member1", "member2"}).get(); - * // result: 2 - * + *

{@code
+     * Long result = client.srem("my_set", new String[]{"member1", "member2"}).get();
+     * assert result == 2L;
+     * }
*/ CompletableFuture srem(String key, String[] members); @@ -54,10 +54,10 @@ public interface SetCommands { * @return A Set of all members of the set. * @remarks If key does not exist an empty set will be returned. * @example - *

- * {@literal Set} result = client.smembers("my_set").get(); - * // result: {"member1", "member2", "member3"} - * + *

{@code
+     * Set result = client.smembers("my_set").get();
+     * assert result.equals(Set.of("member1", "member2", "member3"));
+     * }
*/ CompletableFuture> smembers(String key); @@ -68,10 +68,10 @@ public interface SetCommands { * @param key The key from which to retrieve the number of set members. * @return The cardinality (number of elements) of the set, or 0 if the key does not exist. * @example - *

- * int result = client.scard("my_set").get(); - * // result: 3 - * + *

{@code
+     * Long result = client.scard("my_set").get();
+     * assert result == 3L;
+     * }
*/ CompletableFuture scard(String key); } From 5c4601584aa021b05e4f0c8c9cd8b013b5e1b847 Mon Sep 17 00:00:00 2001 From: SanHalacogluImproving <144171266+SanHalacogluImproving@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:19:35 -0800 Subject: [PATCH 53/65] Java: Add examples to missing commands and minor fixes Generic Commands. (#125) (#1070) * Java: Add examples to missing commands and minor fixes. (#125) * Minor documentation changes. * Minor documentation changes. --- .../api/commands/GenericBaseCommands.java | 91 ++++++++++--------- .../api/commands/GenericClusterCommands.java | 35 +++++-- .../glide/api/commands/GenericCommands.java | 12 +++ 3 files changed, 87 insertions(+), 51 deletions(-) diff --git a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java index e7a47bf279..d8c29319aa 100644 --- a/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericBaseCommands.java @@ -18,6 +18,11 @@ public interface GenericBaseCommands { * @see redis.io for details. * @param keys The keys we wanted to remove. * @return The number of keys that were removed. + * @example + *
{@code
+     * Long num = client.del(new String[] {"key1", "key2"}).get();
+     * assert num == 2l;
+     * }
*/ CompletableFuture del(String[] keys); @@ -29,10 +34,10 @@ public interface GenericBaseCommands { * @return The number of keys that exist. If the same existing key is mentioned in keys * multiple times, it will be counted multiple times. * @example - *

- * long result = client.exists(new String[] {"my_key", "invalid_key"}).get(); + *

{@code
+     * Long result = client.exists(new String[] {"my_key", "invalid_key"}).get();
      * assert result == 1L;
-     * 
+     * }
*/ CompletableFuture exists(String[] keys); @@ -46,11 +51,10 @@ public interface GenericBaseCommands { * @param keys The list of keys to unlink. * @return The number of keys that were unlinked. * @example - *

- *

-     * long result = client.unlink("my_key").get();
+     *     
{@code
+     * Long result = client.unlink("my_key").get();
      * assert result == 1L;
-     * 
+ * }
*/ CompletableFuture unlink(String[] keys); @@ -70,10 +74,10 @@ public interface GenericBaseCommands { * @return true if the timeout was set. false if the timeout was not * set. e.g. key doesn't exist. * @example - *
-     * Boolean isSet = client.expire("my_key", 60).get()
-     * assert isSet //Indicates that a timeout of 60 seconds has been set for "my_key."
-     * 
+ *
{@code
+     * Boolean isSet = client.expire("my_key", 60).get();
+     * assert isSet; //Indicates that a timeout of 60 seconds has been set for "my_key."
+     * }
*/ CompletableFuture expire(String key, long seconds); @@ -95,10 +99,10 @@ public interface GenericBaseCommands { * set. e.g. key doesn't exist, or operation skipped due to the provided * arguments. * @example - *
-     * Boolean isSet = client.expire("my_key", 60, ExpireOptions.HAS_NO_EXPIRY).get()
-     * assert isSet //Indicates that a timeout of 60 seconds has been set for "my_key."
-     * 
+ *
{@code
+     * Boolean isSet = client.expire("my_key", 60, ExpireOptions.HAS_NO_EXPIRY).get();
+     * assert isSet; //Indicates that a timeout of 60 seconds has been set for "my_key."
+     * }
*/ CompletableFuture expire(String key, long seconds, ExpireOptions expireOptions); @@ -118,10 +122,10 @@ public interface GenericBaseCommands { * @return true if the timeout was set. false if the timeout was not * set. e.g. key doesn't exist. * @example - *
-     * Boolean isSet = client.expireAt("my_key", Instant.now().getEpochSecond() + 10).get()
-     * assert isSet
-     * 
+ *
{@code
+     * Boolean isSet = client.expireAt("my_key", Instant.now().getEpochSecond() + 10).get();
+     * assert isSet;
+     * }
*/ CompletableFuture expireAt(String key, long unixSeconds); @@ -143,10 +147,10 @@ public interface GenericBaseCommands { * set. e.g. key doesn't exist, or operation skipped due to the provided * arguments. * @example - *
-     * Boolean isSet = client.expireAt("my_key", Instant.now().getEpochSecond() + 10, ExpireOptions.HasNoExpiry).get()
-     * assert isSet
-     * 
+ *
{@code
+     * Boolean isSet = client.expireAt("my_key", Instant.now().getEpochSecond() + 10, ExpireOptions.HasNoExpiry).get();
+     * assert isSet;
+     * }
*/ CompletableFuture expireAt(String key, long unixSeconds, ExpireOptions expireOptions); @@ -166,10 +170,10 @@ public interface GenericBaseCommands { * @return true if the timeout was set. false if the timeout was not * set. e.g. key doesn't exist. * @example - *
-     * Boolean isSet = client.pexpire("my_key", 60000).get()
-     * assert isSet
-     * 
+ *
{@code
+     * Boolean isSet = client.pexpire("my_key", 60000).get();
+     * assert isSet;
+     * }
*/ CompletableFuture pexpire(String key, long milliseconds); @@ -191,10 +195,10 @@ public interface GenericBaseCommands { * set. e.g. key doesn't exist, or operation skipped due to the provided * arguments. * @example - *
-     * Boolean isSet = client.pexpire("my_key", 60000, ExpireOptions.HasNoExpiry).get()
-     * assert isSet
-     * 
+ *
{@code
+     * Boolean isSet = client.pexpire("my_key", 60000, ExpireOptions.HasNoExpiry).get();
+     * assert isSet;
+     * }
*/ CompletableFuture pexpire(String key, long milliseconds, ExpireOptions expireOptions); @@ -214,10 +218,10 @@ public interface GenericBaseCommands { * @return true if the timeout was set. false if the timeout was not * set. e.g. key doesn't exist. * @example - *
-     * Boolean isSet = client.pexpireAt("my_key", Instant.now().toEpochMilli() + 10).get()
-     * assert isSet
-     * 
+ *
{@code
+     * Boolean isSet = client.pexpireAt("my_key", Instant.now().toEpochMilli() + 10).get();
+     * assert isSet;
+     * }
*/ CompletableFuture pexpireAt(String key, long unixMilliseconds); @@ -239,10 +243,10 @@ public interface GenericBaseCommands { * set. e.g. key doesn't exist, or operation skipped due to the provided * arguments. * @example - *
-     * Boolean isSet = client.pexpireAt("my_key", Instant.now().toEpochMilli() + 10, ExpireOptions.HasNoExpiry).get()
-     * assert isSet
-     * 
+ *
{@code
+     * Boolean isSet = client.pexpireAt("my_key", Instant.now().toEpochMilli() + 10, ExpireOptions.HasNoExpiry).get();
+     * assert isSet;
+     * }
*/ CompletableFuture pexpireAt( String key, long unixMilliseconds, ExpireOptions expireOptions); @@ -255,12 +259,13 @@ CompletableFuture pexpireAt( * @return TTL in seconds, -2 if key does not exist, or -1 * if key exists but has no associated expire. * @example - *
+     *     
{@code
      * Long timeRemaining = client.ttl("my_key").get()
      * assert timeRemaining == 3600L //Indicates that "my_key" has a remaining time to live of 3600 seconds.
-     * Long timeRemaining = client.ttl("nonexistent_key").get()
-     * assert timeRemaining == -2L //Returns -2 for a non-existing key.
-     * 
+ * + * Long timeRemaining = client.ttl("nonexistent_key").get(); + * assert timeRemaining == -2L; //Returns -2 for a non-existing key. + * }
*/ CompletableFuture ttl(String key); } diff --git a/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java b/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java index 1fbca154cc..95e56335d6 100644 --- a/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java +++ b/java/client/src/main/java/glide/api/commands/GenericClusterCommands.java @@ -26,12 +26,13 @@ public interface GenericClusterCommands { * response (such as XREAD), or that change the client's behavior (such as entering * pub/sub mode on RESP2 connections) shouldn't be called using * this function. - * @example Returns a list of all pub/sub clients: - *

- * Object result = client.customCommand(new String[]{ "CLIENT", "LIST", "TYPE", "PUBSUB" }).get(); - * * @param args Arguments for the custom command including the command name. * @return Response from Redis containing an Object. + * @example + *

{@code
+     * ClusterValue data = client.customCommand(new String[] {"ping"}).get();
+     * assert ((String) data.getSingleValue()).equals("PONG");
+     * }
      */
     CompletableFuture> customCommand(String[] args);
 
@@ -46,13 +47,16 @@ public interface GenericClusterCommands {
      *     response (such as XREAD), or that change the client's behavior (such as entering
      *     pub/sub mode on RESP2 connections) shouldn't be called using
      *     this function.
-     * @example Returns a list of all pub/sub clients:
-     *     

- * Object result = client.customCommand(new String[]{ "CLIENT", "LIST", "TYPE", "PUBSUB" }, RANDOM).get(); - * * @param args Arguments for the custom command including the command name * @param route Routing configuration for the command * @return Response from Redis containing an Object. + * @example + *

{@code
+     * ClusterValue result = clusterClient.customCommand(new String[]{ "CONFIG", "GET", "maxmemory"}, ALL_NODES).get();
+     * Map payload = result.getMultiValue();
+     * assert ((String) payload.get("node1")).equals("1GB");
+     * assert ((String) payload.get("node2")).equals("100MB");
+     * }
      */
     CompletableFuture> customCommand(String[] args, Route route);
 
@@ -73,6 +77,13 @@ public interface GenericClusterCommands {
      *       
  • If the transaction failed due to a WATCH command, exec will * return null. * + * + * @example + *
    {@code
    +     * ClusterTransaction transaction = new ClusterTransaction().customCommand(new String[] {"info"});
    +     * Object[] result = clusterClient.exec(transaction).get();
    +     * assert ((String) result[0]).contains("# Stats");
    +     * }
    */ CompletableFuture exec(ClusterTransaction transaction); @@ -92,6 +103,14 @@ public interface GenericClusterCommands { *
  • If the transaction failed due to a WATCH command, exec will * return null. * + * + * @example + *
    {@code
    +     * ClusterTransaction transaction = new ClusterTransaction().ping().info();
    +     * ClusterValue[] result = clusterClient.exec(transaction, RANDOM).get();
    +     * assert ((String) result[0].getSingleValue()).equals("PONG");
    +     * assert ((String) result[1].getSingleValue()).contains("# Stats");
    +     * }
          */
         CompletableFuture[]> exec(ClusterTransaction transaction, Route route);
     }
    diff --git a/java/client/src/main/java/glide/api/commands/GenericCommands.java b/java/client/src/main/java/glide/api/commands/GenericCommands.java
    index 126188457f..d76a7f4f7b 100644
    --- a/java/client/src/main/java/glide/api/commands/GenericCommands.java
    +++ b/java/client/src/main/java/glide/api/commands/GenericCommands.java
    @@ -28,6 +28,11 @@ public interface GenericCommands {
          *
          * @param args Arguments for the custom command.
          * @return Response from Redis containing an Object.
    +     * @example
    +     *     
    {@code
    +     * Object response = (String) client.customCommand(new String[] {"ping", "GLIDE"}).get()
    +     * assert ((String) response).equals("GLIDE");
    +     * }
    */ CompletableFuture customCommand(String[] args); @@ -45,6 +50,13 @@ public interface GenericCommands { *
  • If the transaction failed due to a WATCH command, exec will * return null. * + * + * @example + *
    {@code
    +     * Transaction transaction = new Transaction().customCommand(new String[] {"info"});
    +     * Object[] result = client.exec(transaction).get();
    +     * assert ((String) result[0]).contains("# Stats");
    +     * }
    */ CompletableFuture exec(Transaction transaction); } From b0dace09c8bbb5b55f4472ec8f19999c78daee01 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Tue, 5 Mar 2024 17:11:37 -0800 Subject: [PATCH 54/65] Add `CONFIG REWRITE` and `CONFIG RESETSTAT`. (#1013) * Add `CONFIG REWRITE` and `CONFIG RESETSTAT`. (#95) --------- Signed-off-by: Yury-Fridlyand Co-authored-by: SanHalacogluImproving --- .../src/main/java/glide/api/RedisClient.java | 14 +++ .../java/glide/api/RedisClusterClient.java | 26 ++++++ .../ServerManagementClusterCommands.java | 66 +++++++++++++- .../commands/ServerManagementCommands.java | 31 ++++++- .../glide/api/models/BaseTransaction.java | 27 ++++++ .../test/java/glide/api/RedisClientTest.java | 42 +++++++++ .../glide/api/RedisClusterClientTest.java | 89 +++++++++++++++++++ .../glide/api/models/TransactionTests.java | 8 ++ .../src/test/java/glide/TestUtilities.java | 12 +++ .../java/glide/TransactionTestUtilities.java | 3 + .../test/java/glide/cluster/CommandTests.java | 31 +++++++ .../java/glide/standalone/CommandTests.java | 25 ++++++ 12 files changed, 372 insertions(+), 2 deletions(-) diff --git a/java/client/src/main/java/glide/api/RedisClient.java b/java/client/src/main/java/glide/api/RedisClient.java index 11911f9bf5..5d106bf131 100644 --- a/java/client/src/main/java/glide/api/RedisClient.java +++ b/java/client/src/main/java/glide/api/RedisClient.java @@ -3,6 +3,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; import static redis_request.RedisRequestOuterClass.RequestType.ClientId; +import static redis_request.RedisRequestOuterClass.RequestType.ConfigResetStat; +import static redis_request.RedisRequestOuterClass.RequestType.ConfigRewrite; import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand; import static redis_request.RedisRequestOuterClass.RequestType.Info; import static redis_request.RedisRequestOuterClass.RequestType.Ping; @@ -88,4 +90,16 @@ public CompletableFuture clientGetName() { return commandManager.submitNewCommand( ClientGetName, new String[0], this::handleStringOrNullResponse); } + + @Override + public CompletableFuture configRewrite() { + return commandManager.submitNewCommand( + ConfigRewrite, new String[0], this::handleStringResponse); + } + + @Override + public CompletableFuture configResetStat() { + return commandManager.submitNewCommand( + ConfigResetStat, new String[0], this::handleStringResponse); + } } diff --git a/java/client/src/main/java/glide/api/RedisClusterClient.java b/java/client/src/main/java/glide/api/RedisClusterClient.java index fb8c060687..6947555ecc 100644 --- a/java/client/src/main/java/glide/api/RedisClusterClient.java +++ b/java/client/src/main/java/glide/api/RedisClusterClient.java @@ -3,6 +3,8 @@ import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; import static redis_request.RedisRequestOuterClass.RequestType.ClientId; +import static redis_request.RedisRequestOuterClass.RequestType.ConfigResetStat; +import static redis_request.RedisRequestOuterClass.RequestType.ConfigRewrite; import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand; import static redis_request.RedisRequestOuterClass.RequestType.Info; import static redis_request.RedisRequestOuterClass.RequestType.Ping; @@ -181,4 +183,28 @@ public CompletableFuture> clientGetName(@NonNull Route rout ? ClusterValue.of(handleStringOrNullResponse(response)) : ClusterValue.of(handleMapResponse(response))); } + + @Override + public CompletableFuture configRewrite() { + return commandManager.submitNewCommand( + ConfigRewrite, new String[0], this::handleStringResponse); + } + + @Override + public CompletableFuture configRewrite(@NonNull Route route) { + return commandManager.submitNewCommand( + ConfigRewrite, new String[0], route, this::handleStringResponse); + } + + @Override + public CompletableFuture configResetStat() { + return commandManager.submitNewCommand( + ConfigResetStat, new String[0], this::handleStringResponse); + } + + @Override + public CompletableFuture configResetStat(@NonNull Route route) { + return commandManager.submitNewCommand( + ConfigResetStat, new String[0], route, this::handleStringResponse); + } } diff --git a/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java b/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java index f511cc81fb..33d3c3a4ed 100644 --- a/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java +++ b/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java @@ -8,7 +8,7 @@ import java.util.concurrent.CompletableFuture; /** - * Server Management Commands interface. + * Server Management Commands interface for cluster client. * * @see Server Management Commands */ @@ -71,4 +71,68 @@ public interface ServerManagementClusterCommands { * value is the information of the sections requested for the node. */ CompletableFuture> info(InfoOptions options, Route route); + + /** + * Rewrites the configuration file with the current configuration.
    + * The command will be routed automatically to all nodes. + * + * @see redis.io for details. + * @return OK when the configuration was rewritten properly, otherwise an error is + * thrown. + * @example + *
    {@code
    +     * String response = client.configRewrite().get();
    +     * assert response.equals("OK")
    +     * }
    + */ + CompletableFuture configRewrite(); + + /** + * Rewrites the configuration file with the current configuration. + * + * @see redis.io for details. + * @param route Routing configuration for the command. Client will route the command to the nodes + * defined. + * @return OK when the configuration was rewritten properly, otherwise an error is + * thrown. + * @example + *
    {@code
    +     * String response = client.configRewrite(ALL_PRIMARIES).get();
    +     * assert response.equals("OK")
    +     * }
    + */ + CompletableFuture configRewrite(Route route); + + /** + * Resets the statistics reported by Redis using the INFO and LATENCY HISTOGRAM commands.
    + * The command will be routed automatically to all nodes. + * + * @see redis.io for details. + * @return OK to confirm that the statistics were successfully reset. + * @example + *
    {@code
    +     * String response = client.configResetStat().get();
    +     * assert response.equals("OK")
    +     * }
    + */ + CompletableFuture configResetStat(); + + /** + * Resets the statistics reported by Redis using the INFO and LATENCY HISTOGRAM commands. + * + * @see redis.io for details. + * @param route Routing configuration for the command. Client will route the command to the nodes + * defined. + * @return OK to confirm that the statistics were successfully reset. + * @example + *
    {@code
    +     * String response = client.configResetStat(ALL_PRIMARIES).get();
    +     * assert response.equals("OK")
    +     * }
    + */ + CompletableFuture configResetStat(Route route); } diff --git a/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java b/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java index 5dd94f93e9..3f5cc514b5 100644 --- a/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java +++ b/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java @@ -6,7 +6,7 @@ import java.util.concurrent.CompletableFuture; /** - * Server Management Commands interface. + * Server Management Commands interface for standalone client. * * @see Server Management Commands */ @@ -40,4 +40,33 @@ public interface ServerManagementCommands { * @return A simple OK response. */ CompletableFuture select(long index); + + /** + * Rewrites the configuration file with the current configuration. + * + * @see redis.io for details. + * @return OK when the configuration was rewritten properly, otherwise an error is + * thrown. + * @example + *
    {@code
    +     * String response = client.configRewrite().get();
    +     * assert response.equals("OK");
    +     * }
    + */ + CompletableFuture configRewrite(); + + /** + * Resets the statistics reported by Redis using the INFO and LATENCY HISTOGRAM commands. + * + * @see redis.io for details. + * @return OK to confirm that the statistics were successfully reset. + * @example + *
    {@code
    +     * String response = client.configResetStat().get();
    +     * assert response.equals("OK");
    +     * }
    + */ + CompletableFuture configResetStat(); } 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 6eed1f4ce1..d51fcd1a16 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -4,6 +4,8 @@ import static glide.utils.ArrayTransformUtils.convertMapToArgArray; import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; import static redis_request.RedisRequestOuterClass.RequestType.ClientId; +import static redis_request.RedisRequestOuterClass.RequestType.ConfigResetStat; +import static redis_request.RedisRequestOuterClass.RequestType.ConfigRewrite; import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand; import static redis_request.RedisRequestOuterClass.RequestType.Decr; import static redis_request.RedisRequestOuterClass.RequestType.DecrBy; @@ -991,6 +993,31 @@ public T clientGetName() { return getThis(); } + /** + * Rewrites the configuration file with the current configuration. + * + * @see redis.io for details. + * @return OK is returned when the configuration was rewritten properly. Otherwise, + * the transaction fails with an error. + */ + public T configRewrite() { + protobufTransaction.addCommands(buildCommand(ConfigRewrite)); + return getThis(); + } + + /** + * Resets the statistics reported by Redis using the INFO and LATENCY HISTOGRAM commands. + * + * @see redis.io for details. + * @return OK to confirm that the statistics were successfully reset. + */ + public T configResetStat() { + protobufTransaction.addCommands(buildCommand(ConfigResetStat)); + return getThis(); + } + /** Build protobuf {@link Command} object for given command and arguments. */ protected Command buildCommand(RequestType requestType) { return buildCommand(requestType, buildArgs()); diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index c56d90e30f..2d69888112 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -14,6 +14,8 @@ import static org.mockito.Mockito.when; import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; import static redis_request.RedisRequestOuterClass.RequestType.ClientId; +import static redis_request.RedisRequestOuterClass.RequestType.ConfigResetStat; +import static redis_request.RedisRequestOuterClass.RequestType.ConfigRewrite; import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand; import static redis_request.RedisRequestOuterClass.RequestType.Decr; import static redis_request.RedisRequestOuterClass.RequestType.DecrBy; @@ -1326,4 +1328,44 @@ public void clientGetName_returns_success() { assertEquals(testResponse, response); assertEquals("TEST", response.get()); } + + @SneakyThrows + @Test + public void configRewrite_returns_success() { + // setup + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(OK); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ConfigRewrite), eq(new String[0]), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.configRewrite(); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(OK, payload); + } + + @SneakyThrows + @Test + public void configResetStat_returns_success() { + // setup + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(OK); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ConfigResetStat), eq(new String[0]), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.configResetStat(); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(OK, payload); + } } diff --git a/java/client/src/test/java/glide/api/RedisClusterClientTest.java b/java/client/src/test/java/glide/api/RedisClusterClientTest.java index 616f3440c7..e478e11917 100644 --- a/java/client/src/test/java/glide/api/RedisClusterClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClusterClientTest.java @@ -1,6 +1,7 @@ /** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api; +import static glide.api.BaseClient.OK; import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleRoute.ALL_NODES; import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleRoute.ALL_PRIMARIES; import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleRoute.RANDOM; @@ -13,6 +14,8 @@ import static org.mockito.Mockito.when; import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; import static redis_request.RedisRequestOuterClass.RequestType.ClientId; +import static redis_request.RedisRequestOuterClass.RequestType.ConfigResetStat; +import static redis_request.RedisRequestOuterClass.RequestType.ConfigRewrite; import static redis_request.RedisRequestOuterClass.RequestType.Info; import static redis_request.RedisRequestOuterClass.RequestType.Ping; @@ -441,4 +444,90 @@ public void clientGetName_with_multi_node_route_returns_success() { 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); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ConfigRewrite), eq(new String[0]), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.configRewrite(); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(OK, payload); + } + + @SneakyThrows + @Test + public void configRewrite_with_route_returns_success() { + // setup + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(OK); + + Route route = ALL_NODES; + + // match on protobuf request + when(commandManager.submitNewCommand( + eq(ConfigRewrite), eq(new String[0]), eq(route), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.configRewrite(route); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(OK, payload); + } + + @SneakyThrows + @Test + public void configResetStat_without_route_returns_success() { + // setup + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(OK); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(ConfigResetStat), eq(new String[0]), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.configResetStat(); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(OK, payload); + } + + @SneakyThrows + @Test + public void configResetStat_with_route_returns_success() { + // setup + CompletableFuture testResponse = mock(CompletableFuture.class); + when(testResponse.get()).thenReturn(OK); + + Route route = ALL_NODES; + + // match on protobuf request + when(commandManager.submitNewCommand( + eq(ConfigResetStat), eq(new String[0]), eq(route), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.configResetStat(route); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(OK, payload); + } } 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 8a8660f900..2e60763ac3 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -5,6 +5,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName; import static redis_request.RedisRequestOuterClass.RequestType.ClientId; +import static redis_request.RedisRequestOuterClass.RequestType.ConfigResetStat; +import static redis_request.RedisRequestOuterClass.RequestType.ConfigRewrite; import static redis_request.RedisRequestOuterClass.RequestType.Decr; import static redis_request.RedisRequestOuterClass.RequestType.DecrBy; import static redis_request.RedisRequestOuterClass.RequestType.Del; @@ -277,6 +279,12 @@ public void transaction_builds_protobuf_request(BaseTransaction transaction) transaction.clientGetName(); results.add(Pair.of(ClientGetName, ArgsArray.newBuilder().build())); + transaction.configRewrite(); + results.add(Pair.of(ConfigRewrite, ArgsArray.newBuilder().build())); + + transaction.configResetStat(); + results.add(Pair.of(ConfigResetStat, ArgsArray.newBuilder().build())); + var protobufTransaction = transaction.getProtobufTransaction().build(); for (int idx = 0; idx < protobufTransaction.getCommandsCount(); idx++) { diff --git a/java/integTest/src/test/java/glide/TestUtilities.java b/java/integTest/src/test/java/glide/TestUtilities.java index 49570a117e..8c18e8b98f 100644 --- a/java/integTest/src/test/java/glide/TestUtilities.java +++ b/java/integTest/src/test/java/glide/TestUtilities.java @@ -1,11 +1,23 @@ /** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ package glide; +import static org.junit.jupiter.api.Assertions.fail; + import glide.api.models.ClusterValue; import lombok.experimental.UtilityClass; @UtilityClass public class TestUtilities { + /** Extract integer parameter value from INFO command output */ + public static int getValueFromInfo(String data, String value) { + for (var line : data.split("\r\n")) { + if (line.contains(value)) { + return Integer.parseInt(line.split(":")[1]); + } + } + fail(); + return 0; + } /** Extract first value from {@link ClusterValue} assuming it contains a multi-value. */ public static T getFirstEntryFromMultiValue(ClusterValue data) { diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 3524d7e26d..94f54998bf 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -79,6 +79,8 @@ public static BaseTransaction transactionTest(BaseTransaction baseTransact baseTransaction.scard(key7); baseTransaction.smembers(key7); + baseTransaction.configResetStat(); + return baseTransaction; } @@ -122,6 +124,7 @@ public static Object[] transactionTestResult() { 1L, 1L, Set.of("baz"), + OK }; } } diff --git a/java/integTest/src/test/java/glide/cluster/CommandTests.java b/java/integTest/src/test/java/glide/cluster/CommandTests.java index 4c9be4f1ca..6af733d8dc 100644 --- a/java/integTest/src/test/java/glide/cluster/CommandTests.java +++ b/java/integTest/src/test/java/glide/cluster/CommandTests.java @@ -4,6 +4,8 @@ import static glide.TestConfiguration.CLUSTER_PORTS; import static glide.TestConfiguration.REDIS_VERSION; import static glide.TestUtilities.getFirstEntryFromMultiValue; +import static glide.TestUtilities.getValueFromInfo; +import static glide.api.BaseClient.OK; import static glide.api.models.commands.InfoOptions.Section.CLIENTS; import static glide.api.models.commands.InfoOptions.Section.CLUSTER; import static glide.api.models.commands.InfoOptions.Section.COMMANDSTATS; @@ -11,12 +13,14 @@ import static glide.api.models.commands.InfoOptions.Section.EVERYTHING; import static glide.api.models.commands.InfoOptions.Section.MEMORY; import static glide.api.models.commands.InfoOptions.Section.REPLICATION; +import static glide.api.models.commands.InfoOptions.Section.STATS; import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleRoute.ALL_NODES; import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleRoute.ALL_PRIMARIES; import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleRoute.RANDOM; import static glide.api.models.configuration.RequestRoutingConfiguration.SlotType.PRIMARY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import glide.api.RedisClusterClient; @@ -25,7 +29,9 @@ import glide.api.models.configuration.NodeAddress; import glide.api.models.configuration.RedisClusterClientConfiguration; import glide.api.models.configuration.RequestRoutingConfiguration.SlotKeyRoute; +import glide.api.models.exceptions.RequestException; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import lombok.SneakyThrows; import org.junit.jupiter.api.AfterAll; @@ -335,4 +341,29 @@ public void clientGetName_with_multi_node_route() { assertEquals("clientGetName_with_multi_node_route", getFirstEntryFromMultiValue(name)); } + + @Test + @SneakyThrows + public void config_reset_stat() { + var data = clusterClient.info(InfoOptions.builder().section(STATS).build()).get(); + String firstNodeInfo = getFirstEntryFromMultiValue(data); + int value_before = getValueFromInfo(firstNodeInfo, "total_net_input_bytes"); + + var result = clusterClient.configResetStat().get(); + assertEquals(OK, result); + + data = clusterClient.info(InfoOptions.builder().section(STATS).build()).get(); + firstNodeInfo = getFirstEntryFromMultiValue(data); + int value_after = getValueFromInfo(firstNodeInfo, "total_net_input_bytes"); + assertTrue(value_after < value_before); + } + + @Test + @SneakyThrows + public void config_rewrite_non_existent_config_file() { + // The setup for the Integration Tests server does not include a configuration file for Redis. + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> clusterClient.configRewrite().get()); + assertTrue(executionException.getCause() instanceof RequestException); + } } diff --git a/java/integTest/src/test/java/glide/standalone/CommandTests.java b/java/integTest/src/test/java/glide/standalone/CommandTests.java index 86382214bb..5ded6e00ce 100644 --- a/java/integTest/src/test/java/glide/standalone/CommandTests.java +++ b/java/integTest/src/test/java/glide/standalone/CommandTests.java @@ -3,11 +3,13 @@ import static glide.TestConfiguration.REDIS_VERSION; import static glide.TestConfiguration.STANDALONE_PORTS; +import static glide.TestUtilities.getValueFromInfo; import static glide.api.BaseClient.OK; import static glide.api.models.commands.InfoOptions.Section.CLUSTER; import static glide.api.models.commands.InfoOptions.Section.CPU; import static glide.api.models.commands.InfoOptions.Section.EVERYTHING; import static glide.api.models.commands.InfoOptions.Section.MEMORY; +import static glide.api.models.commands.InfoOptions.Section.STATS; import static glide.cluster.CommandTests.DEFAULT_INFO_SECTIONS; import static glide.cluster.CommandTests.EVERYTHING_INFO_SECTIONS; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -160,4 +162,27 @@ public void clientGetName() { assertEquals("clientGetName", name); } + + @Test + @SneakyThrows + public void config_reset_stat() { + String data = regularClient.info(InfoOptions.builder().section(STATS).build()).get(); + int value_before = getValueFromInfo(data, "total_net_input_bytes"); + + var result = regularClient.configResetStat().get(); + assertEquals(OK, result); + + data = regularClient.info(InfoOptions.builder().section(STATS).build()).get(); + int value_after = getValueFromInfo(data, "total_net_input_bytes"); + assertTrue(value_after < value_before); + } + + @Test + @SneakyThrows + public void config_rewrite_non_existent_config_file() { + // The setup for the Integration Tests server does not include a configuration file for Redis. + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> regularClient.configRewrite().get()); + assertTrue(executionException.getCause() instanceof RequestException); + } } From b4fded9c95ab3a26af534b0c7be58fba85e4ba92 Mon Sep 17 00:00:00 2001 From: Adan Wattad Date: Wed, 6 Mar 2024 10:29:54 +0200 Subject: [PATCH 55/65] Node: added echo command. (#1010) --- node/src/BaseClient.ts | 11 +++++++++++ node/src/Commands.ts | 7 +++++++ node/src/Transaction.ts | 12 ++++++++++++ node/tests/SharedTests.ts | 11 +++++++++++ node/tests/TestUtilities.ts | 2 ++ 5 files changed, 43 insertions(+) diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index a73c40f47e..ff86a54263 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -18,6 +18,7 @@ import { createDecr, createDecrBy, createDel, + createEcho, createExists, createExpire, createExpireAt, @@ -1138,6 +1139,16 @@ export class BaseClient { return this.createWritePromise(createZpopmax(key, count)); } + /** Echoes the provided `message` back. + * See https://redis.io/commands/echo for more details. + * + * @param message - The message to be echoed back. + * @returns The provided `message`. + */ + public echo(message: string): Promise { + return this.createWritePromise(createEcho(message)); + } + private readonly MAP_READ_FROM_STRATEGY: Record< ReadFrom, connection_request.ReadFrom diff --git a/node/src/Commands.ts b/node/src/Commands.ts index ffd24f4731..9b856cfdf3 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -863,3 +863,10 @@ export function createZpopmax(key: string, count?: number): redis_request.Comman const args: string[] = count == undefined ? [key] : [key, count.toString()]; return createCommand(RequestType.ZPopMax, args); } + +/** + * @internal + */ +export function createEcho(message: string): redis_request.Command { + return createCommand(RequestType.Echo, [message]); +} diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index cf6f3826b9..296f63c400 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -18,6 +18,7 @@ import { createDecr, createDecrBy, createDel, + createEcho, createExists, createExpire, createExpireAt, @@ -900,6 +901,17 @@ export class BaseTransaction> { return this.addAndReturn(createZpopmax(key, count)); } + /** Echoes the provided `message` back. + * See https://redis.io/commands/echo for more details. + * + * @param message - The message to be echoed back. + * + * Command Response - The provided `message`. + */ + public echo(message: string): T { + return this.addAndReturn(createEcho(message)); + } + /** Executes a single command, without checking inputs. Every part of the command, including subcommands, * should be added as a separate value in args. * diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index c4e5049df0..32d8f19bf4 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1532,6 +1532,17 @@ export function runBaseTests(config: { config.timeout ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `echo test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const message = uuidv4(); + expect(await client.echo(message)).toEqual(message); + }, protocol); + }, + config.timeout + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `strlen test_%p`, async (protocol) => { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index 70a0489a2d..2193767b2c 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -68,6 +68,8 @@ export function transactionTest( args.push("OK"); baseTransaction.type(key1); args.push("string"); + baseTransaction.echo(value); + args.push(value); baseTransaction.set(key2, "baz", { returnOldValue: true, }); From eaf76071a334f92714eb7e44b03370dfa14fcb14 Mon Sep 17 00:00:00 2001 From: Adan Wattad Date: Wed, 6 Mar 2024 11:59:48 +0200 Subject: [PATCH 56/65] Node: added hvals command. (#1022) --- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/socket_listener.rs | 1 + node/src/BaseClient.ts | 11 +++++++++++ node/src/Commands.ts | 7 +++++++ node/src/Transaction.ts | 12 +++++++++++ node/tests/SharedTests.ts | 22 +++++++++++++++++++++ node/tests/TestUtilities.ts | 2 ++ 7 files changed, 56 insertions(+) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index bfaf1bbe36..6b277f6f98 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -125,6 +125,7 @@ enum RequestType { XGroupDestroy = 81; HSetNX = 82; SIsMember = 83; + Hvals = 84; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index ba24a93a44..e09d05a37b 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -362,6 +362,7 @@ fn get_command(request: &Command) -> Option { RequestType::XTrim => Some(cmd("XTRIM")), RequestType::HSetNX => Some(cmd("HSETNX")), RequestType::SIsMember => Some(cmd("SISMEMBER")), + RequestType::Hvals => Some(cmd("HVALS")), } } diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index ff86a54263..b804501790 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -32,6 +32,7 @@ import { createHLen, createHMGet, createHSet, + createHvals, createIncr, createIncrBy, createIncrByFloat, @@ -632,6 +633,16 @@ export class BaseClient { return this.createWritePromise(createHLen(key)); } + /** Returns all values in the hash stored at key. + * See https://redis.io/commands/hvals/ for more details. + * + * @param key - The key of the hash. + * @returns a list of values in the hash, or an empty list when the key does not exist. + */ + public hvals(key: string): Promise { + return this.createWritePromise(createHvals(key)); + } + /** Inserts all the specified values at the head of the list stored at `key`. * `elements` are inserted one after the other to the head of the list, from the leftmost element to the rightmost element. * If `key` does not exist, it is created as empty list before performing the push operations. diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 9b856cfdf3..a7428736c0 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -592,6 +592,13 @@ export function createHLen(key: string): redis_request.Command { return createCommand(RequestType.HLen, [key]); } +/** + * @internal + */ +export function createHvals(key: string): redis_request.Command { + return createCommand(RequestType.Hvals, [key]); +} + /** * @internal */ diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 296f63c400..6a36f74c91 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -32,6 +32,7 @@ import { createHLen, createHMGet, createHSet, + createHvals, createIncr, createIncrBy, createIncrByFloat, @@ -426,6 +427,17 @@ export class BaseTransaction> { return this.addAndReturn(createHLen(key)); } + /** Returns all values in the hash stored at key. + * See https://redis.io/commands/hvals/ for more details. + * + * @param key - The key of the hash. + * + * Command Response - a list of values in the hash, or an empty list when the key does not exist. + */ + public hvals(key: string): T { + return this.addAndReturn(createHvals(key)); + } + /** Inserts all the specified values at the head of the list stored at `key`. * `elements` are inserted one after the other to the head of the list, from the leftmost element to the rightmost element. * If `key` does not exist, it is created as empty list before performing the push operations. diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 32d8f19bf4..e60b6b5bee 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -772,6 +772,28 @@ export function runBaseTests(config: { config.timeout ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `hvals test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key1 = uuidv4(); + const field1 = uuidv4(); + const field2 = uuidv4(); + const fieldValueMap = { + [field1]: "value1", + [field2]: "value2", + }; + + expect(await client.hset(key1, fieldValueMap)).toEqual(2); + expect(await client.hvals(key1)).toEqual(["value1", "value2"]); + expect(await client.hdel(key1, [field1])).toEqual(1); + expect(await client.hvals(key1)).toEqual(["value2"]); + expect(await client.hvals("nonExistingHash")).toEqual([]); + }, protocol); + }, + config.timeout + ); + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `lpush, lpop and lrange with existing and non existing key_%p`, async (protocol) => { diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index 2193767b2c..f468e83921 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -88,6 +88,8 @@ export function transactionTest( args.push(1); baseTransaction.hlen(key4); args.push(1); + baseTransaction.hvals(key4); + args.push([value]); baseTransaction.hget(key4, field); args.push(value); baseTransaction.hgetall(key4); From b12a10539368b8421732c31dcfff90430d9294d3 Mon Sep 17 00:00:00 2001 From: Shoham Elias Date: Wed, 6 Mar 2024 11:33:19 +0000 Subject: [PATCH 57/65] Python: fix expire, expireat, pexpire doc --- python/python/glide/async_commands/core.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 4b8efe787c..4913073a66 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -1050,7 +1050,7 @@ async def expire( Examples: >>> await client.expire("my_key", 60) - 1 # Indicates that a timeout of 60 seconds has been set for "my_key." + True # Indicates that a timeout of 60 seconds has been set for "my_key." """ args: List[str] = ( [key, str(seconds)] if option is None else [key, str(seconds), option.value] @@ -1080,7 +1080,7 @@ async def expireat( Examples: >>> await client.expireAt("my_key", 1672531200, ExpireOptions.HasNoExpiry) - 1 + True """ args = ( [key, str(unix_seconds)] @@ -1110,7 +1110,7 @@ async def pexpire( Examples: >>> await client.pexpire("my_key", 60000, ExpireOptions.HasNoExpiry) - 1 # Indicates that a timeout of 60,000 milliseconds has been set for "my_key." + True # Indicates that a timeout of 60,000 milliseconds has been set for "my_key." """ args = ( [key, str(milliseconds)] @@ -1142,7 +1142,7 @@ async def pexpireat( Examples: >>> await client.pexpireAt("my_key", 1672531200000, ExpireOptions.HasNoExpiry) - 1 + True """ args = ( [key, str(unix_milliseconds)] From 3ed858f87924067a01b8e37326f64a156ed750da Mon Sep 17 00:00:00 2001 From: Shoham Elias Date: Wed, 6 Mar 2024 11:33:41 +0000 Subject: [PATCH 58/65] Python: adds PTTL command --- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/socket_listener.rs | 1 + python/python/glide/async_commands/core.py | 25 +++++++++++++++++++ .../glide/async_commands/transaction.py | 16 ++++++++++++ python/python/tests/test_async_client.py | 19 ++++++++++++++ 5 files changed, 62 insertions(+) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 6b277f6f98..ceaa83c11a 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -126,6 +126,7 @@ enum RequestType { HSetNX = 82; SIsMember = 83; Hvals = 84; + PTTL = 85; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index e09d05a37b..dfea685be6 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -363,6 +363,7 @@ fn get_command(request: &Command) -> Option { RequestType::HSetNX => Some(cmd("HSETNX")), RequestType::SIsMember => Some(cmd("SISMEMBER")), RequestType::Hvals => Some(cmd("HVALS")), + RequestType::PTTL => Some(cmd("PTTL")), } } diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 4913073a66..956286b72e 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -1170,6 +1170,31 @@ async def ttl(self, key: str) -> int: """ return cast(int, await self._execute_command(RequestType.TTL, [key])) + async def pttl( + self, + key: str, + ) -> int: + """ + Returns the remaining time to live of `key` that has a timeout, in milliseconds. + See https://redis.io/commands/pttl for more details. + + Args: + key (str): The key to return its timeout. + + Returns: + int: TTL in milliseconds. -2 if `key` does not exist, -1 if `key` exists but has no associated expire. + + Examples: + >>> await client.pttl("my_key") + 5000 # Indicates that the key "my_key" has a remaining time to live of 5000 milliseconds. + >>> await client.pttl("non_existing_key") + -2 # Indicates that the key "non_existing_key" does not exist. + """ + return cast( + int, + await self._execute_command(RequestType.PTTL, [key]), + ) + async def echo(self, message: str) -> str: """ Echoes the provided `message` back. diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 58cfab4ac8..9cebba38d9 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -939,6 +939,22 @@ def ttl(self: TTransaction, key: str) -> TTransaction: """ return self.append_command(RequestType.TTL, [key]) + def pttl( + self: TTransaction, + key: str, + ) -> TTransaction: + """ + Returns the remaining time to live of `key` that has a timeout, in milliseconds. + See https://redis.io/commands/pttl for more details. + + Args: + key (str): The key to return its timeout. + + Commands Response: + int: TTL in milliseconds. -2 if `key` does not exist, -1 if `key` exists but has no associated expire. + """ + return self.append_command(RequestType.PTTL, [key]) + def echo(self: TTransaction, message: str) -> TTransaction: """ Echoes the provided `message` back. diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 217920710b..140437a185 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -1048,6 +1048,25 @@ async def test_expire_pexpire_expireAt_pexpireAt_ttl_non_existing_key( assert not await redis_client.pexpireat(key, int(time.time() * 1000) + 50000) assert await redis_client.ttl(key) == -2 + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_pttl(self, redis_client: TRedisClient): + key = get_random_string(10) + assert await redis_client.pttl(key) == -2 + current_time = int(time.time()) + + assert await redis_client.set(key, "value") == OK + assert await redis_client.pttl(key) == -1 + + assert await redis_client.expire(key, 10) + assert 0 < await redis_client.pttl(key) <= 10000 + + assert await redis_client.expireat(key, current_time + 20) + assert 0 < await redis_client.pttl(key) <= 20000 + + assert await redis_client.pexpireat(key, current_time * 1000 + 30000) + assert 0 < await redis_client.pttl(key) <= 30000 + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_zadd_zaddincr(self, redis_client: TRedisClient): From d179378e448fd28e69407738e017808adf442b5e Mon Sep 17 00:00:00 2001 From: ort-bot Date: Wed, 6 Mar 2024 00:19:11 +0000 Subject: [PATCH 59/65] Updated attribution files --- glide-core/THIRD_PARTY_LICENSES_RUST | 4 ++-- node/THIRD_PARTY_LICENSES_NODE | 6 +++--- python/THIRD_PARTY_LICENSES_PYTHON | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/glide-core/THIRD_PARTY_LICENSES_RUST b/glide-core/THIRD_PARTY_LICENSES_RUST index 6f16e9db54..96a819d93d 100644 --- a/glide-core/THIRD_PARTY_LICENSES_RUST +++ b/glide-core/THIRD_PARTY_LICENSES_RUST @@ -19337,7 +19337,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project:1.1.4 +Package: pin-project:1.1.5 The following copyrights and licenses were found in the source code of this package: @@ -19566,7 +19566,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-internal:1.1.4 +Package: pin-project-internal:1.1.5 The following copyrights and licenses were found in the source code of this package: diff --git a/node/THIRD_PARTY_LICENSES_NODE b/node/THIRD_PARTY_LICENSES_NODE index cd76823a39..86a5f9f5a8 100644 --- a/node/THIRD_PARTY_LICENSES_NODE +++ b/node/THIRD_PARTY_LICENSES_NODE @@ -15614,7 +15614,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: libloading:0.8.2 +Package: libloading:0.8.3 The following copyrights and licenses were found in the source code of this package: @@ -20046,7 +20046,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project:1.1.4 +Package: pin-project:1.1.5 The following copyrights and licenses were found in the source code of this package: @@ -20275,7 +20275,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-internal:1.1.4 +Package: pin-project-internal:1.1.5 The following copyrights and licenses were found in the source code of this package: diff --git a/python/THIRD_PARTY_LICENSES_PYTHON b/python/THIRD_PARTY_LICENSES_PYTHON index bd4f340c34..5d847a0c42 100644 --- a/python/THIRD_PARTY_LICENSES_PYTHON +++ b/python/THIRD_PARTY_LICENSES_PYTHON @@ -20028,7 +20028,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project:1.1.4 +Package: pin-project:1.1.5 The following copyrights and licenses were found in the source code of this package: @@ -20257,7 +20257,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- -Package: pin-project-internal:1.1.4 +Package: pin-project-internal:1.1.5 The following copyrights and licenses were found in the source code of this package: From 712c4ac244ce1795ac46429ce8a948be58786bd2 Mon Sep 17 00:00:00 2001 From: Adan Wattad Date: Wed, 6 Mar 2024 15:59:52 +0200 Subject: [PATCH 60/65] Node: added Pttl command. (#1082) --- CHANGELOG.md | 1 + node/src/BaseClient.ts | 11 +++++++++++ node/src/Commands.ts | 7 +++++++ node/src/Transaction.ts | 12 ++++++++++++ node/tests/SharedTests.ts | 29 +++++++++++++++++++++++++++++ 5 files changed, 60 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd21b1c526..da57d3e91b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * Node: Added LINDEX command ([#999](https://github.com/aws/glide-for-redis/pull/999)) * Python, Node: Added ZPOPMAX command ([#996](https://github.com/aws/glide-for-redis/pull/996), [#1009](https://github.com/aws/glide-for-redis/pull/1009)) * Python: Added ZRANGE command ([#906](https://github.com/aws/glide-for-redis/pull/906)) +* Python, Node: Added PTTL command ([#1036](https://github.com/aws/glide-for-redis/pull/1036), [#1082](https://github.com/aws/glide-for-redis/pull/1082)) #### Features diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index b804501790..61454210d0 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -47,6 +47,7 @@ import { createMSet, createPExpire, createPExpireAt, + createPttl, createRPop, createRPush, createSAdd, @@ -1160,6 +1161,16 @@ export class BaseClient { return this.createWritePromise(createEcho(message)); } + /** Returns the remaining time to live of `key` that has a timeout, in milliseconds. + * See https://redis.io/commands/pttl for more details. + * + * @param key - The key to return its timeout. + * @returns TTL in milliseconds. -2 if `key` does not exist, -1 if `key` exists but has no associated expire. + */ + public pttl(key: string): Promise { + return this.createWritePromise(createPttl(key)); + } + private readonly MAP_READ_FROM_STRATEGY: Record< ReadFrom, connection_request.ReadFrom diff --git a/node/src/Commands.ts b/node/src/Commands.ts index a7428736c0..b70685b001 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -877,3 +877,10 @@ export function createZpopmax(key: string, count?: number): redis_request.Comman export function createEcho(message: string): redis_request.Command { return createCommand(RequestType.Echo, [message]); } + +/** + * @internal + */ +export function createPttl(key: string): redis_request.Command { + return createCommand(RequestType.PTTL, [key]); +} diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index 6a36f74c91..de8a087703 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -49,6 +49,7 @@ import { createPExpire, createPExpireAt, createPing, + createPttl, createRPop, createRPush, createSAdd, @@ -924,6 +925,17 @@ export class BaseTransaction> { return this.addAndReturn(createEcho(message)); } + /** Returns the remaining time to live of `key` that has a timeout, in milliseconds. + * See https://redis.io/commands/pttl for more details. + * + * @param key - The key to return its timeout. + * + * Command Response - TTL in milliseconds. -2 if `key` does not exist, -1 if `key` exists but has no associated expire. + */ + public pttl(key: string): T { + return this.addAndReturn(createPttl(key)); + } + /** Executes a single command, without checking inputs. Every part of the command, including subcommands, * should be added as a separate value in args. * diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index e60b6b5bee..3920334eb4 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1651,6 +1651,35 @@ export function runBaseTests(config: { }, config.timeout ); + + it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( + `Pttl test_%p`, + async (protocol) => { + await runTest(async (client: BaseClient) => { + const key = uuidv4(); + expect(await client.pttl(key)).toEqual(-2); + + expect(await client.set(key, "value")).toEqual("OK"); + expect(await client.pttl(key)).toEqual(-1); + + expect(await client.expire(key, 10)).toEqual(true); + let result = await client.pttl(key); + expect(result).toBeGreaterThan(0); + expect(result).toBeLessThanOrEqual(10000); + + expect(await client.expireAt(key, Math.floor(Date.now() / 1000) + 20)).toEqual(true); + result = await client.pttl(key); + expect(result).toBeGreaterThan(0); + expect(result).toBeLessThanOrEqual(20000); + + expect(await client.pexpireAt(key, Date.now() + 30000)).toEqual(true); + result = await client.pttl(key); + expect(result).toBeGreaterThan(0); + expect(result).toBeLessThanOrEqual(30000); + }, protocol); + }, + config.timeout + ); } export function runCommonTests(config: { From b6892bcb806b9f97a7f29b29ef12c97255581a1f Mon Sep 17 00:00:00 2001 From: Avi Fenesh <55848801+avifenesh@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:39:15 +0200 Subject: [PATCH 61/65] Adjust npm-cd to fit prettier structure (#1081) adjust npm-cd to fit prettier structure --- .github/workflows/npm-cd.yml | 2 +- node/package.json | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/npm-cd.yml b/.github/workflows/npm-cd.yml index 68dd3b3b82..d768e542e9 100644 --- a/.github/workflows/npm-cd.yml +++ b/.github/workflows/npm-cd.yml @@ -112,7 +112,7 @@ jobs: run: | # Remove the "cpu" and "os" fileds so the base package would be able to install it on ubuntu SED_FOR_MACOS=`if [[ "${{ matrix.build.OS }}" =~ .*"macos".* ]]; then echo "''"; fi` - sed -i $SED_FOR_MACOS '/"cpu":/d' ./package.json && sed -i $SED_FOR_MACOS '/"os":/d' ./package.json + sed -i $SED_FOR_MACOS '/"\/\/\/cpu": \[/,/]/d' ./package.json && sed -i $SED_FOR_MACOS '/"\/\/\/os": \[/,/]/d' ./package.json mkdir -p bin npm pack --pack-destination ./bin ls ./bin diff --git a/node/package.json b/node/package.json index b943253f51..d3fc6932d1 100644 --- a/node/package.json +++ b/node/package.json @@ -12,6 +12,7 @@ "glide-rs": "file:rust-client", "long": "^5.2.3", "npmignore": "^0.3.0", + "prettier": "^3.2.5", "protobufjs": "^7.2.2" }, "bundleDependencies": [ @@ -32,7 +33,9 @@ "test": "jest --verbose --runInBand --testPathIgnorePatterns='RedisModules'", "lint": "eslint -f unix \"src/**/*.{ts,tsx}\"", "prepack": "npmignore --auto", - "test-modules": "jest --verbose --runInBand 'tests/RedisModules.test.ts'" + "test-modules": "jest --verbose --runInBand 'tests/RedisModules.test.ts'", + "prettier:check:ci": "./node_modules/.bin/prettier --check . --ignore-unknown '!**/*.{js,d.ts}'", + "prettier:format": "./node_modules/.bin/prettier --write . --ignore-unknown '!**/*.{js,d.ts}'" }, "devDependencies": { "@babel/preset-env": "^7.20.2", @@ -70,8 +73,12 @@ "//": [ "The fields below have been commented out and are only necessary for publishing the package." ], - "///cpu": ["${node_arch}"], - "///os": ["${node_os}"], + "///cpu": [ + "${node_arch}" + ], + "///os": [ + "${node_os}" + ], "///name": "${scope}${pkg_name}", "///version": "${package_version}" } From ffb34fc4cee287ce83802e82a89fc26193ce6acd Mon Sep 17 00:00:00 2001 From: Shachar Langbeheim Date: Wed, 6 Mar 2024 18:59:03 +0200 Subject: [PATCH 62/65] Extract error sorting code to module. (#1077) * Extract error sorting code to module. This will allow FFI clients to use the same logic. * ++redis-rs * fix comments. --- glide-core/src/client/value_conversion.rs | 27 ++++++++---------- glide-core/src/errors.rs | 34 +++++++++++++++++++++++ glide-core/src/lib.rs | 1 + glide-core/src/socket_listener.rs | 33 +++++++++------------- glide-core/tests/test_client.rs | 2 +- java/src/lib.rs | 2 +- node/rust-client/src/lib.rs | 4 +-- python/src/lib.rs | 2 +- submodules/redis-rs | 2 +- 9 files changed, 64 insertions(+), 43 deletions(-) create mode 100644 glide-core/src/errors.rs diff --git a/glide-core/src/client/value_conversion.rs b/glide-core/src/client/value_conversion.rs index 73a886e994..9538791422 100644 --- a/glide-core/src/client/value_conversion.rs +++ b/glide-core/src/client/value_conversion.rs @@ -51,7 +51,7 @@ pub(crate) fn convert_to_expected_type( match inner_value { Value::BulkString(_) => Ok(( key_str, - Value::Double(from_owned_redis_value::(inner_value)?.into()), + Value::Double(from_owned_redis_value::(inner_value)?), )), Value::Double(_) => Ok((key_str, inner_value)), _ => Err(( @@ -89,13 +89,11 @@ pub(crate) fn convert_to_expected_type( ) .into()), }, - ExpectedReturnType::Double => { - Ok(Value::Double(from_owned_redis_value::(value)?.into())) - } + ExpectedReturnType::Double => Ok(Value::Double(from_owned_redis_value::(value)?)), ExpectedReturnType::Boolean => Ok(Value::Boolean(from_owned_redis_value::(value)?)), ExpectedReturnType::DoubleOrNull => match value { Value::Nil => Ok(value), - _ => Ok(Value::Double(from_owned_redis_value::(value)?.into())), + _ => Ok(Value::Double(from_owned_redis_value::(value)?)), }, ExpectedReturnType::ZrankReturnType => match value { Value::Nil => Ok(value), @@ -306,10 +304,7 @@ mod tests { Value::BulkString(b"key2".to_vec()), Value::BulkString(b"20.8".to_vec()), ), - ( - Value::Double(20.5.into()), - Value::BulkString(b"30.2".to_vec()), - ), + (Value::Double(20.5), Value::BulkString(b"30.2".to_vec())), ]; let converted_map = convert_to_expected_type( @@ -328,15 +323,15 @@ mod tests { let (key, value) = &converted_map[0]; assert_eq!(*key, Value::BulkString(b"key1".to_vec())); - assert_eq!(*value, Value::Double(10.5.into())); + assert_eq!(*value, Value::Double(10.5)); let (key, value) = &converted_map[1]; assert_eq!(*key, Value::BulkString(b"key2".to_vec())); - assert_eq!(*value, Value::Double(20.8.into())); + assert_eq!(*value, Value::Double(20.8)); let (key, value) = &converted_map[2]; assert_eq!(*key, Value::BulkString(b"20.5".to_vec())); - assert_eq!(*value, Value::Double(30.2.into())); + assert_eq!(*value, Value::Double(30.2)); let array_of_arrays = vec![ Value::Array(vec![ @@ -345,7 +340,7 @@ mod tests { ]), Value::Array(vec![ Value::BulkString(b"key2".to_vec()), - Value::Double(20.5.into()), + Value::Double(20.5), ]), ]; @@ -365,11 +360,11 @@ mod tests { let (key, value) = &converted_map[0]; assert_eq!(*key, Value::BulkString(b"key1".to_vec())); - assert_eq!(*value, Value::Double(10.5.into())); + assert_eq!(*value, Value::Double(10.5)); let (key, value) = &converted_map[1]; assert_eq!(*key, Value::BulkString(b"key2".to_vec())); - assert_eq!(*value, Value::Double(20.5.into())); + assert_eq!(*value, Value::Double(20.5)); let array_of_arrays_err: Vec = vec![Value::Array(vec![ Value::BulkString(b"key".to_vec()), @@ -410,7 +405,7 @@ mod tests { assert_eq!(array_result.len(), 2); assert_eq!(array_result[0], Value::BulkString(b"key".to_vec())); - assert_eq!(array_result[1], Value::Double(20.5.into())); + assert_eq!(array_result[1], Value::Double(20.5)); let array_err = vec![Value::BulkString(b"key".to_vec())]; assert!(convert_to_expected_type( diff --git a/glide-core/src/errors.rs b/glide-core/src/errors.rs new file mode 100644 index 0000000000..1c05aad84b --- /dev/null +++ b/glide-core/src/errors.rs @@ -0,0 +1,34 @@ +/* + * Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 + */ + +use redis::RedisError; + +#[repr(C)] +pub enum RequestErrorType { + Unspecified = 0, + ExecAbort = 1, + Timeout = 2, + Disconnect = 3, +} + +pub fn error_type(error: &RedisError) -> RequestErrorType { + if error.is_timeout() { + RequestErrorType::Timeout + } else if error.is_unrecoverable_error() { + RequestErrorType::Disconnect + } else if matches!(error.kind(), redis::ErrorKind::ExecAbortError) { + RequestErrorType::ExecAbort + } else { + RequestErrorType::Unspecified + } +} + +pub fn error_message(error: &RedisError) -> String { + let error_message = error.to_string(); + if matches!(error_type(error), RequestErrorType::Disconnect) { + format!("Received connection error `{error_message}`. Will attempt to reconnect") + } else { + error_message + } +} diff --git a/glide-core/src/lib.rs b/glide-core/src/lib.rs index 8fe89abd35..aa25cb89b1 100644 --- a/glide-core/src/lib.rs +++ b/glide-core/src/lib.rs @@ -8,4 +8,5 @@ mod retry_strategies; pub mod rotating_buffer; mod socket_listener; pub use socket_listener::*; +pub mod errors; pub mod scripts_container; diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index dfea685be6..21994da38c 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -4,6 +4,7 @@ use super::rotating_buffer::RotatingBuffer; use crate::client::Client; use crate::connection_request::ConnectionRequest; +use crate::errors::{error_message, error_type, RequestErrorType}; use crate::redis_request::{ command, redis_request, Command, RedisRequest, RequestType, Routes, ScriptInvocation, SlotTypes, Transaction, @@ -217,28 +218,20 @@ async fn write_result( Some(response::response::Value::RequestError(request_error)) } Err(ClienUsageError::Redis(err)) => { - let error_message = err.to_string(); + let error_message = error_message(&err); log_warn("received error", error_message.as_str()); log_debug("received error", format!("for callback {}", callback_index)); - let mut request_error = response::RequestError::default(); - if err.is_connection_dropped() { - request_error.type_ = response::RequestErrorType::Disconnect.into(); - request_error.message = format!( - "Received connection error `{error_message}`. Will attempt to reconnect" - ) - .into(); - } else if err.is_timeout() { - request_error.type_ = response::RequestErrorType::Timeout.into(); - request_error.message = error_message.into(); - } else { - request_error.type_ = match err.kind() { - redis::ErrorKind::ExecAbortError => { - response::RequestErrorType::ExecAbort.into() - } - _ => response::RequestErrorType::Unspecified.into(), - }; - request_error.message = error_message.into(); - } + let request_error = response::RequestError { + type_: match error_type(&err) { + RequestErrorType::Unspecified => response::RequestErrorType::Unspecified, + RequestErrorType::ExecAbort => response::RequestErrorType::ExecAbort, + RequestErrorType::Timeout => response::RequestErrorType::Timeout, + RequestErrorType::Disconnect => response::RequestErrorType::Disconnect, + } + .into(), + message: error_message.into(), + ..Default::default() + }; Some(response::response::Value::RequestError(request_error)) } }; diff --git a/glide-core/tests/test_client.rs b/glide-core/tests/test_client.rs index 42cfa9a13c..6aa1c5c040 100644 --- a/glide-core/tests/test_client.rs +++ b/glide-core/tests/test_client.rs @@ -458,7 +458,7 @@ pub(crate) mod shared_client_tests { Value::Boolean(true), Value::Int(1), Value::Okay, - Value::Double(0.5.into()), + Value::Double(0.5), Value::Int(1), ]),) ); diff --git a/java/src/lib.rs b/java/src/lib.rs index cb381b0ebb..32b88969b2 100644 --- a/java/src/lib.rs +++ b/java/src/lib.rs @@ -62,7 +62,7 @@ fn redis_value_to_java<'local>(env: &mut JNIEnv<'local>, val: Value) -> JObject< hashmap } Value::Double(float) => env - .new_object("java/lang/Double", "(D)V", &[float.into_inner().into()]) + .new_object("java/lang/Double", "(D)V", &[float.into()]) .unwrap(), Value::Boolean(bool) => env .new_object("java/lang/Boolean", "(Z)V", &[bool.into()]) diff --git a/node/rust-client/src/lib.rs b/node/rust-client/src/lib.rs index 78d7e7196b..403fa6b200 100644 --- a/node/rust-client/src/lib.rs +++ b/node/rust-client/src/lib.rs @@ -184,9 +184,7 @@ fn redis_value_to_js(val: Value, js_env: Env) -> Result { } Ok(obj.into_unknown()) } - Value::Double(float) => js_env - .create_double(float.into()) - .map(|val| val.into_unknown()), + Value::Double(float) => js_env.create_double(float).map(|val| val.into_unknown()), Value::Boolean(bool) => js_env.get_boolean(bool).map(|val| val.into_unknown()), // format is ignored, as per the RESP3 recommendations - // "Normal client libraries may ignore completely the difference between this" diff --git a/python/src/lib.rs b/python/src/lib.rs index fd05126c17..3b07eb93c3 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -139,7 +139,7 @@ fn glide(_py: Python, m: &PyModule) -> PyResult<()> { let set = PySet::new(py, set.iter())?; Ok(set.into_py(py)) } - Value::Double(double) => Ok(PyFloat::new(py, double.into()).into_py(py)), + Value::Double(double) => Ok(PyFloat::new(py, double).into_py(py)), Value::Boolean(boolean) => Ok(PyBool::new(py, boolean).into_py(py)), Value::VerbatimString { format: _, text } => Ok(text.into_py(py)), Value::BigNumber(bigint) => Ok(bigint.into_py(py)), diff --git a/submodules/redis-rs b/submodules/redis-rs index b974902c91..7f6e4fd68c 160000 --- a/submodules/redis-rs +++ b/submodules/redis-rs @@ -1 +1 @@ -Subproject commit b974902c9137a9d69a6db07f6f00412f4c4680b7 +Subproject commit 7f6e4fd68cf688b75e59e10e23e93433123f8da8 From 0759a2db58634bc1a1e96528e15dc72017f8b726 Mon Sep 17 00:00:00 2001 From: Aaron <69273634+aaron-congo@users.noreply.github.com> Date: Wed, 6 Mar 2024 09:10:01 -0800 Subject: [PATCH 63/65] Go: Implement Redis client configuration logic (#1052) --- go/api/config.go | 309 +++++++++++++++++++++++++++++++++++++++++ go/api/config_test.go | 129 +++++++++++++++++ go/glide/glide.go | 1 - go/go.mod | 15 +- go/go.sum | 24 +++- go/tests/glide_test.go | 13 -- 6 files changed, 474 insertions(+), 17 deletions(-) create mode 100644 go/api/config.go create mode 100644 go/api/config_test.go delete mode 100644 go/glide/glide.go delete mode 100644 go/tests/glide_test.go diff --git a/go/api/config.go b/go/api/config.go new file mode 100644 index 0000000000..9d2417b429 --- /dev/null +++ b/go/api/config.go @@ -0,0 +1,309 @@ +// Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 + +package api + +import "github.com/aws/glide-for-redis/go/glide/protobuf" + +const ( + defaultHost = "localhost" + defaultPort = 6379 +) + +// NodeAddress represents the host address and port of a node in the cluster. +type NodeAddress struct { + Host string // If not supplied, "localhost" will be used. + Port int // If not supplied, 6379 will be used. +} + +func (addr *NodeAddress) toProtobuf() *protobuf.NodeAddress { + if addr.Host == "" { + addr.Host = defaultHost + } + + if addr.Port == 0 { + addr.Port = defaultPort + } + + return &protobuf.NodeAddress{Host: addr.Host, Port: uint32(addr.Port)} +} + +// RedisCredentials represents the credentials for connecting to a Redis server. +type RedisCredentials struct { + // The username that will be used for authenticating connections to the Redis servers. If not supplied, "default" + // will be used. + username string + // The password that will be used for authenticating connections to the Redis servers. + password string +} + +// NewRedisCredentials returns a [RedisCredentials] struct with the given username and password. +func NewRedisCredentials(username string, password string) *RedisCredentials { + return &RedisCredentials{username, password} +} + +// NewRedisCredentialsWithDefaultUsername returns a [RedisCredentials] struct with a default username of "default" and the +// given password. +func NewRedisCredentialsWithDefaultUsername(password string) *RedisCredentials { + return &RedisCredentials{password: password} +} + +func (creds *RedisCredentials) toProtobuf() *protobuf.AuthenticationInfo { + return &protobuf.AuthenticationInfo{Username: creds.username, Password: creds.password} +} + +// ReadFrom represents the client's read from strategy. +type ReadFrom int + +const ( + // Primary - Always get from primary, in order to get the freshest data. + Primary ReadFrom = iota + // PreferReplica - Spread the requests between all replicas in a round-robin manner. If no replica is available, route the + // requests to the primary. + PreferReplica +) + +func mapReadFrom(readFrom ReadFrom) protobuf.ReadFrom { + if readFrom == PreferReplica { + return protobuf.ReadFrom_PreferReplica + } + + return protobuf.ReadFrom_Primary +} + +type baseClientConfiguration struct { + addresses []NodeAddress + useTLS bool + credentials *RedisCredentials + readFrom ReadFrom + requestTimeout int + clientName string +} + +func (config *baseClientConfiguration) toProtobuf() *protobuf.ConnectionRequest { + request := protobuf.ConnectionRequest{} + for _, address := range config.addresses { + request.Addresses = append(request.Addresses, address.toProtobuf()) + } + + if config.useTLS { + request.TlsMode = protobuf.TlsMode_SecureTls + } else { + request.TlsMode = protobuf.TlsMode_NoTls + } + + if config.credentials != nil { + request.AuthenticationInfo = config.credentials.toProtobuf() + } + + request.ReadFrom = mapReadFrom(config.readFrom) + if config.requestTimeout != 0 { + request.RequestTimeout = uint32(config.requestTimeout) + } + + if config.clientName != "" { + request.ClientName = config.clientName + } + + return &request +} + +// BackoffStrategy represents the strategy used to determine how and when to reconnect, in case of connection failures. The +// time between attempts grows exponentially, to the formula: +// +// rand(0 ... factor * (exponentBase ^ N)) +// +// where N is the number of failed attempts. +// +// Once the maximum value is reached, that will remain the time between retry attempts until a reconnect attempt is successful. +// The client will attempt to reconnect indefinitely. +type BackoffStrategy struct { + // Number of retry attempts that the client should perform when disconnected from the server, where the time + // between retries increases. Once the retries have reached the maximum value, the time between retries will remain + // constant until a reconnect attempt is successful. + numOfRetries int + // The multiplier that will be applied to the waiting time between each retry. + factor int + // The exponent base configured for the strategy. + exponentBase int +} + +// NewBackoffStrategy returns a [BackoffStrategy] with the given configuration parameters. +func NewBackoffStrategy(numOfRetries int, factor int, exponentBase int) *BackoffStrategy { + return &BackoffStrategy{numOfRetries, factor, exponentBase} +} + +func (strategy *BackoffStrategy) toProtobuf() *protobuf.ConnectionRetryStrategy { + return &protobuf.ConnectionRetryStrategy{ + NumberOfRetries: uint32(strategy.numOfRetries), + Factor: uint32(strategy.factor), + ExponentBase: uint32(strategy.exponentBase), + } +} + +// RedisClientConfiguration represents the configuration settings for a Standalone Redis client. baseClientConfiguration is an +// embedded struct that contains shared settings for standalone and cluster clients. +type RedisClientConfiguration struct { + baseClientConfiguration + reconnectStrategy *BackoffStrategy + databaseId int +} + +// NewRedisClientConfiguration returns a [RedisClientConfiguration] with default configuration settings. For further +// configuration, use the [RedisClientConfiguration] With* methods. +func NewRedisClientConfiguration() *RedisClientConfiguration { + return &RedisClientConfiguration{} +} + +func (config *RedisClientConfiguration) toProtobuf() *protobuf.ConnectionRequest { + request := config.baseClientConfiguration.toProtobuf() + request.ClusterModeEnabled = false + if config.reconnectStrategy != nil { + request.ConnectionRetryStrategy = config.reconnectStrategy.toProtobuf() + } + + if config.databaseId != 0 { + request.DatabaseId = uint32(config.databaseId) + } + + return request +} + +// WithAddress adds an address for a known node in the cluster to this configuration's list of addresses. WithAddress can be +// called multiple times to add multiple addresses to the list. If the server is in cluster mode the list can be partial, as +// the client will attempt to map out the cluster and find all nodes. If the server is in standalone mode, only nodes whose +// addresses were provided will be used by the client. For example: +// +// config := NewRedisClientConfiguration(). +// WithAddress(&NodeAddress{ +// Host: "sample-address-0001.use1.cache.amazonaws.com", Port: 6379}). +// WithAddress(&NodeAddress{ +// Host: "sample-address-0002.use1.cache.amazonaws.com", Port: 6379}) +func (config *RedisClientConfiguration) WithAddress(address *NodeAddress) *RedisClientConfiguration { + config.addresses = append(config.addresses, *address) + return config +} + +// WithUseTLS configures the TLS settings for this configuration. Set to true if communication with the cluster should use +// Transport Level Security. This setting should match the TLS configuration of the server/cluster, otherwise the connection +// attempt will fail. +func (config *RedisClientConfiguration) WithUseTLS(useTLS bool) *RedisClientConfiguration { + config.useTLS = useTLS + return config +} + +// WithCredentials sets the credentials for the authentication process. If none are set, the client will not authenticate +// itself with the server. +func (config *RedisClientConfiguration) WithCredentials(credentials *RedisCredentials) *RedisClientConfiguration { + config.credentials = credentials + return config +} + +// WithReadFrom sets the client's [ReadFrom] strategy. If not set, [Primary] will be used. +func (config *RedisClientConfiguration) WithReadFrom(readFrom ReadFrom) *RedisClientConfiguration { + config.readFrom = readFrom + return config +} + +// WithRequestTimeout sets the duration in milliseconds that the client should wait for a request to complete. This duration +// encompasses sending the request, awaiting for a response from the server, and any required reconnections or retries. If the +// specified timeout is exceeded for a pending request, it will result in a timeout error. If not set, a default value will be +// used. +func (config *RedisClientConfiguration) WithRequestTimeout(requestTimeout int) *RedisClientConfiguration { + config.requestTimeout = requestTimeout + return config +} + +// WithClientName sets the client name to be used for the client. Will be used with CLIENT SETNAME command during connection +// establishment. +func (config *RedisClientConfiguration) WithClientName(clientName string) *RedisClientConfiguration { + config.clientName = clientName + return config +} + +// WithReconnectStrategy sets the [BackoffStrategy] used to determine how and when to reconnect, in case of connection +// failures. If not set, a default backoff strategy will be used. +func (config *RedisClientConfiguration) WithReconnectStrategy(strategy *BackoffStrategy) *RedisClientConfiguration { + config.reconnectStrategy = strategy + return config +} + +// WithDatabaseId sets the index of the logical database to connect to. +func (config *RedisClientConfiguration) WithDatabaseId(id int) *RedisClientConfiguration { + config.databaseId = id + return config +} + +// RedisClusterClientConfiguration represents the configuration settings for a Cluster Redis client. +// Note: Currently, the reconnection strategy in cluster mode is not configurable, and exponential backoff with fixed values is +// used. +type RedisClusterClientConfiguration struct { + baseClientConfiguration +} + +// NewRedisClusterClientConfiguration returns a [RedisClusterClientConfiguration] with default configuration settings. For +// further configuration, use the [RedisClientConfiguration] With* methods. +func NewRedisClusterClientConfiguration() *RedisClusterClientConfiguration { + return &RedisClusterClientConfiguration{ + baseClientConfiguration: baseClientConfiguration{}, + } +} + +func (config *RedisClusterClientConfiguration) toProtobuf() *protobuf.ConnectionRequest { + request := config.baseClientConfiguration.toProtobuf() + request.ClusterModeEnabled = true + return request +} + +// WithAddress adds an address for a known node in the cluster to this configuration's list of addresses. WithAddress can be +// called multiple times to add multiple addresses to the list. If the server is in cluster mode the list can be partial, as +// the client will attempt to map out the cluster and find all nodes. If the server is in standalone mode, only nodes whose +// addresses were provided will be used by the client. For example: +// +// config := NewRedisClusterClientConfiguration(). +// WithAddress(&NodeAddress{ +// Host: "sample-address-0001.use1.cache.amazonaws.com", Port: 6379}). +// WithAddress(&NodeAddress{ +// Host: "sample-address-0002.use1.cache.amazonaws.com", Port: 6379}) +func (config *RedisClusterClientConfiguration) WithAddress(address *NodeAddress) *RedisClusterClientConfiguration { + config.addresses = append(config.addresses, *address) + return config +} + +// WithUseTLS configures the TLS settings for this configuration. Set to true if communication with the cluster should use +// Transport Level Security. This setting should match the TLS configuration of the server/cluster, otherwise the connection +// attempt will fail. +func (config *RedisClusterClientConfiguration) WithUseTLS(useTLS bool) *RedisClusterClientConfiguration { + config.useTLS = useTLS + return config +} + +// WithCredentials sets the credentials for the authentication process. If none are set, the client will not authenticate +// itself with the server. +func (config *RedisClusterClientConfiguration) WithCredentials( + credentials *RedisCredentials, +) *RedisClusterClientConfiguration { + config.credentials = credentials + return config +} + +// WithReadFrom sets the client's [ReadFrom] strategy. If not set, [Primary] will be used. +func (config *RedisClusterClientConfiguration) WithReadFrom(readFrom ReadFrom) *RedisClusterClientConfiguration { + config.readFrom = readFrom + return config +} + +// WithRequestTimeout sets the duration in milliseconds that the client should wait for a request to complete. This duration +// encompasses sending the request, awaiting for a response from the server, and any required reconnections or retries. If the +// specified timeout is exceeded for a pending request, it will result in a timeout error. If not set, a default value will be +// used. +func (config *RedisClusterClientConfiguration) WithRequestTimeout(requestTimeout int) *RedisClusterClientConfiguration { + config.requestTimeout = requestTimeout + return config +} + +// WithClientName sets the client name to be used for the client. Will be used with CLIENT SETNAME command during connection +// establishment. +func (config *RedisClusterClientConfiguration) WithClientName(clientName string) *RedisClusterClientConfiguration { + config.clientName = clientName + return config +} diff --git a/go/api/config_test.go b/go/api/config_test.go new file mode 100644 index 0000000000..53a18e5308 --- /dev/null +++ b/go/api/config_test.go @@ -0,0 +1,129 @@ +// Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 + +package api + +import ( + "fmt" + "testing" + + "github.com/aws/glide-for-redis/go/glide/protobuf" + "github.com/stretchr/testify/assert" +) + +func TestDefaultStandaloneConfig(t *testing.T) { + config := NewRedisClientConfiguration() + expected := &protobuf.ConnectionRequest{ + TlsMode: protobuf.TlsMode_NoTls, + ClusterModeEnabled: false, + ReadFrom: protobuf.ReadFrom_Primary, + } + + result := config.toProtobuf() + + assert.Equal(t, expected, result) +} + +func TestDefaultClusterConfig(t *testing.T) { + config := NewRedisClusterClientConfiguration() + expected := &protobuf.ConnectionRequest{ + TlsMode: protobuf.TlsMode_NoTls, + ClusterModeEnabled: true, + ReadFrom: protobuf.ReadFrom_Primary, + } + + result := config.toProtobuf() + + assert.Equal(t, expected, result) +} + +func TestConfig_allFieldsSet(t *testing.T) { + hosts := []string{"host1", "host2"} + ports := []int{1234, 5678} + username := "username" + password := "password" + timeout := 3 + clientName := "client name" + retries, factor, base := 5, 10, 50 + databaseId := 1 + + config := NewRedisClientConfiguration(). + WithUseTLS(true). + WithReadFrom(PreferReplica). + WithCredentials(NewRedisCredentials(username, password)). + WithRequestTimeout(timeout). + WithClientName(clientName). + WithReconnectStrategy(NewBackoffStrategy(retries, factor, base)). + WithDatabaseId(databaseId) + + expected := &protobuf.ConnectionRequest{ + TlsMode: protobuf.TlsMode_SecureTls, + ReadFrom: protobuf.ReadFrom_PreferReplica, + ClusterModeEnabled: false, + AuthenticationInfo: &protobuf.AuthenticationInfo{Username: username, Password: password}, + RequestTimeout: uint32(timeout), + ClientName: clientName, + ConnectionRetryStrategy: &protobuf.ConnectionRetryStrategy{ + NumberOfRetries: uint32(retries), + Factor: uint32(factor), + ExponentBase: uint32(base), + }, + DatabaseId: uint32(databaseId), + } + + assert.Equal(t, len(hosts), len(ports)) + for i := 0; i < len(hosts); i++ { + config.WithAddress(&NodeAddress{hosts[i], ports[i]}) + expected.Addresses = append( + expected.Addresses, + &protobuf.NodeAddress{Host: hosts[i], Port: uint32(ports[i])}, + ) + } + + result := config.toProtobuf() + + assert.Equal(t, expected, result) +} + +func TestNodeAddress(t *testing.T) { + parameters := []struct { + input NodeAddress + expected *protobuf.NodeAddress + }{ + {NodeAddress{}, &protobuf.NodeAddress{Host: defaultHost, Port: defaultPort}}, + {NodeAddress{Host: "host"}, &protobuf.NodeAddress{Host: "host", Port: defaultPort}}, + {NodeAddress{Port: 1234}, &protobuf.NodeAddress{Host: defaultHost, Port: 1234}}, + {NodeAddress{"host", 1234}, &protobuf.NodeAddress{Host: "host", Port: 1234}}, + } + + for i, parameter := range parameters { + t.Run(fmt.Sprintf("Testing [%v]", i), func(t *testing.T) { + result := parameter.input.toProtobuf() + + assert.Equal(t, parameter.expected, result) + }) + } +} + +func TestRedisCredentials(t *testing.T) { + parameters := []struct { + input *RedisCredentials + expected *protobuf.AuthenticationInfo + }{ + { + NewRedisCredentials("username", "password"), + &protobuf.AuthenticationInfo{Username: "username", Password: "password"}, + }, + { + NewRedisCredentialsWithDefaultUsername("password"), + &protobuf.AuthenticationInfo{Password: "password"}, + }, + } + + for i, parameter := range parameters { + t.Run(fmt.Sprintf("Testing [%v]", i), func(t *testing.T) { + result := parameter.input.toProtobuf() + + assert.Equal(t, parameter.expected, result) + }) + } +} diff --git a/go/glide/glide.go b/go/glide/glide.go deleted file mode 100644 index 767864d035..0000000000 --- a/go/glide/glide.go +++ /dev/null @@ -1 +0,0 @@ -package glide diff --git a/go/go.mod b/go/go.mod index 50091801de..29fff40d72 100644 --- a/go/go.mod +++ b/go/go.mod @@ -2,4 +2,17 @@ module github.com/aws/glide-for-redis/go/glide go 1.18 -require google.golang.org/protobuf v1.32.0 +require ( + github.com/stretchr/testify v1.8.4 + google.golang.org/protobuf v1.32.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go/go.sum b/go/go.sum index b010a4f69c..22f5ed3148 100644 --- a/go/go.sum +++ b/go/go.sum @@ -1,4 +1,24 @@ -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go/tests/glide_test.go b/go/tests/glide_test.go deleted file mode 100644 index 32103c6fc5..0000000000 --- a/go/tests/glide_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package tests - -import ( - "testing" -) - -// TODO: Replace this test with real tests when glide client implementation is started -func TestArbitraryLogic(t *testing.T) { - someVar := true - if !someVar { - t.Fatalf("Expected someVar to be true, but was false.") - } -} From 3448bea9826c667737dcba44fa1889d6d43edc00 Mon Sep 17 00:00:00 2001 From: Shachar Langbeheim <98546660+shachlanAmazon@users.noreply.github.com> Date: Wed, 6 Mar 2024 19:34:21 +0200 Subject: [PATCH 64/65] Fix node lint. (#1084) Co-authored-by: nihohit --- node/rust-client/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/rust-client/src/lib.rs b/node/rust-client/src/lib.rs index 403fa6b200..3b8c1fd384 100644 --- a/node/rust-client/src/lib.rs +++ b/node/rust-client/src/lib.rs @@ -328,7 +328,7 @@ pub fn create_leaked_bigint(big_int: BigInt) -> [u32; 2] { /// Should NOT be used in production. #[cfg(feature = "testing_utilities")] pub fn create_leaked_double(float: f64) -> [u32; 2] { - let pointer = Box::leak(Box::new(Value::Double(float.into()))) as *mut Value; + let pointer = Box::leak(Box::new(Value::Double(float))) as *mut Value; split_pointer(pointer) } From 94e1186f18de28a1dedfb9b970b6abc15294b4a6 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Wed, 6 Mar 2024 10:07:11 -0800 Subject: [PATCH 65/65] C#: Make IT to manage redis. (#116) (#1072) * C#: Make IT to manage redis. (#116) Signed-off-by: Yury-Fridlyand --- .github/workflows/csharp.yml | 9 +- .../GetAndSet.cs} | 25 ++-- .../tests/Integration/IntegrationTestBase.cs | 134 ++++++++++++++++++ 3 files changed, 149 insertions(+), 19 deletions(-) rename csharp/tests/{AsyncClientTests.cs => Integration/GetAndSet.cs} (80%) create mode 100644 csharp/tests/Integration/IntegrationTestBase.cs diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index eeae290b73..e7916a82e8 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -71,14 +71,14 @@ jobs: working-directory: ./csharp run: dotnet format --verify-no-changes --verbosity diagnostic - - uses: ./.github/workflows/test-benchmark - with: - language-flag: -csharp - - name: Test dotnet ${{ matrix.dotnet }} working-directory: ./csharp run: dotnet test --framework net${{ matrix.dotnet }} "-l:html;LogFileName=TestReport.html" --results-directory . -warnaserror + - uses: ./.github/workflows/test-benchmark + with: + language-flag: -csharp + - name: Upload test reports if: always() continue-on-error: true @@ -88,6 +88,7 @@ jobs: path: | csharp/TestReport.html benchmarks/results/* + utils/clusters/** lint-rust: timeout-minutes: 10 diff --git a/csharp/tests/AsyncClientTests.cs b/csharp/tests/Integration/GetAndSet.cs similarity index 80% rename from csharp/tests/AsyncClientTests.cs rename to csharp/tests/Integration/GetAndSet.cs index e9adfdf97b..ed37512337 100644 --- a/csharp/tests/AsyncClientTests.cs +++ b/csharp/tests/Integration/GetAndSet.cs @@ -2,19 +2,14 @@ * Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ -namespace tests; +namespace tests.Integration; using Glide; -// TODO - need to start a new redis server for each test? -public class AsyncClientTests -{ - [OneTimeSetUp] - public void Setup() - { - Glide.Logger.SetLoggerConfig(Glide.Level.Info); - } +using static tests.Integration.IntegrationTestBase; +public class GetAndSet +{ private async Task GetAndSetRandomValues(AsyncClient client) { var key = Guid.NewGuid().ToString(); @@ -27,7 +22,7 @@ private async Task GetAndSetRandomValues(AsyncClient client) [Test] public async Task GetReturnsLastSet() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", TestConfiguration.STANDALONE_PORTS[0], false)) { await GetAndSetRandomValues(client); } @@ -36,7 +31,7 @@ public async Task GetReturnsLastSet() [Test] public async Task GetAndSetCanHandleNonASCIIUnicode() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", TestConfiguration.STANDALONE_PORTS[0], false)) { var key = Guid.NewGuid().ToString(); var value = "שלום hello 汉字"; @@ -49,7 +44,7 @@ public async Task GetAndSetCanHandleNonASCIIUnicode() [Test] public async Task GetReturnsNull() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", TestConfiguration.STANDALONE_PORTS[0], false)) { var result = await client.GetAsync(Guid.NewGuid().ToString()); Assert.That(result, Is.EqualTo(null)); @@ -59,7 +54,7 @@ public async Task GetReturnsNull() [Test] public async Task GetReturnsEmptyString() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", TestConfiguration.STANDALONE_PORTS[0], false)) { var key = Guid.NewGuid().ToString(); var value = ""; @@ -72,7 +67,7 @@ public async Task GetReturnsEmptyString() [Test] public async Task HandleVeryLargeInput() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", TestConfiguration.STANDALONE_PORTS[0], false)) { var key = Guid.NewGuid().ToString(); var value = Guid.NewGuid().ToString(); @@ -92,7 +87,7 @@ public async Task HandleVeryLargeInput() [Test] public void ConcurrentOperationsWork() { - using (var client = new AsyncClient("localhost", 6379, false)) + using (var client = new AsyncClient("localhost", TestConfiguration.STANDALONE_PORTS[0], false)) { var operations = new List(); diff --git a/csharp/tests/Integration/IntegrationTestBase.cs b/csharp/tests/Integration/IntegrationTestBase.cs new file mode 100644 index 0000000000..635e0544cf --- /dev/null +++ b/csharp/tests/Integration/IntegrationTestBase.cs @@ -0,0 +1,134 @@ +/** + * Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 + */ + +using System.Diagnostics; + +// Note: All IT should be in the same namespace +namespace tests.Integration; + +[SetUpFixture] +public class IntegrationTestBase +{ + internal class TestConfiguration + { + public static List STANDALONE_PORTS { get; internal set; } = new(); + public static List CLUSTER_PORTS { get; internal set; } = new(); + public static Version REDIS_VERSION { get; internal set; } = new(); + } + + [OneTimeSetUp] + public void SetUp() + { + // Stop all if weren't stopped on previous test run + StopRedis(false); + + // Delete dirs if stop failed due to https://github.com/aws/glide-for-redis/issues/849 + Directory.Delete(Path.Combine(_scriptDir, "clusters"), true); + + // Start cluster + TestConfiguration.CLUSTER_PORTS = StartRedis(true); + // Start standalone + TestConfiguration.STANDALONE_PORTS = StartRedis(false); + // Get redis version + TestConfiguration.REDIS_VERSION = GetRedisVersion(); + + TestContext.Progress.WriteLine($"Cluster ports = {string.Join(',', TestConfiguration.CLUSTER_PORTS)}"); + TestContext.Progress.WriteLine($"Standalone ports = {string.Join(',', TestConfiguration.STANDALONE_PORTS)}"); + TestContext.Progress.WriteLine($"Redis version = {TestConfiguration.REDIS_VERSION}"); + } + + [OneTimeTearDown] + public void TearDown() + { + // Stop all + StopRedis(true); + } + + private readonly string _scriptDir; + + // Nunit requires a public default constructor. These variables would be set in SetUp method. + public IntegrationTestBase() + { + string? projectDir = Directory.GetCurrentDirectory(); + while (!(Path.GetFileName(projectDir) == "csharp" || projectDir == null)) + projectDir = Path.GetDirectoryName(projectDir); + + if (projectDir == null) + throw new FileNotFoundException("Can't detect the project dir. Are you running tests from `csharp` directory?"); + + _scriptDir = Path.Combine(projectDir, "..", "utils"); + } + + internal List StartRedis(bool cluster, bool tls = false, string? name = null) + { + string cmd = $"start {(cluster ? "--cluster-mode" : "-r 0")} {(tls ? " --tls" : "")} {(name != null ? " --prefix " + name : "")}"; + return ParsePortsFromOutput(RunClusterManager(cmd, false)); + } + + /// + /// Stop all instances on the given . + /// + internal void StopRedis(bool keepLogs, string? name = null) + { + string cmd = $"stop --prefix {name ?? "redis-cluster"} {(keepLogs ? "--keep-folder" : "")}"; + RunClusterManager(cmd, true); + } + + private string RunClusterManager(string cmd, bool ignoreExitCode) + { + ProcessStartInfo info = new() + { + WorkingDirectory = _scriptDir, + FileName = "python3", + Arguments = "cluster_manager.py " + cmd, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + Process? script = Process.Start(info); + script?.WaitForExit(); + string? error = script?.StandardError.ReadToEnd(); + string? output = script?.StandardOutput.ReadToEnd(); + int? exit_code = script?.ExitCode; + + TestContext.Progress.WriteLine($"cluster_manager.py stdout\n====\n{output}\n====\ncluster_manager.py stderr\n====\n{error}\n====\n"); + + if (!ignoreExitCode && exit_code != 0) + throw new ApplicationException($"cluster_manager.py script failed: exit code {exit_code}."); + + return output ?? ""; + } + + private static List ParsePortsFromOutput(string output) + { + List ports = new(); + foreach (string line in output.Split("\n")) + { + if (!line.StartsWith("CLUSTER_NODES=")) + continue; + + string[] addresses = line.Split("=")[1].Split(","); + foreach (string address in addresses) + ports.Add(uint.Parse(address.Split(":")[1])); + } + return ports; + } + + private static Version GetRedisVersion() + { + ProcessStartInfo info = new() + { + FileName = "redis-server", + Arguments = "-v", + UseShellExecute = false, + RedirectStandardOutput = true, + }; + Process? proc = Process.Start(info); + proc?.WaitForExit(); + string output = proc?.StandardOutput.ReadToEnd() ?? ""; + + // Redis server v=7.2.3 sha=00000000:0 malloc=jemalloc-5.3.0 bits=64 build=7504b1fedf883f2 + return new Version(output.Split(" ")[2].Split("=")[1]); + } +}