From 4e3f976da8f21cc1e45d801cf01d0a5a41203149 Mon Sep 17 00:00:00 2001 From: tjzhang-BQ <111323543+tjzhang-BQ@users.noreply.github.com> Date: Mon, 27 May 2024 22:35:39 -0700 Subject: [PATCH] Add command LSET (#312) * Add command LSET --------- Co-authored-by: TJ Zhang --- glide-core/src/protobuf/redis_request.proto | 1 + glide-core/src/request_type.rs | 3 ++ .../src/main/java/glide/api/BaseClient.java | 7 ++++ .../glide/api/commands/ListBaseCommands.java | 19 +++++++++ .../glide/api/models/BaseTransaction.java | 19 +++++++++ .../test/java/glide/api/RedisClientTest.java | 26 ++++++++++++ .../glide/api/models/TransactionTests.java | 4 ++ .../test/java/glide/SharedCommandTests.java | 42 +++++++++++++++++++ .../java/glide/TransactionTestUtilities.java | 9 +++- 9 files changed, 129 insertions(+), 1 deletion(-) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index a2ef588281..3467b9acc5 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -194,6 +194,7 @@ enum RequestType { ExpireTime = 156; PExpireTime = 157; BLMPop = 158; + LSet = 165; } message Command { diff --git a/glide-core/src/request_type.rs b/glide-core/src/request_type.rs index be4befae0b..5d6aafdf65 100644 --- a/glide-core/src/request_type.rs +++ b/glide-core/src/request_type.rs @@ -164,6 +164,7 @@ pub enum RequestType { ExpireTime = 156, PExpireTime = 157, BLMPop = 158, + LSet = 165, } fn get_two_word_command(first: &str, second: &str) -> Cmd { @@ -331,6 +332,7 @@ impl From<::protobuf::EnumOrUnknown> for RequestType { ProtobufRequestType::HStrlen => RequestType::HStrlen, ProtobufRequestType::ExpireTime => RequestType::ExpireTime, ProtobufRequestType::PExpireTime => RequestType::PExpireTime, + ProtobufRequestType::LSet => RequestType::LSet, } } } @@ -494,6 +496,7 @@ impl RequestType { RequestType::HStrlen => Some(cmd("HSTRLEN")), RequestType::ExpireTime => Some(cmd("EXPIRETIME")), RequestType::PExpireTime => Some(cmd("PEXPIRETIME")), + RequestType::LSet => Some(cmd("LSET")), } } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index 2fb2ead8e0..90f60752c2 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -59,6 +59,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.LPushX; import static redis_request.RedisRequestOuterClass.RequestType.LRange; import static redis_request.RedisRequestOuterClass.RequestType.LRem; +import static redis_request.RedisRequestOuterClass.RequestType.LSet; import static redis_request.RedisRequestOuterClass.RequestType.LTrim; import static redis_request.RedisRequestOuterClass.RequestType.MGet; import static redis_request.RedisRequestOuterClass.RequestType.MSet; @@ -1543,4 +1544,10 @@ public CompletableFuture> blmpop( arguments, response -> castMapOfArrays(handleMapOrNullResponse(response), String.class)); } + + @Override + public CompletableFuture lset(@NonNull String key, long index, @NonNull String element) { + String[] arguments = new String[] {key, Long.toString(index), element}; + return commandManager.submitNewCommand(LSet, arguments, this::handleStringResponse); + } } 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 f06c64394c..4135d2a0a4 100644 --- a/java/client/src/main/java/glide/api/commands/ListBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/ListBaseCommands.java @@ -482,4 +482,23 @@ CompletableFuture> blmpop( */ CompletableFuture> blmpop( String[] keys, PopDirection direction, double timeout); + + /** + * Sets the list element at index to element.
+ * The index is zero-based, so 0 means the first element, 1 the second + * element and so on. Negative indices can be used to designate elements starting at the tail of + * the list. Here, -1 means the last element, -2 means the penultimate + * and so forth. + * + * @see valkey.io for details. + * @param key The key of the list. + * @param index The index of the element in the list to be set. + * @return OK. + * @example + *
{@code
+     * String response = client.lset("testKey", 1, "two").get();
+     * assertEquals(response, "OK");
+     * }
+ */ + CompletableFuture lset(String key, long index, String element); } 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 f836decaa9..d119d34a9e 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -74,6 +74,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.LPushX; import static redis_request.RedisRequestOuterClass.RequestType.LRange; import static redis_request.RedisRequestOuterClass.RequestType.LRem; +import static redis_request.RedisRequestOuterClass.RequestType.LSet; import static redis_request.RedisRequestOuterClass.RequestType.LTrim; import static redis_request.RedisRequestOuterClass.RequestType.LastSave; import static redis_request.RedisRequestOuterClass.RequestType.Lolwut; @@ -3723,6 +3724,24 @@ public T lmpop(@NonNull String[] keys, @NonNull PopDirection direction) { return getThis(); } + /** + * Sets the list element at index to element.
+ * The index is zero-based, so 0 means the first element, 1 the second + * element and so on. Negative indices can be used to designate elements starting at the tail of + * the list. Here, -1 means the last element, -2 means the penultimate + * and so forth. + * + * @see valkey.io for details. + * @param key The key of the list. + * @param index The index of the element in the list to be set. + * @return Command Response - OK. + */ + public T lset(@NonNull String key, long index, @NonNull String element) { + ArgsArray commandArgs = buildArgs(key, Long.toString(index), element); + protobufTransaction.addCommands(buildCommand(LSet, 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 c864cf4138..7a1341f6ba 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -95,6 +95,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.LPushX; import static redis_request.RedisRequestOuterClass.RequestType.LRange; import static redis_request.RedisRequestOuterClass.RequestType.LRem; +import static redis_request.RedisRequestOuterClass.RequestType.LSet; import static redis_request.RedisRequestOuterClass.RequestType.LTrim; import static redis_request.RedisRequestOuterClass.RequestType.LastSave; import static redis_request.RedisRequestOuterClass.RequestType.Lolwut; @@ -5101,4 +5102,29 @@ public void lmpop_with_count_returns_success() { assertEquals(testResponse, response); assertEquals(value, payload); } + + @SneakyThrows + @Test + public void lset_returns_success() { + // setup + String key = "testKey"; + long index = 0; + String element = "two"; + String[] arguments = new String[] {key, "0", element}; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(OK); + + // match on protobuf request + when(commandManager.submitNewCommand(eq(LSet), eq(arguments), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.lset(key, index, element); + 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 53a3dca6fe..0f368e9d83 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -84,6 +84,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.LPushX; import static redis_request.RedisRequestOuterClass.RequestType.LRange; import static redis_request.RedisRequestOuterClass.RequestType.LRem; +import static redis_request.RedisRequestOuterClass.RequestType.LSet; import static redis_request.RedisRequestOuterClass.RequestType.LTrim; import static redis_request.RedisRequestOuterClass.RequestType.LastSave; import static redis_request.RedisRequestOuterClass.RequestType.Lolwut; @@ -853,6 +854,9 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), transaction.blmpop(new String[] {"key"}, PopDirection.LEFT, 1L, 0.1); results.add(Pair.of(BLMPop, buildArgs("0.1", "1", "key", "LEFT", "COUNT", "1"))); + transaction.lset("key", 0, "zero"); + results.add(Pair.of(LSet, buildArgs("key", "0", "zero"))); + 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 2b39e51a40..1ebf618cbc 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -4207,4 +4207,46 @@ public void blmpop_timeout_check(BaseClient client) { testClient.blmpop(new String[] {key}, PopDirection.LEFT, 0).get(3, TimeUnit.SECONDS)); } } + + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void lset(BaseClient client) { + // setup + String key = "testKey"; + String nonExistingKey = "nonExisting"; + long index = 0; + long oobIndex = 10; + long negativeIndex = -1; + String element = "zero"; + String[] lpushArgs = {"four", "three", "two", "one"}; + String[] expectedList = {"zero", "two", "three", "four"}; + String[] expectedList2 = {"zero", "two", "three", "zero"}; + + // key does not exist + ExecutionException noSuchKeyException = + assertThrows( + ExecutionException.class, () -> client.lset(nonExistingKey, index, element).get()); + assertInstanceOf(RequestException.class, noSuchKeyException.getCause()); + + // pushing elements to list + client.lpush(key, lpushArgs).get(); + + // index out of range + ExecutionException indexOutOfBoundException = + assertThrows(ExecutionException.class, () -> client.lset(key, oobIndex, element).get()); + assertInstanceOf(RequestException.class, indexOutOfBoundException.getCause()); + + // assert lset result + String response = client.lset(key, index, element).get(); + assertEquals(OK, response); + String[] updatedList = client.lrange(key, 0, -1).get(); + assertArrayEquals(updatedList, expectedList); + + // assert lset with a negative index for the last element in the list + String response2 = client.lset(key, negativeIndex, element).get(); + assertEquals(OK, response2); + String[] updatedList2 = client.lrange(key, 0, -1).get(); + assertArrayEquals(updatedList2, expectedList2); + } } diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index f7dddad6d7..285e4bc57e 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -250,6 +250,7 @@ private static Object[] listCommands(BaseTransaction transaction) { String listKey2 = "{ListKey}-2-" + UUID.randomUUID(); String listKey3 = "{ListKey}-3-" + UUID.randomUUID(); String listKey4 = "{ListKey}-4-" + UUID.randomUUID(); + String listKey5 = "{ListKey}-5-" + UUID.randomUUID(); transaction .lpush(listKey1, new String[] {value1, value1, value2, value3, value3}) @@ -268,7 +269,10 @@ private static Object[] listCommands(BaseTransaction transaction) { .lpush(listKey3, new String[] {value1, value2, value3}) .linsert(listKey3, AFTER, value2, value2) .blpop(new String[] {listKey3}, 0.01) - .brpop(new String[] {listKey3}, 0.01); + .brpop(new String[] {listKey3}, 0.01) + .lpush(listKey5, new String[] {value2, value3}) + .lset(listKey5, 0, value1) + .lrange(listKey5, 0, -1); if (REDIS_VERSION.isGreaterThanOrEqualTo("7.0.0")) { transaction @@ -298,6 +302,9 @@ private static Object[] listCommands(BaseTransaction transaction) { 4L, // linsert(listKey3, AFTER, value2, value2) new String[] {listKey3, value3}, // blpop(new String[] { listKey3 }, 0.01) new String[] {listKey3, value1}, // brpop(new String[] { listKey3 }, 0.01) + 2L, // lpush(listKey5, new String[] {value2, value3}) + OK, // lset(listKey5, 0, value1) + new String[] {value1, value2} // lrange(listKey5, 0, -1) }; if (REDIS_VERSION.isGreaterThanOrEqualTo("7.0.0")) {