From 929e6670dd11e20eb7492e11b75405dc9c719b64 Mon Sep 17 00:00:00 2001 From: Alon Arenberg <93711356+alon-arenberg@users.noreply.github.com> Date: Sun, 7 Jul 2024 13:05:25 +0300 Subject: [PATCH] support mset and msetnx with GlideString (#1842) * support mset and msetnx with GlideString * fix comments --- .../src/main/java/glide/api/BaseClient.java | 13 +++++ .../api/commands/StringBaseCommands.java | 33 +++++++++++ .../test/java/glide/api/RedisClientTest.java | 51 ++++++++++++++++ .../test/java/glide/SharedCommandTests.java | 58 ++++++++++++++++--- .../java/glide/standalone/CommandTests.java | 18 +++--- 5 files changed, 157 insertions(+), 16 deletions(-) diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index f2b56f5c02..7d8459a70a 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -843,6 +843,12 @@ public CompletableFuture mset(@NonNull Map keyValueMap) return commandManager.submitNewCommand(MSet, args, this::handleStringResponse); } + @Override + public CompletableFuture msetBinary(@NonNull Map keyValueMap) { + GlideString[] args = convertMapToKeyValueGlideStringArray(keyValueMap); + return commandManager.submitNewCommand(MSet, args, this::handleStringResponse); + } + @Override public CompletableFuture objectEncoding(@NonNull String key) { return commandManager.submitNewCommand( @@ -4022,6 +4028,13 @@ public CompletableFuture msetnx(@NonNull Map keyValueMa return commandManager.submitNewCommand(MSetNX, args, this::handleBooleanResponse); } + @Override + public CompletableFuture msetnxBinary( + @NonNull Map keyValueMap) { + GlideString[] args = convertMapToKeyValueGlideStringArray(keyValueMap); + return commandManager.submitNewCommand(MSetNX, args, this::handleBooleanResponse); + } + @Override public CompletableFuture lcs(@NonNull String key1, @NonNull String key2) { String[] arguments = new String[] {key1, key2}; diff --git a/java/client/src/main/java/glide/api/commands/StringBaseCommands.java b/java/client/src/main/java/glide/api/commands/StringBaseCommands.java index 151b5e909b..58f7b83834 100644 --- a/java/client/src/main/java/glide/api/commands/StringBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/StringBaseCommands.java @@ -300,6 +300,22 @@ public interface StringBaseCommands { */ CompletableFuture mset(Map keyValueMap); + /** + * Sets multiple keys to multiple values in a single operation. + * + * @apiNote When in cluster mode, the command may route to multiple nodes when keys in + * keyValueMap map to different hash slots. + * @see valkey.io for details. + * @param keyValueMap A key-value map consisting of keys and their respective values to set. + * @return Always OK. + * @example + *
{@code
+     * String result = client.msetBinary(Map.of(gs("key1"), gs("value1"), gs("key2"), gs("value2")}).get();
+     * assert result.equals("OK"));
+     * }
+ */ + CompletableFuture msetBinary(Map keyValueMap); + /** * Sets multiple keys to values if the key does not exist. The operation is atomic, and if one or * more keys already exist, the entire operation fails. @@ -317,6 +333,23 @@ public interface StringBaseCommands { */ CompletableFuture msetnx(Map keyValueMap); + /** + * Sets multiple keys to values if the key does not exist. The operation is atomic, and if one or + * more keys already exist, the entire operation fails. + * + * @apiNote When in cluster mode, all keys in keyValueMap must map to the same hash + * slot. + * @see valkey.io for details. + * @param keyValueMap A key-value map consisting of keys and their respective values to set. + * @return true if all keys were set. false if no key was set. + * @example + *
{@code
+     * Boolean result = client.msetnxBinary(Map.of(gs("key1"), gs("value1"), gs("key2"), gs("value2")}).get();
+     * assert result;
+     * }
+ */ + CompletableFuture msetnxBinary(Map keyValueMap); + /** * Increments the number stored at key by one. If key does not exist, it * is set to 0 before performing the operation. diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index ebfb49f63a..464c421fe3 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -1750,6 +1750,31 @@ public void mset_returns_success() { assertEquals(OK, payload); } + @SneakyThrows + @Test + public void mset_binary_returns_success() { + // setup + Map keyValueMap = new LinkedHashMap<>(); + keyValueMap.put(gs("key1"), gs("value1")); + keyValueMap.put(gs("key2"), gs("value2")); + GlideString[] args = {gs("key1"), gs("value1"), gs("key2"), gs("value2")}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(MSet), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.msetBinary(keyValueMap); + String payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(OK, payload); + } + @SneakyThrows @Test public void msetnx_returns_success() { @@ -1776,6 +1801,32 @@ public void msetnx_returns_success() { assertEquals(value, payload); } + @SneakyThrows + @Test + public void msetnx_binary_returns_success() { + // setup + Map keyValueMap = new LinkedHashMap<>(); + keyValueMap.put(gs("key1"), gs("value1")); + keyValueMap.put(gs("key2"), gs("value2")); + GlideString[] args = {gs("key1"), gs("value1"), gs("key2"), gs("value2")}; + Boolean value = true; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(value); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(MSetNX), eq(args), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.msetnxBinary(keyValueMap); + Boolean payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(value, payload); + } + @SneakyThrows @Test public void incr_returns_success() { diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index 0693d3a51a..3ebecea104 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -621,21 +621,39 @@ public void mset_mget_existing_non_existing_key(BaseClient client) { client.mget(new String[] {key1, key2, nonExisting, key3}).get()); } + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void mset_mget_existing_non_existing_key_binary(BaseClient client) { + // keys are from different slots + GlideString key1 = gs(UUID.randomUUID().toString()); + GlideString key2 = gs(UUID.randomUUID().toString()); + GlideString key3 = gs(UUID.randomUUID().toString()); + GlideString nonExisting = gs(UUID.randomUUID().toString()); + GlideString value = gs(UUID.randomUUID().toString()); + Map keyValueMap = Map.of(key1, value, key2, value, key3, value); + + assertEquals(OK, client.msetBinary(keyValueMap).get()); + assertArrayEquals( + new GlideString[] {value, value, null, value}, + client.mget(new GlideString[] {key1, key2, nonExisting, key3}).get()); + } + @SneakyThrows @ParameterizedTest(autoCloseArguments = false) @MethodSource("getClients") public void mset_mget_binary(BaseClient client) { // keys are from different slots - String key1 = UUID.randomUUID().toString(); - String key2 = UUID.randomUUID().toString(); - String key3 = UUID.randomUUID().toString(); - String value = UUID.randomUUID().toString(); - Map keyValueMap = Map.of(key1, value, key2, value, key3, value); + GlideString key1 = gs(UUID.randomUUID().toString()); + GlideString key2 = gs(UUID.randomUUID().toString()); + GlideString key3 = gs(UUID.randomUUID().toString()); + GlideString value = gs(UUID.randomUUID().toString()); + Map keyValueMap = Map.of(key1, value, key2, value, key3, value); - assertEquals(OK, client.mset(keyValueMap).get()); + assertEquals(OK, client.msetBinary(keyValueMap).get()); assertArrayEquals( - new GlideString[] {gs(value), gs(value), gs(value)}, - client.mget(new GlideString[] {gs(key1), gs(key2), gs(key3)}).get()); + new GlideString[] {value, value, value}, + client.mget(new GlideString[] {key1, key2, key3}).get()); } @SneakyThrows @@ -10821,6 +10839,30 @@ public void msetnx(BaseClient client) { assertNull(client.get(key3).get()); } + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void msetnx_binary(BaseClient client) { + // keys are from different slots + GlideString key1 = gs("{key}-1" + UUID.randomUUID()); + GlideString key2 = gs("{key}-2" + UUID.randomUUID()); + GlideString key3 = gs("{key}-3" + UUID.randomUUID()); + GlideString nonExisting = gs(UUID.randomUUID().toString()); + GlideString value = gs(UUID.randomUUID().toString()); + Map keyValueMap1 = Map.of(key1, value, key2, value); + Map keyValueMap2 = Map.of(key2, value, key3, value); + + // all keys are empty, successfully set + assertTrue(client.msetnxBinary(keyValueMap1).get()); + assertArrayEquals( + new GlideString[] {value, value, null}, + client.mget(new GlideString[] {key1, key2, nonExisting}).get()); + + // one of the keys is already set, nothing gets set + assertFalse(client.msetnxBinary(keyValueMap2).get()); + assertNull(client.get(key3).get()); + } + @SneakyThrows @ParameterizedTest(autoCloseArguments = false) @MethodSource("getClients") diff --git a/java/integTest/src/test/java/glide/standalone/CommandTests.java b/java/integTest/src/test/java/glide/standalone/CommandTests.java index f5e66eba70..211bf14a25 100644 --- a/java/integTest/src/test/java/glide/standalone/CommandTests.java +++ b/java/integTest/src/test/java/glide/standalone/CommandTests.java @@ -1563,9 +1563,10 @@ public void scan_binary() { GlideString initialCursor = gs("0"); int numberKeys = 500; - Map keys = new HashMap<>(); + Map keys = new HashMap<>(); for (int i = 0; i < numberKeys; i++) { - keys.put("{key}-" + i + "-" + UUID.randomUUID(), "{value}-" + i + "-" + UUID.randomUUID()); + keys.put( + gs("{key}-" + i + "-" + UUID.randomUUID()), gs("{value}-" + i + "-" + UUID.randomUUID())); } int resultCursorIndex = 0; @@ -1585,7 +1586,7 @@ public void scan_binary() { assertDeepEquals(new String[] {}, negativeResult[resultCollectionIndex]); // Add keys to the database using mset - regularClient.mset(keys).get(); + regularClient.msetBinary(keys).get(); Object[] result; Object[] keysFound = new GlideString[0]; @@ -1607,7 +1608,7 @@ public void scan_binary() { // check that each key added to the database is found through the cursor Object[] finalKeysFound = keysFound; - keys.forEach((key, value) -> assertTrue(ArrayUtils.contains(finalKeysFound, gs(key)))); + keys.forEach((key, value) -> assertTrue(ArrayUtils.contains(finalKeysFound, key))); } @Test @@ -1701,11 +1702,12 @@ public void scan_binary_with_options() { int resultCollectionIndex = 1; // Add string keys to the database using mset - Map stringKeys = new HashMap<>(); + Map stringKeys = new HashMap<>(); for (int i = 0; i < 10; i++) { - stringKeys.put("{key}-" + i + "-" + matchPattern, "{value}-" + i + "-" + matchPattern); + stringKeys.put( + gs("{key}-" + i + "-" + matchPattern), gs("{value}-" + i + "-" + matchPattern)); } - regularClient.mset(stringKeys).get(); + regularClient.msetBinary(stringKeys).get(); // Add set keys to the database using sadd List setKeys = new ArrayList<>(); @@ -1743,7 +1745,7 @@ public void scan_binary_with_options() { // check that each key added to the database is found through the cursor Object[] finalKeysFound = keysFound; - stringKeys.forEach((key, value) -> assertTrue(ArrayUtils.contains(finalKeysFound, gs(key)))); + stringKeys.forEach((key, value) -> assertTrue(ArrayUtils.contains(finalKeysFound, key))); // scan for sets by match pattern: options = ScanOptions.builder().matchPattern("*" + matchPattern).count(100L).type(SET).build();