diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 5224a9ff2a..8ae8b557ad 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -196,6 +196,7 @@ enum RequestType { BLMPop = 158; XLen = 159; LSet = 165; + XDel = 166; } message Command { diff --git a/glide-core/src/request_type.rs b/glide-core/src/request_type.rs index ba7fba0980..74b870f632 100644 --- a/glide-core/src/request_type.rs +++ b/glide-core/src/request_type.rs @@ -166,6 +166,7 @@ pub enum RequestType { BLMPop = 158, XLen = 159, LSet = 165, + XDel = 166, } fn get_two_word_command(first: &str, second: &str) -> Cmd { @@ -335,6 +336,7 @@ impl From<::protobuf::EnumOrUnknown> for RequestType { ProtobufRequestType::PExpireTime => RequestType::PExpireTime, ProtobufRequestType::XLen => RequestType::XLen, ProtobufRequestType::LSet => RequestType::LSet, + ProtobufRequestType::XDel => RequestType::XDel, } } } @@ -500,6 +502,7 @@ impl RequestType { RequestType::PExpireTime => Some(cmd("PEXPIRETIME")), RequestType::XLen => Some(cmd("XLEN")), RequestType::LSet => Some(cmd("LSET")), + RequestType::XDel => Some(cmd("XDEL")), } } } diff --git a/java/client/src/main/java/glide/api/BaseClient.java b/java/client/src/main/java/glide/api/BaseClient.java index a3bd7da9a7..056ea31159 100644 --- a/java/client/src/main/java/glide/api/BaseClient.java +++ b/java/client/src/main/java/glide/api/BaseClient.java @@ -101,6 +101,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; +import static redis_request.RedisRequestOuterClass.RequestType.XDel; import static redis_request.RedisRequestOuterClass.RequestType.XLen; import static redis_request.RedisRequestOuterClass.RequestType.XTrim; import static redis_request.RedisRequestOuterClass.RequestType.ZAdd; @@ -1236,6 +1237,12 @@ public CompletableFuture xlen(@NonNull String key) { return commandManager.submitNewCommand(XLen, new String[] {key}, this::handleLongResponse); } + @Override + public CompletableFuture xdel(@NonNull String key, @NonNull String[] ids) { + String[] arguments = ArrayUtils.addFirst(ids, key); + return commandManager.submitNewCommand(XDel, arguments, this::handleLongResponse); + } + @Override public CompletableFuture pttl(@NonNull String key) { return commandManager.submitNewCommand(PTTL, new String[] {key}, this::handleLongResponse); diff --git a/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java index 548e685d89..447fb903b2 100644 --- a/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java +++ b/java/client/src/main/java/glide/api/commands/StreamBaseCommands.java @@ -88,4 +88,21 @@ public interface StreamBaseCommands { * } */ CompletableFuture xlen(String key); + + /** + * Removes the specified entries by id from a stream, and returns the number of entries deleted. + * + * @see valkey.io for details. + * @param key The key of the stream. + * @param ids An array of entry ids. + * @return The number of entries removed from the stream. This number may be less than the number + * of entries in ids, if the specified ids don't exist in the + * stream. + * @example + *
{@code
+     * Long num = client.xdel("key", new String[] {"1538561698944-0", "1538561698944-1"}).get();
+     * assert num == 2L; // Stream marked 2 entries as deleted
+     * }
+ */ + CompletableFuture xdel(String key, String[] ids); } 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 8b53c5bf01..c53b70fdae 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -120,6 +120,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; +import static redis_request.RedisRequestOuterClass.RequestType.XDel; import static redis_request.RedisRequestOuterClass.RequestType.XLen; import static redis_request.RedisRequestOuterClass.RequestType.XTrim; import static redis_request.RedisRequestOuterClass.RequestType.ZAdd; @@ -2647,6 +2648,22 @@ public T xlen(@NonNull String key) { return getThis(); } + /** + * Removes the specified entries by id from a stream, and returns the number of entries deleted. + * + * @see valkey.io for details. + * @param key The key of the stream. + * @param ids An array of entry ids. + * @return Command Response - The number of entries removed from the stream. This number may be + * less than the number of entries in ids, if the specified ids + * don't exist in the stream. + */ + public T xdel(String key, String[] ids) { + ArgsArray commandArgs = buildArgs(ArrayUtils.addFirst(ids, key)); + protobufTransaction.addCommands(buildCommand(XDel, commandArgs)); + return getThis(); + } + /** * Returns the remaining time to live of key that has a timeout, in milliseconds. * diff --git a/java/client/src/test/java/glide/api/RedisClientTest.java b/java/client/src/test/java/glide/api/RedisClientTest.java index 5bd0e343c3..4ac1322412 100644 --- a/java/client/src/test/java/glide/api/RedisClientTest.java +++ b/java/client/src/test/java/glide/api/RedisClientTest.java @@ -141,6 +141,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; +import static redis_request.RedisRequestOuterClass.RequestType.XDel; import static redis_request.RedisRequestOuterClass.RequestType.XLen; import static redis_request.RedisRequestOuterClass.RequestType.XTrim; import static redis_request.RedisRequestOuterClass.RequestType.ZAdd; @@ -3999,6 +4000,31 @@ public void xlen_returns_success() { assertEquals(completedResult, payload); } + @Test + @SneakyThrows + public void xdel_returns_success() { + // setup + String key = "testKey"; + String[] ids = {"one-1", "two-2", "three-3"}; + Long completedResult = 69L; + + CompletableFuture testResponse = new CompletableFuture<>(); + testResponse.complete(completedResult); + + // match on protobuf request + when(commandManager.submitNewCommand( + eq(XDel), eq(new String[] {key, "one-1", "two-2", "three-3"}), any())) + .thenReturn(testResponse); + + // exercise + CompletableFuture response = service.xdel(key, ids); + Long payload = response.get(); + + // verify + assertEquals(testResponse, response); + assertEquals(completedResult, payload); + } + @SneakyThrows @Test public void type_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 a30b4ad1f8..6b138e2571 100644 --- a/java/client/src/test/java/glide/api/models/TransactionTests.java +++ b/java/client/src/test/java/glide/api/models/TransactionTests.java @@ -130,6 +130,7 @@ import static redis_request.RedisRequestOuterClass.RequestType.Type; import static redis_request.RedisRequestOuterClass.RequestType.Unlink; import static redis_request.RedisRequestOuterClass.RequestType.XAdd; +import static redis_request.RedisRequestOuterClass.RequestType.XDel; import static redis_request.RedisRequestOuterClass.RequestType.XLen; import static redis_request.RedisRequestOuterClass.RequestType.XTrim; import static redis_request.RedisRequestOuterClass.RequestType.ZAdd; @@ -681,6 +682,9 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)), transaction.xlen("key"); results.add(Pair.of(XLen, buildArgs("key"))); + transaction.xdel("key", new String[] {"12345-1", "98765-4"}); + results.add(Pair.of(XDel, buildArgs("key", "12345-1", "98765-4"))); + transaction.time(); results.add(Pair.of(Time, buildArgs())); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index f79f6e00b6..0a4fbe4757 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -2992,6 +2992,48 @@ public void xadd_xlen_and_xtrim(BaseClient client) { assertTrue(executionException.getCause() instanceof RequestException); } + @SneakyThrows + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getClients") + public void xdel(BaseClient client) { + + String key = UUID.randomUUID().toString(); + String key2 = UUID.randomUUID().toString(); + String streamId1 = "0-1"; + String streamId2 = "0-2"; + String streamId3 = "0-3"; + + assertEquals( + streamId1, + client + .xadd( + key, + Map.of("f1", "foo1", "f2", "bar2"), + StreamAddOptions.builder().id(streamId1).build()) + .get()); + assertEquals( + streamId2, + client + .xadd( + key, + Map.of("f1", "foo1", "f2", "bar2"), + StreamAddOptions.builder().id(streamId2).build()) + .get()); + assertEquals(2L, client.xlen(key).get()); + + // Deletes one stream id, and ignores anything invalid: + assertEquals(1L, client.xdel(key, new String[] {streamId1, streamId3}).get()); + assertEquals(0L, client.xdel(key2, new String[] {streamId3}).get()); + + // Throw Exception: Key exists - but it is not a stream + assertEquals(OK, client.set(key2, "xdeltest").get()); + + ExecutionException executionException = + assertThrows( + ExecutionException.class, () -> client.xdel(key2, new String[] {streamId3}).get()); + assertTrue(executionException.getCause() instanceof RequestException); + } + @SneakyThrows @ParameterizedTest(autoCloseArguments = false) @MethodSource("getClients") diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index b8d46fb80c..9987b7ceb4 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -540,7 +540,8 @@ private static Object[] streamCommands(BaseTransaction transaction) { .xadd(streamKey1, Map.of("field2", "value2"), StreamAddOptions.builder().id("0-2").build()) .xadd(streamKey1, Map.of("field3", "value3"), StreamAddOptions.builder().id("0-3").build()) .xlen(streamKey1) - .xtrim(streamKey1, new MinId(true, "0-2")); + .xtrim(streamKey1, new MinId(true, "0-2")) + .xdel(streamKey1, new String[] {"0-3", "0-5"}); return new Object[] { "0-1", // xadd(streamKey1, Map.of("field1", "value1"), ... .id("0-1").build()); @@ -548,6 +549,7 @@ private static Object[] streamCommands(BaseTransaction transaction) { "0-3", // xadd(streamKey1, Map.of("field3", "value3"), ... .id("0-3").build()); 3L, // xlen(streamKey1) 1L, // xtrim(streamKey1, new MinId(true, "0-2")) + 1L, // .xdel(streamKey1, new String[] {"0-1", "0-5"}); }; }